在同一个Java进程中连接多个RocketMQ集群
RocketMQ使用场景:
- 作为消费者:用户应用 --> MQ集群A --> 权益应用 消息内容:客户开户/销户相关消息
- 作为生产者:权益应用 --> MQ集群B --> 信贷应用 消息内容:卡券事件消息
问题现象:
一个Java进程要连接多个RocketMQ集群时,作为消费者功能无法正常使用,作为生产者功能可以正常使用
原因:RocketMQ Client 有一个核心类
MQClientManager
, 在我们需要使用 MQ Client 实例的时候,实际上都是通过它的 getAndCreateMQClientInstance
方法进行创建的;public MQClientInstance getAndCreateMQClientInstance(ClientConfig clientConfig, RPCHook rpcHook) { String clientId = clientConfig.buildMQClientId(); MQClientInstance instance = (MQClientInstance)this.factoryTable.get(clientId); if (null == instance) { instance = new MQClientInstance(clientConfig.cloneClientConfig(), this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook); MQClientInstance prev = (MQClientInstance)this.factoryTable.putIfAbsent(clientId, instance); if (prev != null) { instance = prev; log.warn("Returned Previous MQClientInstance for clientId:[{}]", clientId); } else { log.info("Created new MQClientInstance for clientId:[{}]", clientId); } } return instance; }
我们可以看到它利用客户的配置信息生成一个固定的 clientId,以此去缓存 factoryTable 中查找,不存在才会创建全新一个实例。
那么,可以理解一个 clientID 仅能存在一个连接实例了,可这个 clientId 是怎么产生的呢?继续跟踪看看这段代码
1 2 3 4 5 6 7 8 9 10 11 | 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(); } |
代码层面上对 clientId 进行了约定,格式为 “ClientIp@InstanceName” 格式,当 unitName 不为空的时候还会在后面加上 “@unitName”。
解决方案:
方法1:设定不同的 instanceName:
instanceName = System.getProperty("rocketmq.client.name", "DEFAULT");
从系统属性中读取出来的,也就是一般在 JVM 启动时设定的。当然此处可以改变,但是需要评估修改之后的影响。这是为什么多少 RocketMQ Client 都只能连接一个服务器的原因。
方法2:可以将RocketMQ Client升级到4.9.0
,解决这个问题的:
1 2 3 4 5 | public void changeInstanceNameToPID() { if ( this .instanceName.equals( "DEFAULT" )) { this .instanceName = UtilAll.getPid() + "#" + System.nanoTime(); } } |
方法3:可以设定不同的 unitName
DefaultMQProducer producer1 = new DefaultMQProducer("producer_group_1");
producer1.setNamesrvAddr("192.168.2.230:9876");
producer1.setUnitName("producer1");
producer1.start();
DefaultMQProducer producer2 = new DefaultMQProducer("producer_group_1");
producer2.setNamesrvAddr("192.168.2.231:9876");
producer2.setUnitName("producer2");
producer2.start();
方法4:使用一个工具,将 MQClientInstance
实例 放到工具创建的隔离环境中, 从而实现多个实例完全隔离的效果。
综上:前两种方法的逻辑是通过修改 clientId 实现多个实例,而方法 4 的逻辑则是 “既然你的缓存已经有这个 key,我就换个缓存”。
代码地址
分类:
MQ
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
2020-07-30 Nacos环境搭建