重新认识RocketMQ(5) - Consumer消费

·  阅读 452
重新认识RocketMQ(5) - Consumer消费

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

之前分析过RocketMQ的Broker拉取消费如何实现,再来分析下Consumer消费的核心实现方式。

理解RocketMQ的概念与原理,需要通过看源代码和画图的方式梳理流程。

通过查看源码梳理出的Consumer消费整体流程为:拉取消息->执行消费逻辑->移除过期队列->获取锁->获取消息->消费消息->更新进度,其中还有定时任务刷新锁、未获取锁时执行延迟获取锁、重平衡逻辑等等。

以下分步骤分析源码与画图梳理流程,这篇进行移除过期队列->获取锁的分析

移除过期队列

更新队列负载均衡,去除获取不到锁的队列

流程图

整个逻辑的流程图如下: q_lock.png

代码

查询mq是否再processQueueTable中,在processQueueTable中的队列尝试获取锁,获取不到锁直接不进行下面的处理。

 private boolean updateProcessQueueTableInRebalance(final String topic, final Set<MessageQueue> mqSet, final boolean isOrder) {
    // ..... 此处省略部分代码 
     // 增加 不在processQueueTable && 存在于mqSet 里的消息队列。
     List<PullRequest> pullRequestList = new ArrayList<>(); // 拉消息请求数组
     for (MessageQueue mq : mqSet) {
         if (!this.processQueueTable.containsKey(mq)) {
             if (isOrder && !this.lock(mq)) { // 顺序消息锁定消息队列
                 log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);
                 continue;
             }
 
              // ..... 此处省略部分代码 
         }
     }
 
 // ..... 此处省略部分代码 
 }
复制代码

锁相关代码

RocketMQ 集群模式下,每个queue只有对应group下的一个Consumer获取,通过锁机制实现。

流程图

消费前会尝试获取锁,获取成功执行消费,消费结束处理消费结果、释放锁。获取失败会进入重试阶段,流程图如下: qqq.drawio (1).png

代码

以下几个跟锁操作相关的代码。

获取锁

请求Broker获得指定消息队列的分布式锁。 首先获取Broker地址,this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ根据Broker地址批量获取持有锁MQ。

 /**
  * 请求Broker获得指定消息队列的分布式锁
  *
  * @param mq 队列
  * @return 是否成功
  */
 public boolean lock(final MessageQueue mq) {
     FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true);
     if (findBrokerResult != null) {
         ...
             Set<MessageQueue> lockedMq =
                 this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000);
             for (MessageQueue mmqq : lockedMq) {
                 ProcessQueue processQueue = this.processQueueTable.get(mmqq);
                 if (processQueue != null) {
                     processQueue.setLocked(true);
                     processQueue.setLastLockTimestamp(System.currentTimeMillis());
                 }
             }
	  }
     return false;
 }
复制代码

消费消息前,获取锁

messageQueueLock.fetchLockObject(this.messageQueue)
复制代码
定时刷新锁

定时轮询,将锁过期。Consumer定时向Broker刷新锁过期时间

public void start() {
     if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())) {
         this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
             @Override
             public void run() {
                 ConsumeMessageOrderlyService.this.lockMQPeriodically();
             }
         }, 1000 * 1, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS);
     }
 }
复制代码
释放锁

移除队列时,释放锁

  public boolean removeUnnecessaryMessageQueue(MessageQueue mq, ProcessQueue pq) {
        // 同步进度
        this.defaultMQPushConsumerImpl.getOffsetStore().persist(mq);
		// 移除队列
        this.defaultMQPushConsumerImpl.getOffsetStore().removeOffset(mq);
        if (this.defaultMQPushConsumerImpl.isConsumeOrderly()
            && MessageModel.CLUSTERING.equals(this.defaultMQPushConsumerImpl.messageModel())) {
			...
                if (pq.getConsumeLock().tryLock(1000, TimeUnit.MILLISECONDS)) {
                    try {
                        return this.unlockDelay(mq, pq);
                    } finally {
                        pq.getConsumeLock().unlock();
                    }
                } else {
                    
                    pq.incTryUnlockTimes();
                }
              ...
            return false;
        }
        return true;
    }

复制代码
收藏成功!
已添加到「」, 点击更改