rocketmq 消费者负载均衡相关源码

RocketMQ Rebalance机制解析
本文深入探讨RocketMQ中Rebalance机制的实现细节,包括如何动态调整消费者处理的消息队列,确保消息均衡分配。介绍了RebalanceService的工作原理及关键方法如doRebalance和rebalanceByTopic的具体流程。
/**
 * Copyright (C) 2010-2013 Alibaba Group Holding Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.rocketmq.client.impl.consumer;

import org.slf4j.Logger;

import com.alibaba.rocketmq.client.impl.factory.MQClientFactory;
import com.alibaba.rocketmq.client.log.ClientLogger;
import com.alibaba.rocketmq.common.ServiceThread;


/**
 * Rebalance服务
 * 
 * <br/>
 * chen.si 主要负责对每个consumer group,定期检查 group包含的 活动consumer 是否有变化,以及时更新 consumer处理的分区队列
 * 
 * @author shijia.wxr<vintage.wang@gmail.com>
 * @since 2013-7-24
 */
public class RebalanceService extends ServiceThread {
    private final Logger log = ClientLogger.getLog();
    private final MQClientFactory mqClientFactory;


    public RebalanceService(MQClientFactory mqClientFactory) {
        this.mqClientFactory = mqClientFactory;
    }

    private static long WaitInterval = 1000 * 10;


    @Override
    public void run() {
        log.info(this.getServiceName() + " service started");

        while (!this.isStoped()) {
            this.waitForRunning(WaitInterval);
            this.mqClientFactory.doRebalance();
        }

        log.info(this.getServiceName() + " service end");
    }


    @Override
    public String getServiceName() {
        return RebalanceService.class.getSimpleName();
    }
}

com.alibaba.rocketmq.client.impl.consumer.RebalanceImpl.doRebalance()

 public void doRebalance() {
        Map<String, SubscriptionData> subTable = this.getSubscriptionInner();
        if (subTable != null) {
        	/*
        	 * chen.si 针对每个topic,检查当前consumer group中的 活动consumer list是否有变化,如果变化了,需要重新调整 分区 
        	 */
            for (final Map.Entry<String, SubscriptionData> entry : subTable.entrySet()) {
                final String topic = entry.getKey();
                try {
                    this.rebalanceByTopic(topic);
                }
                catch (Exception e) {
                    if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                        log.warn("rebalanceByTopic Exception", e);
                    }
                }
            }
        }

        this.truncateMessageQueueNotMyTopic();
    }

com.alibaba.rocketmq.client.impl.consumer.RebalanceImpl.rebalanceByTopic(String)
 private void rebalanceByTopic(final String topic) {
        switch (messageModel) {
        case BROADCASTING: {
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            if (mqSet != null) {
                boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet);
                if (changed) {
                    this.messageQueueChanged(topic, mqSet, mqSet);
                    log.info("messageQueueChanged {} {} {} {}",//
                        consumerGroup,//
                        topic,//
                        mqSet,//
                        mqSet);
                }
            }
            else {
                log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
            }
            break;
        }
        case CLUSTERING: {
        	/*
        	 * chen.si topic下的所有的分区队列
        	 */
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            /*
             * chen.si 一个consumer,会有一个唯一的clientId。这里通过clientId来标识 同一个consumer group下的当前活动的所有consumer。
             */
            List<String> cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
            if (null == mqSet) {
                if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    log.warn("doRebalance, {}, but the topic[{}] not exist.", consumerGroup, topic);
                }
            }

            if (null == cidAll) {
                log.warn("doRebalance, {} {}, get consumer id list failed", consumerGroup, topic);
            }

            if (mqSet != null && cidAll != null) {
                List<MessageQueue> mqAll = new ArrayList<MessageQueue>();
                mqAll.addAll(mqSet);

                // 排序
                Collections.sort(mqAll);
                Collections.sort(cidAll);

                AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;

                // 执行分配算法
                List<MessageQueue> allocateResult = null;
                try {
                    allocateResult = strategy.allocate(this.mQClientFactory.getClientId(), mqAll, cidAll);
                }
                catch (Throwable e) {
                    log.error("AllocateMessageQueueStrategy.allocate Exception", e);
                }

                Set<MessageQueue> allocateResultSet = new HashSet<MessageQueue>();
                if (allocateResult != null) {
                    allocateResultSet.addAll(allocateResult);
                }

                // 更新本地队列
                boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet);
                if (changed) {
                    log.info("rebalanced result changed. mqSet={}, ConsumerId={}, mqSize={}, cidSize={}",
                        allocateResult, this.mQClientFactory.getClientId(), mqAll.size(), cidAll.size());

                    /*
                     * chen.si 当前consumer负责的分区队列有变化,需要通知consumer。 当前consumer需要重新调整fetch
                     */
                    this.messageQueueChanged(topic, mqSet, allocateResultSet);
                    log.info("messageQueueChanged {} {} {} {}",//
                        consumerGroup,//
                        topic,//
                        mqSet,//
                        allocateResultSet);

                    log.info("messageQueueChanged consumerIdList: {}",//
                        cidAll);
                }
            }
            break;
        }
        default:
            break;
        }
    }

