负载均衡
RocketMQ 负载均衡在 Client 端完成,分为 Producer 端发送消息时的负载均衡和 Consumer 端订阅消息的负载均衡。
Producer 的负载均衡
Producer 端在发送消息时,先根据 Topic 找到指定的 TopicPublishInfo,获取了 TopicPublishInfo 路由信息后,RocketMQ 的客户端在默认方式下 selectOneMessageQueue() 方法会从 TopicPublishInfo 中的 messageQueueList 中选择一个队列(MessageQueue)进行发送消息。默认采用轮训,以达到让消息平均落在不同的队列上。
有一个 sendLatencyFaultEnable 开关变量,如果开启,在随机递增取模的基础上,再过滤掉 not available 的 Broker。所谓的"latencyFaultTolerance",是指对之前失败的,按一定的时间做退避。例如,如果上次请求的 latency 超过 550Lms,就退避 3000Lms;超过 1000L,就退避 60000L;如果关闭,采用随机递增取模的方式选择一个队列(MessageQueue)来发送消息。
Consumer 的负载均衡
在 RocketMQ 中,Consumer 端两种消费模式(Push/Pull)是基于拉模式来获取消息的,而在 Push 模式只是对 pull 模式的一种封装,本质实现为消息拉取线程在从服务器拉取到一批消息后,然后提交到消息消费线程池后,又“马不停蹄”的继续向服务器再次尝试拉取消息。如果未拉取到消息,则延迟一下又继续拉取。
基于拉模式的消费方式(Push/Pull)中,均需要 Consumer 端知道从 Broker 端的哪一个消息队列中去获取消息。有必要在 Consumer 端来做负载均衡,即 Broker 端中多个 MessageQueue 分配给同一个 ConsumerGroup 中的哪些 Consumer 消费。
集群模式
在集群消费模式下,每条消息只需要投递到订阅该 topic 的 ConsumerGroup 下的一个实例即可。RocketMQ 采用主动拉取的方式拉取并消费消息,拉取时需要明确指定拉取哪一条 messageQueue。而每当实例的数量有变更,都会触发一次所有实例的负载均衡,此时会按照 queue 的数量和实例的数量平均分配 queue 给每个实例。
默认的分配算法是 AllocateMessageQueueAveragely

另外一种平均的算法是 AllocateMessageQueueAveragelyByCircle,平均分摊每一条 queue,只是以环状轮流分 queue 的形式,如下图:

集群模式下,每个 queue 都是只允许分配一个实例,由于拉取哪些消息是 consumer 主动控制的,如果多个实例同时消费一个 queue 的消息就会导致同一个消息在不同的实例下被消费多次,所以算法上都是一个 queue 只分给一个 consumer 实例,一个 consumer 实例可以允许同时分到不同的 queue。
通过增加 consumer 实例去分摊 queue 的消费,可以起到水平扩展的消费能力的作用。而有实例下线的时候,会重新触发负载均衡,此时原来分配到的 queue 将分配到其他实例上继续消费。
如果 consumer 实例的数量比 message queue 的总数量还多的话,多出来的 consumer 实例将无法分到 queue,也就无法消费到消息,也就无法起到分摊负载的作用了。所以需要控制让 queue 的总数量大于等于 consumer 的数量。
广播模式
由于广播模式下要求一条消息需要投递到一个消费组下面所有的消费者实例,所以也就没有消息被分摊消费的说法。
心跳包发送
在 Consumer 启动后,它就会通过定时任务不断地向 RocketMQ 集群中的所有 Broker 实例发送心跳包(其中包含,消息消费分组名称、订阅关系集合、消息通信模式和客户端 id 的值等信息)。
Broker 端在收到 Consumer 的心跳消息后,会将它维护在 ConsumerManager 的本地缓存变量 - consumerTable,同时并将封装后的客户端网络通道信息保存在本地缓存变量 — channelInfoTable 中,为之后做 Consumer 端的负载均衡提供可以依据的元数据信息。
浙公网安备 33010602011771号