rocketmq源码分析 - 消息队列的平衡/负载均衡RebalanceService

概念

在rocketmq中,消费者必须属于一个消费者组。消费者组有两种消费模式:

  • 集群:就是一个消费者组内的消费者,每个消费一个topic的某个子集,所有的消费者的子集共同组成topic的所有消息
  • 广播:就是消费者组内的所有消费者都消费topic的所有消息

我们还知道,在rocketmq中,订阅了某个topic,就相当于订阅了这个topic的所有消息队列,MessageQueue,而MessageQueue是可能动态变化的,消费者组内的消费者数量也可能动态变化。所以,就需要一个组件来完成这种消费队列在消费者间的重新平衡。这个组件就是RebalanceService。

实现

RebalanceService实现了Runnable接口,看看其重写的run方法:

@Override
public void run() {
  while (!this.isStopped()) {
    //等待waitInterval时间
    this.waitForRunning(waitInterval);
    //主要的逻辑方法
    this.mqClientFactory.doRebalance();
  }
}

主要的逻辑方法在MQClientInstance.doRebalance()中实现

public void doRebalance() {
  for (Map.Entry<String, MQConsumerInner> entry : this.consumerTable.entrySet()) {
    MQConsumerInner impl = entry.getValue();
    if (impl != null) {
      impl.doRebalance();
    }
  }
}

继续进入,找到RebalanceImpl.doRebalance()方法

public void doRebalance(final boolean isOrder) {
  Map<String, SubscriptionData> subTable = this.getSubscriptionInner();
  if (subTable != null) {
    for (final Map.Entry<String, SubscriptionData> entry : subTable.entrySet()) {
      final String topic = entry.getKey();
      //主要逻辑方法
      this.rebalanceByTopic(topic, isOrder);
    }
  }
  this.truncateMessageQueueNotMyTopic();
}

继续到rebalanceByTopic()方法

private void rebalanceByTopic(final String topic, final boolean isOrder) {
  switch (messageModel) {
    case BROADCASTING: {
      Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
      if (mqSet != null) {
        boolean changed = this.updateProcessQueueTableInRebalance(topic, mqSet, isOrder);
        if (changed) {
          this.messageQueueChanged(topic, mqSet, mqSet);
        }
      }
      break;
    }
    case CLUSTERING: {
      //见下文 集群模式的处理
      break;
    }
    default:
      break;
  }
}

从代码来看,很清晰的根据消费模式的不同,集群模式和广播模式分别对应不同的处理

集群模式的处理

方法段为:

//获取MessageQueue集合、消费者集合
Set<MessageQueue> mqSet = this.topicSubscribeInfoTable.get(topic);
List<String> cidAll = this.mQClientFactory.findConsumerIdList(topic, consumerGroup);

if (mqSet != null && cidAll != null) {
  List<MessageQueue> mqAll = new ArrayList<MessageQueue>();
  mqAll.addAll(mqSet);
  Collections.sort(mqAll);
  Collections.sort(cidAll);
	//根据MessageQueue分配策略获取MessageQueue
  AllocateMessageQueueStrategy strategy = this.allocateMessageQueueStrategy;
  List<MessageQueue> allocateResult = strategy.allocate(this.consumerGroup,this.mQClientFactory.getClientId(),mqAll,cidAll);
  Set<MessageQueue> allocateResultSet = new HashSet<MessageQueue>();
  if (allocateResult != null) {
    allocateResultSet.addAll(allocateResult);
  }
	//检测MessageQueue相较于之前是否发生了变化
  boolean changed = this.updateProcessQueueTableInRebalance(topic, allocateResultSet, isOrder);
  if (changed) {
    this.messageQueueChanged(topic, mqSet, allocateResultSet);
  }
}

根据MessageQueue分配策略获取MessageQueue

AllocateMessageQueueStrategy定义了MessageQueue的分配策略。接口定义如下:

public interface AllocateMessageQueueStrategy {
	//选择消息队列
  List<MessageQueue> allocate(final String consumerGroup,final String currentCID,final List<MessageQueue> mqAll,
                              final List<String> cidAll);
  //策略名称
  String getName();
}

在rocketmq中,有如下实现:

  • AllocateMachineRoomNearby:根据机房位置
  • AllocateMessageQueueAveragely:平均分
  • AllocateMessageQueueAveragelyByCircle
  • AllocateMessageQueueByConfig
  • AllocateMessageQueueByMachineRoom
  • AllocateMessageQueueConsistentHash:基于一致性hash算法

