bocketmq 多个消费者同时_在rocketmq中,部署多台机器上的消费者负载均衡是如何解决的?...

本文探讨了RocketMQ中客户端如何实现负载均衡,而非依赖中央调度器。通过消费者获取在线列表和队列列表,对两者排序后应用算法进行分配,确保每个队列被恰当地分配给消费者实例,同时避免冲突。默认的负载均衡算法源码也被展示,并指出该方法存在的问题,如策略一致性、订阅关系同步和队列数量变化时的挑战。
摘要由CSDN通过智能技术生成

很多人提到负载均衡,先入为主地都认为应该在一个集中的地方做分配,例如有一个如一个基于zookeeper之类中央调度器模块,让它做分配。实际上,很多负载均衡完全可以在客户端实现的,如RPC框架,假设每个调用方实例都按照轮询的方式做后端的实例选择,那么可以认为全局实际上也是平均的。

回到消息中间件领域,通常情况下,中央调度的这个分配角色就是broker。之所以以中央负载均衡的消息中间件居多,个人认为是因为很多传统的消息中间件采取的是Push(推送消息)的模式进行消息投递。

回到RocketMQ,或许你已经知道RocketMQ内部实现实际上是Pull(消息拉取)的模式,基于这个模式,实际上,我们是可以让负载均衡由客户端去自行分配的。

需要做到负载均衡,有两个“全局因子”是需要获取的:针对这个topic订阅的在线的消费者列表。如:当前在线ClientA,ClientB,ClientC。

当前的队列列表:如:brokerA-q0,brokerB-q0,brokerC-q0。

只要让客户端拿到这两个“视图“,客户端其实是有能力相互不窜乱的情况下获得正确的队列分配的。最后期望达到这样的效果:希望每个队列都有人分配,同时不会有一个队列被分配到多个消费者实例中。注:消费者是允许完全分配不到队列的,典型场景为消费者实例数多于队列总数的时候。

大概过程如下:每个实例询问broker,最后都拿到一样的"视图"(消费者列表、队列列表)。

每个实例都知道自己的实例名称(如ClientA),这个名称肯定在#1中视图里面的消费者在线列表中

针对#1的视图,对消费者列表和队列列表做一个排序。

调用同样的算法分配函数,函数输出的是本实例应该分配的队列列表。

当然了,你可能会问,相同的视图,相同的函数,是怎么出不窜乱的分配结果的?那是因为#2中,每个消费者实例自己的名字是不同的,也就是说运行这个算法,你可以认为有三个入参:1.在线消费者列表 2.队列列表 3.当前实例名称

由于入参不同,自然有能力算出来不同的出参。

以下是默认的负载均衡算法的源码,大家可以结合阅读理解以下:

@Override

public List allocate(String consumerGroup, String currentCID, List mqAll,

List cidAll) {

if (currentCID == null || currentCID.length() < 1) {

throw new IllegalArgumentException("currentCID is empty");

}

if (mqAll == null || mqAll.isEmpty()) {

throw new IllegalArgumentException("mqAll is null or mqAll empty");

}

if (cidAll == null || cidAll.isEmpty()) {

throw new IllegalArgumentException("cidAll is null or cidAll empty");

}

List result = new ArrayList();

if (!cidAll.contains(currentCID)) {

log.info("[BUG] ConsumerGroup: {} The consumerId: {} not in cidAll: {}",

consumerGroup,

currentCID,

cidAll);

return result;

}

int index = cidAll.indexOf(currentCID);

int mod = mqAll.size() % cidAll.size();

int averageSize =

mqAll.size() <= cidAll.size() ? 1 : (mod > 0 && index < mod ? mqAll.size() / cidAll.size()

+ 1 : mqAll.size() / cidAll.size());

int startIndex = (mod > 0 && index < mod) ? index * averageSize : index * averageSize + mod;

int range = Math.min(averageSize, mqAll.size() - startIndex);

for (int i = 0; i < range; i++) {

result.add(mqAll.get((startIndex + i) % mqAll.size()));

}

return result;

}

当然这种方式实际上有一些问题如:

1. 每个消费者的分配策略必须是一样的,否则就有有队列重复、有队列漏分配。典型出现问题的场景是:希望更换分配策略,在所有实例完成升级之前。

2.每个消费者实例本身的订阅关系应该是一样的,否则会有队列漏分配的问题(具体原因还涉及到消费者列表获取的细节,这里不阐述),典型情况如:需要多监听一个topicX,在全部消费者实例更新发布前,会发现有一些队列无法分配出去。

3.在队列数量变化的过程中(如broker扩容),可能会有短暂的队列分配重复、队列漏分配的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值