RocketMQ 同一 JVM 启动多个 Producer 导致 NameServer 只连一个的问题
Hi,我是阿昌,今天记录一个容器化迁移 RocketMQ 时踩到的坑:
同一个 JVM 里同时存在两个 NameServer 配置,结果只改了某个 Producer 的推送地址,消息还是发到了旧集群。
一、问题现象
迁移 MQ 时,先把消费方迁移到了容器里,接着开始测试生产者推消息。
当时只改了某个生产者的推送地址,项目里随之出现了两个 NAME_SERVER。
奇怪的地方在于:
- 第一次启动时,推送消息是成功的;
- 第二次启动后,消息推送成功了,但消费方一直消费不到;
- 登录 MQ 后台才发现,新建的 Topic 跑到老 MQ 地址上去了。


也就是说:
消息确实发出去了,但发错了地方。
二、问题定位
初步判断是生产方出问题了:明明改了 name_server,为什么还会推到老地址?

顺着代码往下看,最终定位到获取 MQ 客户端工厂的地方。



核心结论是:
两个 Producer 的 IP 是一样的,而且都没有设置
instanceName。
因为 IP 相同,instanceName 又没显式设置,所以这两个 Producer 最终会复用同一个 MQ 客户端实例。
流程可以简单画成这样:
text
Producer A -> NameServer 新地址
Producer B -> NameServer 老地址
↓
两个 Producer 计算出的 ClientId 相同
↓
MQClientManager 返回同一个 MQClientInstance
↓
同一个客户端实例只维护了其中一个 NameServer 的连接
↓
消息自然就发到了那个唯一连上的集群
所以不是配置丢了,而是两个 Producer 在底层共用了一个客户端实例。
三、为什么多个 Producer 会共用一个客户端实例
RocketMQ 获取客户端实例的逻辑大致如下:
java
public MQClientInstance getOrCreateMQClientFactory(ClientConfig clientConfig) {
String clientId = clientConfig.buildMQClientId();
MQClientInstance instance = factoryTable.get(clientId);
if (instance == null) {
instance = new MQClientInstance(clientConfig);
factoryTable.put(clientId, instance);
}
return instance;
}
关键就在 clientId 上。
clientId 的生成逻辑简化后大概是这样:

text
clientId = clientIP + "@" + instanceName + "@" + unitName
默认情况下如果不设置 instanceName,RocketMQ 会使用类似 ip@pid 作为唯一标识。
那么问题就来了:
- 同一个 JVM,IP 相同;
- 多个 Producer 都没有设置
instanceName; - 计算出来的
clientId一样; MQClientManager根据这个相同的clientId,返回了同一个MQClientInstance;- 多个 Producer 最终只共享一个与 MQ 交互的客户端实例。
所以虽然两个 Producer 配了不同的 NameServer 地址,但底层只有一个客户端实例真正生效,最终只会往其中一个集群发消息。
四、解决方案
1、Producer
默认情况下不需要设置 instanceName,RocketMQ 会使用类似 ip@pid 作为唯一标识。
如果同一个 JVM 中,不同的 Producer 需要往不同的 RocketMQ 集群发送消息,就需要设置不同的 instanceName。
否则多个 Producer 内部只会有一个 MQClientInstance,最终只往一个集群发消息。
示例写法:
java
DefaultMQProducer producerA = new DefaultMQProducer("producerGroupA");
producerA.setNamesrvAddr("new-nameserver:9876");
producerA.setInstanceName("producer-a");
DefaultMQProducer producerB = new DefaultMQProducer("producerGroupB");
producerB.setNamesrvAddr("old-nameserver:9876");
producerB.setInstanceName("producer-b");
2、Consumer
Consumer 的情况也一样。
默认情况下不需要设置 instanceName,RocketMQ 会使用类似 ip@pid 作为标识。
如果显式设置了 instanceName,RocketMQ 会使用 ip@instanceName 作为 Consumer 的唯一标识。
需要注意:
- 同一 IP 下的不同 JVM 实例,不能使用同一个
instanceName; - 不同 IP 之间,可以使用相同的
instanceName。
五、最终建议
这次迁移问题的最终处理方式是:
把
name_server统一改成新地址。
也就是说,不建议在这种场景下通过增加 instanceName 来解决问题。
原因是:
- 多个客户端实例意味着多套网络连接、多组线程资源;
- 会增加线程数,占用更多内存;
- 还容易引入其他难以排查的问题。
对于"迁移 MQ 集群"这种场景,统一切到新 NameServer 比维护多个客户端实例更干净。
六、参数说明
| 参数名 | 默认值 | 说明 |
|---|---|---|
instanceName |
DEFAULT |
客户端实例名称。客户端创建的多个 Producer、Consumer 实际可能共用一个内部实例,这个实例包含网络连接、线程资源等。 |
七、总结
这个问题本质上不是 NameServer 配置没生效,而是:
同一 JVM 下,多个 Producer 因为 ClientId 相同,底层共用了同一个
MQClientInstance。
短期解法:
- 如果确实要一个 JVM 连多个 MQ 集群,就为不同 Producer/Consumer 设置不同的
instanceName。
长期解法:
- 如果只是集群迁移,直接统一所有
name_server到新地址,避免维护多套客户端资源。
排查这类问题时,别只看业务代码里的 setNamesrvAddr,还要看到底层客户端实例是否被复用了。
以上就是这次 RocketMQ 同一 JVM 启动多个 Producer 导致 NameServer 只连一个的问题记录,感谢观看。