生产环境逐步容器化的过程中遇到了一些坑,特此记录一下:
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,在实际消费中出现了重复消费问题。如下:
当然,在网上搜索时也发现有的朋友遇到的是不消费的状态,具体为何没有深究。
问题分析
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
持续更新。。。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。