检测MessageQueue相较于之前是否发生了变化

大致逻辑为:

  • 判断有没有删除老的MessageQueue,如果删除了,也从processQueueTable中删除
  • 判断本次的MessageQueue,是不是有原来不存在的,如果有新增的MessageQueue,就构建一个PullRequest请求
private boolean updateProcessQueueTableInRebalance(String topic,Set<MessageQueue> mqSet,boolean isOrder) {
  boolean changed = false;
  Iterator<Entry<MessageQueue, ProcessQueue>> it = this.processQueueTable.entrySet().iterator();
  //判断有没有删除老的MessageQueue
  while (it.hasNext()) {
    Entry<MessageQueue, ProcessQueue> next = it.next();
    MessageQueue mq = next.getKey();
    ProcessQueue pq = next.getValue();

    if (mq.getTopic().equals(topic)) {
      if (!mqSet.contains(mq)) {
        pq.setDropped(true);
        if (this.removeUnnecessaryMessageQueue(mq, pq)) {
          it.remove();
          changed = true;
        }
      } else if (pq.isPullExpired()) {
        switch (this.consumeType()) {
          case CONSUME_ACTIVELY:
            break;
          case CONSUME_PASSIVELY:
            pq.setDropped(true);
            if (this.removeUnnecessaryMessageQueue(mq, pq)) {
              it.remove();
              changed = true;
            }
            break;
          default:
            break;
        }
      }
    }
  }
  //判断有没有新增的MessageQueue
  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) {
        ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
        if (pre == null) {
          //构建PullRequest请求
          PullRequest pullRequest = new PullRequest();
          pullRequest.setXXX
            changed = true;
        }
      }
    }
    this.dispatchPullRequest(pullRequestList);
    return changed;
  }
}
计算要从哪里开始消费
public long computePullFromWhere(MessageQueue mq) {
  long result = -1;
  final ConsumeFromWhere consumeFromWhere = this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeFromWhere();
  final OffsetStore offsetStore = this.defaultMQPushConsumerImpl.getOffsetStore();
  switch (consumeFromWhere) {
      //从MessageQueue的最新偏移量开始消费
    case CONSUME_FROM_LAST_OFFSET: 
      long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);
      if (lastOffset >= 0) {
        result = lastOffset;
      }
      // First start,no offset
      else if (-1 == lastOffset) {
        if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
          result = 0L;
        } else {
          result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq);
        }
      } else {
        result = -1;
      }
      break;
    case CONSUME_FROM_FIRST_OFFSET: 
      //从MessageQueue的最小偏移量开始消费
      long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);
      if (lastOffset >= 0) {
        result = lastOffset;
      } else if (-1 == lastOffset) {
        result = 0L;
      } else {
        result = -1;
      }
      break;
    case CONSUME_FROM_TIMESTAMP: 
      //从消费者启动的时间戳对应的消费进度开始消费
      long lastOffset = offsetStore.readOffset(mq, ReadOffsetType.READ_FROM_STORE);
      if (lastOffset >= 0) {
        result = lastOffset;
      } else if (-1 == lastOffset) {
        if (mq.getTopic().startsWith(MixAll.RETRY_GROUP_TOPIC_PREFIX)) {
          result = this.mQClientFactory.getMQAdminImpl().maxOffset(mq);
        } else {
          long timestamp = UtilAll.parseDate(this.defaultMQPushConsumerImpl.getDefaultMQPushConsumer().getConsumeTimestamp(),
                                             UtilAll.YYYYMMDDHHMMSS).getTime();
          result = this.mQClientFactory.getMQAdminImpl().searchOffset(mq, timestamp);
        }
      } else {
        result = -1;
      }
      break;
  }
  return result;
}

ConsumeFromWhere是一个枚举,目前还有三个是没有Deprecated的值:

  • CONSUME_FROM_LAST_OFFSET
  • CONSUME_FROM_FIRST_OFFSET
  • CONSUME_FROM_TIMESTAMP

不同的ConsumeFromWhere的第一步都是依赖OffsetStore先从本地读取offset,如果offset>-1,则表示是一个有效的值,则会已这个offset为准;如果offset=-1,是初始值,则需要根据不同的ConsumeFromWhere去broker读取对应MessageQueue的offset

结语

(水平有限,最近在看rocketmq源码,记录学习过程,也希望对各位有点微小的帮助。如有错误,请指正~)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值