解决多个rocketmq 多个MQ实例时使用DefaultMQProducer NamesrvAddr被覆盖问题
背景:系统里已经有一个mq在使用,现在要接入其他mq,实现和其他系统的消息对接,所以两个mq有自己独立的配置,要相互不影响。使用DefaultMQProducer创建之后发现setNamesrvAddr配置参数成功,但是获取到的brokerName全是loaclhost,也就是说获取到的全是原来系统在使用的mq的broker。
翻看源码发现DefaultMQProducerImpl.java 的169 170行存在问题,在此之前this.defaultMQProducer的NamesrvAddr是新的配置ip,但是获取到的mQClientFactory不正确,继续往下看getOrCreateMQClientInstance方法
if (!this.defaultMQProducer.getProducerGroup().equals(MixAll.CLIENT_INNER_PRODUCER_GROUP)) {
this.defaultMQProducer.changeInstanceNameToPID();
}
this.mQClientFactory = MQClientManager.getInstance().getOrCreateMQClientInstance(this.defaultMQProducer, rpcHook);
boolean registerOK = mQClientFactory.registerProducer(this.defaultMQProducer.getProducerGroup(), this);
通过getOrCreateMQClientInstance方法可以看到MQClientInstance是从 factoryTable这个Map中获取的,如果根据key值clientId获取不到的话才会创建新的MQClientInstance,点进clientConfig.buildMQClientId()方法
public MQClientInstance getOrCreateMQClientInstance(final ClientConfig clientConfig, RPCHook rpcHook) {
String clientId = clientConfig.buildMQClientId();
MQClientInstance instance = this.factoryTable.get(clientId);
if (null == instance) {
instance =
new MQClientInstance(clientConfig.cloneClientConfig(),
this.factoryIndexGenerator.getAndIncrement(), clientId, rpcHook);
MQClientInstance prev = 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的生成规则是ip+@InstanceName,其中InstanceName每次启动生成的就是固定的,ip是启动的时候本身的ip,所以每次启动生成的clientId都是一致的,而在我的代码使用DefaultMQProducer创建实例的时候就已经初始化过了,factoryTable中已经存在老mq的实例,所以新mq通过一模一样的clientId只能获取到老mq的配置生成的MQClientInstance
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时还加上了unitName,其实这里如果 unitName不同,那么生成的 clientId就不同了。在DefaultMQProducer实例化的时候调用setUnitName即可
DefaultMQProducer defaultMQProducer = new DefaultMQProducer("group");
defaultMQProducer.setUnitName("yourName");
或者升级RocketMQ Client到4.9.0 ,我用的rocketmq-spring-boot-starter升级到2.3.0也行
升级后的instanceName会加毫秒值
public void changeInstanceNameToPID() {
if (this.instanceName.equals("DEFAULT")) {
this.instanceName = UtilAll.getPid() + "#" + System.nanoTime();
}
}
buildMQClientId也有所改动
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);
}
if (this.enableStreamRequestType) {
sb.append("@");
sb.append(RequestType.STREAM);
}
return sb.toString();
}