/**
 * Copyright (C) 2010-2013 Alibaba Group Holding Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.alibaba.rocketmq.client.consumer.rebalance;

import java.util.ArrayList;
import java.util.List;

import com.alibaba.rocketmq.client.consumer.AllocateMessageQueueStrategy;
import com.alibaba.rocketmq.common.message.MessageQueue;


/**
 * 平均分配队列算法
 * 
 * @author fuchong<yubao.fyb@alibaba-inc.com>
 * @author manhong.yqd<manhong.yqd@taobao.com>
 * @since 2013-7-24
 */
public class AllocateMessageQueueAveragely implements AllocateMessageQueueStrategy {
    @Override
    public List<MessageQueue> allocate(String currentCID, List<MessageQueue> mqAll, List<String> cidAll) {
    	/*
    	 * chen.si 参数很重要,这里把接口的方法参数 引用到这里:
    	 * 			currentCID  当前ConsumerId
    	 * 			mqAll 		当前Topic的所有队列集合,无重复数据,且有序
    	 * 			cidAll 		当前订阅组的所有Consumer集合,无重复数据,且有序
    	 * 			return 		分配结果,无重复数据
    	 */
        if (currentCID == null || currentCID.length() < 1) {
            throw new IllegalArgumentException("currentCID is empty");
        }
        if (mqAll == null || mqAll.size() < 1) {
            throw new IllegalArgumentException("mqAll is null or mqAll size < 1");
        }
        if (cidAll == null || cidAll.size() < 1) {
            throw new IllegalArgumentException("cidAll is null or cidAll size < 1");
        }

        List<MessageQueue> result = new ArrayList<MessageQueue>();
        if (!cidAll.contains(currentCID)) { // 不存在此ConsumerId ,直接返回
            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;
    }
}



com.alibaba.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService.start()
   public void start() {
    }
	
	
com.alibaba.rocketmq.client.impl.consumer.ConsumeMessageOrderlyService.start()

    public void start() {
        // 启动定时lock队列服务
        if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl
            .messageModel())) {
            this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    ConsumeMessageOrderlyService.this.lockMQPeriodically();
                }
            }, 1000 * 1, ProcessQueue.RebalanceLockInterval, TimeUnit.MILLISECONDS);
        }
    }


com.alibaba.rocketmq.client.impl.consumer.RebalanceImpl.lockAll()
 public void lockAll() {
        HashMap<String, Set<MessageQueue>> brokerMqs = this.buildProcessQueueTableByBrokerName();

        Iterator<Entry<String, Set<MessageQueue>>> it = brokerMqs.entrySet().iterator();
        while (it.hasNext()) {
            Entry<String, Set<MessageQueue>> entry = it.next();
            final String brokerName = entry.getKey();
            final Set<MessageQueue> mqs = entry.getValue();

            if (mqs.isEmpty())
                continue;

            FindBrokerResult findBrokerResult =
                    this.mQClientFactory.findBrokerAddressInSubscribe(brokerName, MixAll.MASTER_ID, true);
            if (findBrokerResult != null) {
                LockBatchRequestBody requestBody = new LockBatchRequestBody();
                requestBody.setConsumerGroup(this.consumerGroup);
                requestBody.setClientId(this.mQClientFactory.getClientId());
                requestBody.setMqSet(mqs);

                try {
                    Set<MessageQueue> lockOKMQSet =
                            this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ(
                                findBrokerResult.getBrokerAddr(), requestBody, 1000);

                    // 锁定成功的队列
                    for (MessageQueue mq : lockOKMQSet) {
                        ProcessQueue processQueue = this.processQueueTable.get(mq);
                        if (processQueue != null) {
                            if (!processQueue.isLocked()) {
                                log.info("the message queue locked OK, Group: {} {}", this.consumerGroup, mq);
                            }

                            processQueue.setLocked(true);
                            processQueue.setLastLockTimestamp(System.currentTimeMillis());
                        }
                    }
                    // 锁定失败的队列
                    for (MessageQueue mq : mqs) {
                        if (!lockOKMQSet.contains(mq)) {
                            ProcessQueue processQueue = this.processQueueTable.get(mq);
                            if (processQueue != null) {
                                processQueue.setLocked(false);
                                log.warn("the message queue locked Failed, Group: {} {}", this.consumerGroup,
                                    mq);
                            }
                        }
                    }
                }
                catch (Exception e) {
                    log.error("lockBatchMQ exception, " + mqs, e);
                }
            }
        }
    }	
	
	
	


 

