RocketMQ 同一 JVM 启动多个 Producer 导致 NameServer 只连一个的问题

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 只连一个的问题记录,感谢观看。

相关推荐
金銀銅鐵10 小时前
同一个 Hello World,Kotlin 和 Java 在字节码层面差在哪
jvm·后端·kotlin
yeccheng2 天前
MySQL 索引超详解:从底层 B+ 树到面试高频考点
java·开发语言·jvm·mysql·面试
阿里云云原生3 天前
RocketMQ 和 Higress 荣获“直通乌镇”全球互联网大赛开源先锋社区奖
rocketmq
On The Road :4 天前
深入理解 JVM 垃圾回收:GC 是什么、如何判定垃圾、四大回收算法全解析
jvm·算法
Yves..4 天前
JVM 学习笔记:内存区域、类加载机制与垃圾回收
jvm
akinggw5 天前
libagentcore - 一个基于c++的AI多agent系统升级到2.0版本
开发语言·jvm·c++
yh弓长5 天前
JVM 核心机制
jvm
tg_xianheyun6 天前
腾讯云国际账号注册代充值适合哪些海外业务场景?
jvm
yuniko-n6 天前
【JVM】对象内存布局
java·开发语言·jvm
wuminyu6 天前
JVM执行引擎学习总结:热点字节码编译成本地机器码
java·linux·c语言·jvm·c++