生产环境逐步容器化的过程中遇到了一些坑,特此记录一下:

Docker默认网络模式带来的ip问题

这是初步使用docker时遇见最多的问题,在多个组件或中间件上体现,但是本质问题还是因为docker默认的网络模式是bridge,会为当前容器分配格式为172.17.0.X的IP地址。这个IP的特点是在一台宿主机上递增,但是假如同一个服务在多个宿主机上都使用容器部署,很可能会出现IP地址相同的情况,对于以IP来识别终端的一些组件和中间件来说,会出现问题。

针对此问题,大部分情况下可以设置网络模式为host来解决:

--net=host

但是如此一来,同一宿主机上就不能放置多个同服务的容器,因为端口相同(host模式下不能使用 -p 设置端口),除非修改源码改变端口,但是这样代价太大。另外对于个别问题还是解决不了。另外也是在初步涉及docker,使用默认模式更保险一些。

RocketMQ消费者客户端由于IP相同导致重复消费问题

  • 问题表象

    A,B两台宿主机都有一个基于docker的消费客户端,IP地址都为172.17.0.1,在实际消费中出现了重复消费问题。如下:

    img

    当然,在网上搜索时也发现有的朋友遇到的是不消费的状态,具体为何没有深究。

  • 问题分析

    ​ RocketMQ用一个叫ClientID的概念,来唯一标记一个客户端实例,一个客户端实例对于Broker而言会开辟一个Netty的客户端实例。 而ClientID是由ClientIP+InstanceName构成,如下源码:

    public String buildMQClientId() {
      StringBuilder sb = new StringBuilder();
      sb.append(this.getClientIP());
    
      sb.append("@");
      sb.append(this.getInstanceName());
      if (!UtilAll.isBlank(this.unitName)) {
        sb.append("@");
        sb.append(this.unitName);
      }
      return sb.toString();
    }

​ 而instanceName一般我们时不会设置的,默认的话会取进程号

public void changeInstanceNameToPID() {
  if (this.instanceName.equals("DEFAULT")) {
    this.instanceName = String.valueOf(UtilAll.getPid());
  }
}

​ 故如果一个进程中多个实例(无论Producer还是Consumer)ClientIP和InstanceName都一样,他们将公用一个内部实例(同一套网络连接,线程资源等)

​ 此外,此ClientID在对于Consumer负载均衡的时候起到唯一标识的作用,一旦多个实例(无论不同进程、不通机器、还是同一进程)的多个Consumer实例有一样的ClientID,负载均衡的时候必然RocketMQ任然会把两个实例当作一个client(因为同样一个clientID)。

​ 故为了避免不必要的问题,ClientIP + instance Name的组合建议唯一,这里我采用自定义instanceName的方式,在springboot下如下:

@Configuration
@AutoConfigureBefore(RocketMQAutoConfiguration.class)
public class RocketMQCustomFrontConfig {
    static {
        System.setProperty("rocketmq.client.name", String.valueOf(System.currentTimeMillis()));
    }
}

到此问题解决。

使用nacos作为服务发现导致服务间调用不通问题

​ 同样的原因,导致nacos服务节点ip一模一样,但是由于是虚拟ip,所以会影响服务间的调用,解决方法也很简单,我是在docker启动脚本中添加如下配置:

--spring.cloud.nacos.discovery.ip=$nacos_discovery_id

​ 当然在springboot环境下配置文件中也可以设置,不过不同环境下就需要修改源码配置文件,所以不做考虑。若使用的是其他服务发现组件,都有各自配置支持。

阿里Sentinel下节点状态不对且链路不通问题

​ Sentinel下的问题要从2个方面考虑:ip和端口。基于docker环境,默认情况下:

  • 若ip为虚拟ip则链路不通。
  • sentinel的客户端暴露数据的端口默认为8719,若端口被占用则自增;但是在docker环境下,每一个客户端的端口都为8719,这时候使用docker -p映射端口就难以确定或者每个节点都需要不同的脚本,这个实在太麻烦了。
  • 若ip和端口都一致,则会出现多个节点在dashboard被覆盖的问题

​ 解决方法如下:

## 关于ip可以使用一下参数设置
--spring.cloud.sentinel.transport.client-ip=$nacos_discovery_id

## 至于端口,我才用的方法就是设计好端口规则,然后在springboot中手动设置sentinel的数据端口,最后在docker脚本中通过-p配置固定端口,这样至少每一个应用只需要一个脚本,另外对于一个应用的不同节点最好放在不同宿主机上
spring:
  cloud:
    sentinel:
      transport:
        port: 17019

持续更新。。。


朱世伟
7 声望7 粉丝