转载于:https://my.oschina.net/xiaominmin/blog/1592790

<think>我们正在讨论RocketMQ中自定义负载均衡策略的实现。根据用户需求,需要提供实现指南。 首先,我们需要理解RocketMQ负载均衡机制。RocketMQ负载均衡发生在消费者端,消费者组内的每个消费者会负责消费一部分消息队列。 默认的负载均衡策略是AllocateMessageQueueAveragely(平均分配)等。自定义策略需要实现AllocateMessageQueueStrategy接口。 步骤: 1. 创建自定义负载均衡策略类,实现org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy接口。 2. 实现接口中的allocate方法,该方法负责分配消息队列给消费者。 3. 实现接口中的getName方法,返回策略名称。 4.消费者端配置自定义负载均衡策略。 注意:在实现allocate方法时,需要根据当前消费者组内的消费者列表和主题下的消息队列列表,以及当前消费者ID来进行分配。 下面是一个示例,展示如何实现一个简单的随机分配策略(仅用于演示,实际生产环境可能需要更复杂的逻辑):</think>### RocketMQ 自定义负载均衡策略实现指南 在 RocketMQ 中实现自定义负载均衡策略需通过实现 `AllocateMessageQueueStrategy` 接口完成,以下是详细步骤: #### 1. **创建自定义策略类** 实现 `AllocateMessageQueueStrategy` 接口并重写关键方法: ```java import org.apache.rocketmq.client.consumer.AllocateMessageQueueStrategy; import org.apache.rocketmq.common.message.MessageQueue; import java.util.List; import java.util.ArrayList; import java.util.Collections; public class CustomLoadBalanceStrategy implements AllocateMessageQueueStrategy { // 核心分配逻辑 @Override public List<MessageQueue> allocate(String consumerGroup, String currentCID, List<MessageQueue> mqAll, List<String> cidAll) { List<MessageQueue> result = new ArrayList<>(); int index = cidAll.indexOf(currentCID); if (index < 0) return result; // 示例:轮询分配算法 for (int i = 0; i < mqAll.size(); i++) { if (i % cidAll.size() == index) { result.add(mqAll.get(i)); } } return result; } // 策略名称标识 @Override public String getName() { return "CUSTOM_ROUND_ROBIN"; } } ``` #### 2. **注册策略到消费者** 在消费者初始化时注入自定义策略: ```java DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("GROUP_NAME"); consumer.setAllocateMessageQueueStrategy(new CustomLoadBalanceStrategy()); // 注入策略 consumer.subscribe("TOPIC_NAME", "*"); consumer.start(); ``` #### 3. **关键配置参数** | 参数 | 说明 | 示例值 | |------|------|--------| | `consumerGroup` | 消费者组名 | `"order_consumer"` | | `currentCID` | 当前消费者ID | `"CID_192.168.1.101@12345"` | | `mqAll` | 主题所有消息队列 | `[MessageQueue{topic='TP_TEST', brokerName='broker-a', queueId=0}, ...]` | | `cidAll` | 消费者组所有客户端ID | `["CID_1", "CID_2", ...]` | #### 4. **负载均衡算法设计要点 - **均匀分配**:确保每个消费者获得近似等量的队列 $$ \text{avg} = \lceil \frac{|mqAll|}{|cidAll|} \rceil $$ - **容错处理**:检测离线消费者并重新分配其队列 - **粘性分配**:尽量减少消费者重启导致的队列迁移 #### 5. **部署注意事项** 1. **策略一致性**:消费者组内所有实例必须使用相同策略 2. **动态生效**:修改策略后需重启消费者集群 3. **日志监控**:添加调试日志验证分配结果: ```java System.out.printf("Consumer %s got queues: %s%n", currentCID, result.stream().map(MessageQueue::getQueueId).toList()); ``` #### 6. **测试验证方法** ```java // 模拟分配调用 List<String> cids = Arrays.asList("CID_A", "CID_B", "CID_C"); List<MessageQueue> queues = IntStream.range(0, 9) .mapToObj(i -> new MessageQueue("TP_TEST", "broker1", i)) .collect(Collectors.toList()); AllocateMessageQueueStrategy strategy = new CustomLoadBalanceStrategy(); System.out.println("CID_A分配: " + strategy.allocate("G1", "CID_A", queues, cids)); ``` ### 典型应用场景 1. **机架感知分配**:优先分配同机房队列降低网络延迟 2. **权重分配**:根据消费者性能差异动态分配负载 3. **灰度发布**:定向分配特定队列到新版本消费者 > **注意**:避免在分配逻辑中进行阻塞操作,否则会导致消费者启动卡顿[^1]。完整接口定义参考 [AllocateMessageQueueStrategy 源码](https://github.com/apache/rocketmq/blob/master/client/src/main/java/org/apache/rocketmq/client/consumer/AllocateMessageQueueStrategy.java)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值