rocketmq核心源码分析第十六篇一消息消费五部曲一消息平衡

组件图

  • RebalanceService线程每20秒进行自旋再平衡
  • 获取mqclient注册的消费者集合,遍历每个消费者再平衡
  • ConsumerImpl调用rebalanceImpl再平衡
  • 获取当前订阅的topic集合进行再平衡
  • 分别处理广播和集群模式
  • 获取nameserver上当前topic的队列集合信息对应缓存topicSubscribeInfoTable
  • 通过远程通信获取消费组注册的消费者节点集合
  • 通过allocateMessageQueueStrategy进行分配
  • 处理PullRequest唤醒消息拉取线程
    在这里插入图片描述

流程图

  • rebalance线程根据subscriptionInner订阅信息对topicSubscribeInfoTable进行再平衡
  • 平衡结果放置processQueueTable,并将processQueueTable不存在的messagequeue构建pullrequest
  • PullRequest一MessageQueue一ProcessQueue为一对一关系
    在这里插入图片描述

源码分析一RebalanceService

  • 每20秒自旋进行再平衡
 
public class RebalanceService extends ServiceThread {
    @Override
    public void run() {
        log.info(this.getServiceName() + " service started");
        while (!this.isStopped()) {
            默认每隔20秒则执行在平衡
            this.waitForRunning(waitInterval);
            this.mqClientFactory.doRebalance();
        }
}

源码分析MQClientInstance.doRebalance

  • 消费都会注册到consumerTable
  • 通过consumerTable遍历所有的消费者进行再平衡
  public void doRebalance() {
        for (Map.Entry<String, MQConsumerInner> entry : this.consumerTable.entrySet()) {
            MQConsumerInner impl = entry.getValue();
            if (impl != null) {
                try {
                    impl.doRebalance();
                } catch (Throwable e) {
                    log.error("doRebalance exception", e);
                }
            }
        }
    }

DefaultMQPushConsumerImpl.doRebalance

  • 通过rebalanceImpl执行再平衡
 
public void doRebalance() {
    if (!this.pause) {
        MessageListenerOrderly   MessageListenerConcurrently 看是顺序还是并发
        this.rebalanceImpl.doRebalance(this.isConsumeOrderly());
    }
}

RebalanceImpl.doRebalance

  • 获取当前的订阅信息subscriptionInner
  • 遍历订阅信息的主题
  • 对每个主题进行再平衡
public void doRebalance(final boolean isOrder) {
    获取当前的订阅信息subscriptionInner
    Map<String, SubscriptionData> subTable = this.getSubscriptionInner();
    if (subTable != null) {
        遍历订阅信息的主题
        for (final Map.Entry<String, SubscriptionData> entry : subTable.entrySet()) {
            final String topic = entry.getKey();
            try {
                对每个主题进行再平衡
                this.rebalanceByTopic(topic, isOrder);
            } catch (Throwable e) {
                if (!topic.startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
                    log.warn("rebalanceByTopic Exception", e);
                }
            }
        }
    }

    this.truncateMessageQueueNotMyTopic();
}

RebalanceImpl.rebalanceByTopic一核心代码

  • 获取topic所有的MessageQueue
  • 通过broker获取同一个consumerGroup所有启动的clientId
  • 排序MessageQueue集合和clientId集合
  • 排序后交给strategy执行分配负载
  • 判断分配负载是否发生了变化,底层如发生变化创建PullRequest,交由消息拉取线程处理
  • 消费者进程注册broker
private void rebalanceByTopic(final String topic, final boolean isOrder) {
    switch (messageModel) {
        ...... 删除广播模式代码
        
        集群模式
        case CLUSTERING: {
            Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
            通过broker获取同一个consumerGroup所有启动的clientId
            List<String> cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);
            
            ...... 删除其他代码
            if (mqSet != null && cidAll != null) {
                List<MessageQueue> mqAll = new ArrayList<MessageQueue>();
                mqAll.addAll(mqSet);
                排序后交给strategy执行分配负载 只要排序算法的入参一样出参永远一样 避免分布式一致性投票的复杂性和可靠性问题
                Collections.sort(mqAll);
                Collections.sort(cidAll);
                AllocateMessageQueueAveragely
                    AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;
                表示当前clientId应该消费哪些MessageQueue
                List<MessageQueue> allocateResult = null;
           		...... 删除其他代码
                allocateResult = strategy.allocate(
                    this.consumerGroup,
                    this.mQClientFactory.getClientId(),
                    mqAll,
                    cidAll);
                去重
                Set<MessageQueue> allocateResultSet = new HashSet<MessageQueue>();
                if (allocateResult != null) {
                    allocateResultSet.addAll(allocateResult);
                }
                判断自己的消费是否发生了变化 底层比较重要的是在必要的时候创建PullRequest 而pullRequest被被PuMessage
                boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
                if (changed) {
                    ...... 删除日志代码
                    消费者进程注册broker
                    this.messageQueueChanged(topic, mqSet, allocateResultSet);
                }
            }
            break;
        }
    }
}

