Consumer的负载均衡

想要提高Consumer的处理速度,可以启动多个Consumer并发处理,这个时候就涉及如何在多个Consumer之间负载均衡的问题,接下来结合源码分析Consumer的负载均衡实现。

要做负载均衡,必须知道一些全局信息,也就是一个ConsumerGroup里到底有多少个Consumer,知道了全局信息,才可以根据某种算法来分配,比如简单地平均分到各个Consumer。在RocketMQ中,负载均衡或者消息分配是在Consumer端代码中完成的,Consumer从Broker处获得全局信息,然后自己做负载均衡,只处理分给自己的那部分消息。

1.DefaultMQPushConsumer的负载均衡

DefaultMQPushConsumer的负载均衡过程不需要使用者操心,客户端程序会自动处理,每个DefaultMQPushConsumer启动后,会马上会触发一个doRebalance动作;而且在同一个ConsumerGroup里加入新的DefaultMQPush-Consumer时,各个Consumer都会被触发doRebalance动作。

如图7-2所示,具体的负载均衡算法有五种,默认用的是第一种AllocateMessageQueueAveragely。负载均衡的结果与Topic的Message Queue数量,以及ConsumerGroup里的Consumer的数量有关。负载均衡的分配粒度只到Message Queue,把Topic下的所有Message Queue分配到不同的Consumer中,所以Message Queue和Consumer的数量关系,或者整除关系影响负载均衡结果。

图7-2 RocketMQ客户端负载均衡策略

以AllocateMessageQueueAveragely策略为例,如果创建Topic的时候,把Message Queue数设为3,当Consumer数量为2的时候,有一个Consumer需要处理Topic三分之二的消息,另一个处理三分之一的消息;当Consumer数量为4的时候,有一个Consumer无法收到消息,其他3个Consumer各处理Topic三分之一的消息。可见Message Queue数量设置过小不利于做负载均衡,通常情况下,应把一个Topic的Message Queue数设置为16。

2.DefaultMQPullConsumer的负载均衡

Pull Consumer可以看到所有的Message Queue,而且从哪个Message Queue读取消息,读消息时的Offset都由使用者控制,使用者可以实现任何特殊方式的负载均衡。

DefaultMQPullConsumer有两个辅助方法可以帮助实现负载均衡,一个是registerMessageQueueListener函数,如代码清单7-5所示。

代码清单7-5 registerMessageQueueListener

Consumer.registerMessageQueueListener("TOPICNAME", new MessageQueue-Listener() {
public void MessageQueueChanged(String Topic, Set<MessageQueue> mqAll, Set<MessageQueue> mqDivided) }

registerMessageQueueListener函数在有新的Consumer加入或退出时被触发。另一个辅助工具是MQPullConsumerScheduleService类,使用这个Class类似使用DefaultMQPushConsumer,但是它把Pull消息的主动性留给了使用者,如代码清单7-6所示。

代码清单7-6 使用MQPullConsumerScheduleService示例

public class PullConsumerServiceTest {
public static void main(String\[\] args) throws MQClientException {
final MQPullConsumerScheduleService scheduleService = new MQPull-ConsumerScheduleService("PullConsumerService1");
scheduleService.getDefaultMQPullConsumer().setNamesrvAddr("localh-ost:9876");
scheduleService.setMessageModel(MessageModel.CLUSTERING );
scheduleService.registerPullTaskCallback("testPullConsumer", new PullTaskCallback() {
public void doPullTask(MessageQueue mq, PullTaskContext context) {
MQPullConsumer Consumer = context.getPullConsumer();
try {
long Offset = Consumer.fetchConsumeOffset(mq, false);
if (Offset < 0)
Offset = 0;
PullResult pullResult = Consumer.pull(mq, "*", Offset, 32);
System.out.printf("%s%n", Offset + "\t" + mq + "\t" + pullResult);
switch (pullResult.getPullStatus()) {
case FOUND:
break;
case NO_MATCHED_MSG:
break;
case NO_NEW_MSG:
case OFFSET_ILLEGAL:
break;
default:
break;
}
Consumer.updateConsumeOffset(mq, pullResult.getNextBeginOffset());
context.setPullNextDelayTimeMillis(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
});
scheduleService.start();
}
}

然后我们看一看在MQPullConsumerScheduleService类的实现里,实现负载均衡的代码,如代码清单7-7所示。

代码清单7-7 MQPullConsumerScheduleService的负载均衡实现

class MessageQueueListenerImpl implements MessageQueueListener {
@Override
public void MessageQueueChanged(String Topic, Set<MessageQueue> mqAll, Set<MessageQueue> mqDivided) {
MessageModel MessageModel =
MQPullConsumerScheduleService.this.defaultMQPullConsumer.getMessageModel();
switch (MessageModel) {
case BROADCASTING:
MQPullConsumerScheduleService.this.putTask(Topic, mqAll);
break;
case CLUSTERING :
MQPullConsumerScheduleService.this.putTask(Topic, mqDivided);
break;
default:
break;
}
}
}

从源码中可以看出,用户通过更改MessageQueueListenerImpl的实现来做自己的负载均衡策略。

相关推荐
geovindu7 分钟前
rust: pool Pattern
开发语言·后端·设计模式·rust·对象池简写模式
Rain的Java大神之路9 分钟前
Java 工程师做 AI,核心机会在「落地」而非「训练」
java·后端·ai编程
柠檬味拥抱11 分钟前
基于 YOLO11 的六类生活垃圾视觉分类系统 | 用于社区投放点智能督导、分拣线品类分流、投错率统计分析 | 附完整源码与效果演示
后端
程序员小新70028 分钟前
RAG 实战与踩坑:交通法规智能问答助手
人工智能·后端
拖孩28 分钟前
AI 时代,个人开发者到底能不能靠一个小程序赚到钱
前端·后端·微信小程序
EAIReport36 分钟前
SpringBoot 与 SpringCloud 深度解析:核心特点、差异与协同关系
spring boot·后端·spring cloud
梦里寻码41 分钟前
从 0 到 1 用 NestJS + LangChain 搭一套可售卖的 SaaS AI 客服:挂件一行代码接入,知识库秒变 SEO 帮助中心
前端·后端·github
imDwAaY44 分钟前
程序为什么会卡在死锁里?从四个必要条件到预防与解除
后端·操作系统
Bazingga1 小时前
知识注入的两条路:RAG知识库与Skills蒸馏
后端
薛之谦_1 小时前
JAVA 多线程编程:从基础原理到实战应用
java·开发语言