先考虑一个问题:在一个JVM进程中,启动多个消费者/生产者,有没有限制?使用的netty client是同一个还是多个?

以DefaultMQPushConsumer为例,start---this.defaultMQPushConsumerImpl.start()---mQClientFactory.start()----this.mQClientAPIImpl.start()-----this.remotingClient.start(),消费者和生产者中保存间接指向remotingClient的引用,所有的请求都通过这个remotingClient来进行。而在mQClientFactory生成的时候,以消费者启动为例,getOrCreateMQClientInstance方法中,看到有一个以clientId为key 的缓存factoryTable(注意这里的MQClientManager是全局唯一的),clientId的生成是ip+instanceName(广播模式)/pid(集群模式)+自定义的unitName,先不考虑unitName,那么同一个springboot项目中,所有消费者的clientId都是相同的,那么MQClientInstance也就是mQClientFactory都是同一个,那么remotingClient也是同一个。那么在mQClientFactory.registerConsumer方法中,如果同一个进程中同一个consumerGroup下面有多个消费者,是注册不成功的。(同一个进程下,所有的mQClientFactory都是同一个,consumer启动的时候都要根据consumerGroup检查唯一性,所以同一进程下,同一个consumerGroup下只能有一个consumer)

这大概是因为同一进程下,用同一个remotingClient,对channel的管理是按照broker/namesrv的地址来一一对应的,如果两个consumer,需要两个channel。

也就是说:不考虑unitName情况下,同一个进程下remotingClient是同一个,而且同一个进程同一个consumerGroup下只能有一个consumer。那么如果是两个不同的进程中的consumer,属于同一个consumerGroup,但是messageModel或者topic、tag这些细节并不一样,会发生什么事情?

一个consumer/producer在启动的时候,引用的那个唯一的MQClientInstance会在定时任务中向所有的broker发出心跳(下一个consumer/producer启动的时候有serviceState做标记),心跳发出的信息中用prepareHeartbeatData方法把所有的consumer和producer的信息发送到broker,到了broker这边,有updateChannel和updateSubscription,前者主要是看channel是不是新注册的(先不管),后者是看当前group下有没有保存所有的这次注册的属于这个group的topic,如果不是,那么updated为true,r2为true,会发出通知,让所有consumer重新负载均衡。

如果同一group下两个consumer(只能在两个不同的服务中)的topic不同,那么就直接进入if (null == prev) ,updated为true。

如果同一个group下两个consumer的topic相同,但是messageModel不同,我们知道广播模式是到达最大重试次数才发送回去,所以直接送到死信队列而不会订阅重试队列的,但是集群模式是会的,所以集群模式会比广播模式多一个retry的topic,如果广播订阅了一个A topic,那么集群就会有retry和A两个,如果当前subscriptionTable只有之前广播模式的那个consumer注册的A topic信息,集群模式的heartbeat过来的时候,会发现retry没有,一样进入if (null == prev) 。反之如果是A topic过来遇到存的是A 和 retry,遇到retry的时候下面的sub.getTopic().equals(oldTopic)就是false,existed为false,一样updated为true。

这样导致的结果就是,任何一个consumer的heartbeat都会导致重新负载均衡,这是效率是很低的。

所以结论就是,同一个group下面的所有consumer,messageModel要一样,所订阅的topic也要一样,至于tag则可以不一样,这样可以使得不同的consumer收到不同的信息

 

ps:上面说的是同一个consumerGroup启动两个consumer的情况,假设是两个不同的consumerGroup下的consumer或者说一个consumer一个producer先后启动,但是两者的nameservice地址不同,会发生什么呢?

根据上面的逻辑,clientId没变的话,第一个之后的consumer/producer会使用第一个的MQClientInstance,那么nameService地址也会是第一个MQClientInstance生成的时候设定的。也就是说一个remoteClient(或者说netty client端)只能