findConsumerIdList

  • 可能有多个broker 随机获取一个 消费者进程会向所有broker注册自己
  • 通过远程调用获取broker上注册的消费者进程集合

public List findConsumerIdList(final String topic, final String group) {
可能有多个broker 随机获取一个 消费者进程会向所有broker注册自己
String brokerAddr = this.findBrokerAddrByTopic(topic);
if (null == brokerAddr) {
this.updateTopicRouteInfoFromNameServer(topic);
brokerAddr = this.findBrokerAddrByTopic(topic);
}

if (null != brokerAddr) {
    try {
        通过远程调用获取broker上注册的消费者进程集合
        return this.mQClientAPIImpl.getConsumerIdListByGroup(brokerAddr, group, 3000);
    } catch (Exception e) {
        log.warn("getConsumerIdListByGroup exception, " + brokerAddr + " " + group, e);
    }
}
return null;

}

allocateMessageQueueStrategy原理

  • AllocateMessageQueueAveragely如下
消费者进程集合消息队列集合
cid1,cid2,cid3mq1,mq2,mq3,mq4,mq5,mq6,mq7,mq8

cid1消费mq1,mq2,mq3

cid2消费mq4,mq5,mq6
cid3消费mq7,mq8

  • AllocateMessageQueueAveragelyByCircle如下
消费者进程集合消息队列集合
cid1,cid2,cid3mq1,mq2,mq3,mq4,mq5,mq6,mq7,mq8

cid1消费mq1,mq4,mq7

cid2消费mq2,mq5,mq8
cid3消费mq3,mq6

updateProcessQueueTableInRebalance变更核心数据

  • 修改processQueueTable为最新的负载结果
  • processQueue存储消息拉取结果用于消息消费
  • 构建新的PullRequest用于消息拉取

private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet,
    final boolean isOrder) {
    boolean changed = false;
    Iterator<Entry<MessageQueue, ProcessQueue>> it = this.processQueueTable.entrySet().iterator();
    ...... 删除代码:重新负载后mqSet不存在processQueueTable依旧存在

    List<PullRequest> pullRequestList = new ArrayList<PullRequest>();
    for (MessageQueue mq : mqSet) {
        if (!this.processQueueTable.containsKey(mq)) {
            if (isOrder && !this.lock(mq)) {
                continue;
            }
            this.removeDirtyOffset(mq);
            ProcessQueue pq = new ProcessQueue();
            long nextOffset = this.computePullFromWhere(mq);
            if (nextOffset >= 0) {
            	构建新的MessageQueueProcessQueue映射
                ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
                if (pre != null) {
                } else {
                    如果MessageQueue之前未曾消费过 一个PullRequest======一个MessageQueue   ========一个ProcessQueue
                    log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
                    PullRequest pullRequest = new PullRequest();
                    pullRequest.setConsumerGroup(consumerGroup);
                    pullRequest.setNextOffset(nextOffset);
                    pullRequest.setMessageQueue(mq);
                    pullRequest.setProcessQueue(pq);
                    pullRequestList.add(pullRequest);
                    changed = true;
                }
            } else {
            }
        }
    }
    拉取broker消息 交给PullMessageService执行拉取任务
    this.dispatchPullRequest(pullRequestList);
    return changed;
}

总结

  • 平衡线程每20秒进行一次
  • 构建消费者会触发立即再平衡
  • 再平衡使用allocateMessageQueueStrategy处理
  • 根据处理结果构建pullrequest,用于消息拉取
  • 根据处理结果构建修改processQueueTable用于消息拉取结果存储
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值