分布式消息队列 RocketMQ 源码分析 —— Message 顺序发送与消费

RocketMQ顺序消息机制源码分析~ RocketMQ为了保证消息的顺序性,分别从Producer和Consumer都有着相应的设计~Producer方面,为保证顺序消息,可自定义MessageQueueSelector来选择队列。例: orderId % msgQueueSize,从而可保证同一个orderId的相关消息,会被发送到同一个队列里。Consumer方面,在整体设计上用了三把锁,来保证消息的顺序消费。定时任务周期性对消费者所属队列加锁消费时对消息队列加锁消息真正被消费前,对processQueue加消费锁。 阅读详情

摘要: 原创出处 http://www.iocoder.cn/RocketMQ/message-send-and-consume-orderly/ 「芋道源码」欢迎转载,保留摘要,谢谢!

本文主要基于 RocketMQ 4.0.x 正式版


������关注微信公众号:【芋道源码】有福利:
1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
4. 新的源码解析文章实时收到通知。每周更新一篇左右。

> 5. 认真的源码交流微信群。

1. 概述

建议前置阅读内容:

当然对 Message 发送与消费已经有一定了解的同学,可以选择跳过。


RocketMQ 提供了两种顺序级别:

  • 普通顺序消息 :Producer 将相关联的消息发送到相同的消息队列。
  • 完全严格顺序 :在 普通顺序消息 的基础上,Consumer 严格顺序消费。

绝大部分场景下只需要用到普通顺序消息。
例如说:给用户发送短信消息 + 发送推送消息,将两条消息发送到不同的消息队列,若其中一条消息队列消费较慢造成堵塞,用户可能会收到两条消息会存在一定的时间差,带来的体验会相对较差。当然类似这种场景,即使有一定的时间差,不会产生系统逻辑上BUG。另外,普通顺序消息性能能更加好。
那么什么时候使用使用完全严格顺序?如下是来自官方文档的说明:

目前已知的应用只有数据库 binlog 同步强依赖严格顺序消息,其他应用绝大部分都可以容忍短暂乱序,推荐使用普通的顺序消息


��上代码!!!

2. Producer 顺序发送

官方发送顺序消息的例子:

  1: package org.apache.rocketmq.example.ordermessage;
  2: 
  3: import java.io.UnsupportedEncodingException;
  4: import java.util.List;
  5: import org.apache.rocketmq.client.exception.MQBrokerException;
  6: import org.apache.rocketmq.client.exception.MQClientException;
  7: import org.apache.rocketmq.client.producer.DefaultMQProducer;
  8: import org.apache.rocketmq.client.producer.MQProducer;
  9: import org.apache.rocketmq.client.producer.MessageQueueSelector;
 10: import org.apache.rocketmq.client.producer.SendResult;
 11: import org.apache.rocketmq.common.message.Message;
 12: import org.apache.rocketmq.common.message.MessageQueue;
 13: import org.apache.rocketmq.remoting.common.RemotingHelper;
 14: import org.apache.rocketmq.remoting.exception.RemotingException;
 15: 
 16: public class Producer {
 17:     public static void main(String[] args) throws UnsupportedEncodingException {
 18:         try {
 19:             MQProducer producer = new DefaultMQProducer("please_rename_unique_group_name");
 20:             producer.start();
 21: 
 22:             String[] tags = new String[] {"TagA", "TagB", "TagC", "TagD", "TagE"};
 23:             for (int i = 0; i < 100; i++) {
 24:                 int orderId = i % 10;
 25:                 Message msg =
 26:                     new Message("TopicTestjjj", tags[i % tags.length], "KEY" + i,
 27:                         ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
 28:                 SendResult sendResult = producer.send(msg, new MessageQueueSelector() {
 29:                     @Override
 30:                     public MessageQueue select(List mqs, Message msg, Object arg) {
 31:                         Integer id = (Integer) arg;
 32:                         int index = id % mqs.size();
 33:                         return mqs.get(index);
 34:                     }
 35:                 }, orderId);
 36: 
 37:                 System.out.printf("%s%n", sendResult);
 38:             }
 39: 
 40:             producer.shutdown();
 41:         } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) {
 42:             e.printStackTrace();
 43:         }
 44:     }
 45: }
  • 第 28 至 35 行 :实现了根据 id % mqs.size() 来进行消息队列的选择。当前例子,我们传递 orderId 作为参数,那么相同的 orderId 能够进入相同的消息队列。

MessageQueueSelector 接口的源码:

  1: public interface MessageQueueSelector {
  2: 
  3:     /**
  4:      * 选择消息队列
  5:      *
  6:      * @param mqs 消息队列
  7:      * @param msg 消息
  8:      * @param arg 参数
  9:      * @return 消息队列
 10:      */
 11:     MessageQueue select(final List mqs, final Message msg, final Object arg);
 12: }

Producer 选择队列发送消息方法的源码:

 16: private SendResult sendSelectImpl(//
 17:     Message msg, //
 18:     MessageQueueSelector selector, //
 19:     Object arg, //
 20:     final CommunicationMode communicationMode, //
 21:     final SendCallback sendCallback, final long timeout//
 22: ) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
 23:     this.makeSureStateOK();
 24:     Validators.checkMessage(msg, this.defaultMQProducer);
 25: 
 26:     TopicPublishInfo topicPublishInfo = this.tryToFindTopicPublishInfo(msg.getTopic());
 27:     if (topicPublishInfo != null && topicPublishInfo.ok()) {
 28:         MessageQueue mq = null;
 29:         try {
 30:             mq = selector.select(topicPublishInfo.getMessageQueueList(), msg, arg);
 31:         } catch (Throwable e) {
 32:             throw new MQClientException("select message queue throwed exception.", e);
 33:         }
 34: 
 35:         if (mq != null) {
 36:             return this.sendKernelImpl(msg, mq, communicationMode, sendCallback, null, timeout);
 37:         } else {
 38:             throw new MQClientException("select message queue return null.", null);
 39:         }
 40:     }
 41: 
 42:     throw new MQClientException("No route info for this topic, " + msg.getTopic(), null);
 43: }
  • 第 30 行 :选择消息队列。
  • 第 36 行 :发送消息。

3. Consumer 严格顺序消费

Consumer 在严格顺序消费时,通过 三 把锁保证严格顺序消费。

  • Broker 消息队列锁(分布式锁) :
    • 集群模式下,Consumer 从 Broker 获得该锁后,才能进行消息拉取、消费。
    • 广播模式下,Consumer 无需该锁。
  • Consumer 消息队列锁(本地锁) :Consumer 获得该锁才能操作消息队列。
  • Consumer 消息处理队列消费锁(本地锁) :Consumer 获得该锁才能消费消息队列。

可能同学有疑问,为什么有 Consumer 消息队列锁还需要有 Consumer 消息队列消费锁呢?��让我们带着疑问继续往下看。


3.1 获得(锁定)消息队列

集群模式下,Consumer 更新属于自己的消息队列时,会向 Broker 锁定该消息队列(广播模式下不需要)。如果锁定失败,则更新失败,即该消息队列不属于自己,不能进行消费。核心代码如下:

  1: // ⬇️⬇️⬇️【RebalanceImpl.java】
  2: private boolean updateProcessQueueTableInRebalance(final String topic, final Set mqSet, final boolean isOrder) {
  3: // ..... 此处省略部分代码 
  4:     // 增加 不在processQueueTable && 存在于mqSet 里的消息队列。
  5:     List pullRequestList = new ArrayList<>(); // 拉消息请求数组
  6:     for (MessageQueue mq : mqSet) {
  7:         if (!this.processQueueTable.containsKey(mq)) {
  8:             if (isOrder && !this.lock(mq)) { // 顺序消息锁定消息队列
  9:                 log.warn("doRebalance, {}, add a new mq failed, {}, because lock failed", consumerGroup, mq);
 10:                 continue;
 11:             }
 12: 
 13:             this.removeDirtyOffset(mq);
 14:             ProcessQueue pq = new ProcessQueue();
 15:             long nextOffset = this.computePullFromWhere(mq);
 16:             if (nextOffset >= 0) {
 17:                 ProcessQueue pre = this.processQueueTable.putIfAbsent(mq, pq);
 18:                 if (pre != null) {
 19:                     log.info("doRebalance, {}, mq already exists, {}", consumerGroup, mq);
 20:                 } else {
 21:                     log.info("doRebalance, {}, add a new mq, {}", consumerGroup, mq);
 22:                     PullRequest pullRequest = new PullRequest();
 23:                     pullRequest.setConsumerGroup(consumerGroup);
 24:                     pullRequest.setNextOffset(nextOffset);
 25:                     pullRequest.setMessageQueue(mq);
 26:                     pullRequest.setProcessQueue(pq);
 27:                     pullRequestList.add(pullRequest);
 28:                     changed = true;
 29:                 }
 30:             } else {
 31:                 log.warn("doRebalance, {}, add new mq failed, {}", consumerGroup, mq);
 32:             }
 33:         }
 34:     }
 35: 
 36: // ..... 此处省略部分代码 
 37: }
 38: 
 39: // ⬇️⬇️⬇️【RebalanceImpl.java】
 40: /**
 41:  * 请求Broker获得指定消息队列的分布式锁
 42:  *
 43:  * @param mq 队列
 44:  * @return 是否成功
 45:  */
 46: public boolean lock(final MessageQueue mq) {
 47:     FindBrokerResult findBrokerResult = this.mQClientFactory.findBrokerAddressInSubscribe(mq.getBrokerName(), MixAll.MASTER_ID, true);
 48:     if (findBrokerResult != null) {
 49:         LockBatchRequestBody requestBody = new LockBatchRequestBody();
 50:         requestBody.setConsumerGroup(this.consumerGroup);
 51:         requestBody.setClientId(this.mQClientFactory.getClientId());
 52:         requestBody.getMqSet().add(mq);
 53: 
 54:         try {
 55:             // 请求Broker获得指定消息队列的分布式锁
 56:             Set lockedMq =
 57:                 this.mQClientFactory.getMQClientAPIImpl().lockBatchMQ(findBrokerResult.getBrokerAddr(), requestBody, 1000);
 58: 
 59:             // 设置消息处理队列锁定成功。锁定消息队列成功,可能本地没有消息处理队列,设置锁定成功会在lockAll()方法。
 60:             for (MessageQueue mmqq : lockedMq) {
 61:                 ProcessQueue processQueue = this.processQueueTable.get(mmqq);
 62:                 if (processQueue != null) {
 63:                     processQueue.setLocked(true);
 64:                     processQueue.setLastLockTimestamp(System.currentTimeMillis());
 65:                 }
 66:             }
 67: 
 68:             boolean lockOK = lockedMq.contains(mq);
 69:             log.info("the message queue lock {}, {} {}",
 70:                 lockOK ? "OK" : "Failed",
 71:                 this.consumerGroup,
 72:                 mq);
 73:             return lockOK;
 74:         } catch (Exception e) {
 75:             log.error("lockBatchMQ exception, " + mq, e);
 76:         }
 77:     }
 78: 
 79:     return false;
 80: }
  • ⬆️⬆️⬆️
  • 第 8 至 11 行 :顺序消费时,锁定消息队列。如果锁定失败,新增消息处理队列失败。

Broker 消息队列锁会过期,默认配置 30s。因此,Consumer 需要不断向 Broker 刷新该锁过期时间,默认配置 20s 刷新一次。核心代码如下:

  1: // ⬇️⬇️⬇️【ConsumeMessageOrderlyService.java】
  2: public void start() {
  3:     if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())) {
  4:         this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
  5:             @Override
  6:             public void run() {
  7:                 ConsumeMessageOrderlyService.this.lockMQPeriodically();
  8:             }
  9:         }, 1000 * 1, ProcessQueue.REBALANCE_LOCK_INTERVAL, TimeUnit.MILLISECONDS);
 10:     }
 11: }

3.2 移除消息队列

集群模式下,Consumer 移除自己的消息队列时,会向 Broker 解锁该消息队列(广播模式下不需要)。核心代码如下:

  1: // ⬇️⬇️⬇️【RebalancePushImpl.java】
  2: /**
  3:  * 移除不需要的队列相关的信息
  4:  * 1. 持久化消费进度,并移除之
  5:  * 2. 顺序消费&集群模式,解锁对该队列的锁定
  6:  *
  7:  * @param mq 消息队列
  8:  * @param pq 消息处理队列
  9:  * @return 是否移除成功
 10:  */
 11: @Override
 12: public boolean removeUnnecessaryMessageQueue(MessageQueue mq, ProcessQueue pq) {
 13:     // 同步队列的消费进度,并移除之。
 14:     this.defaultMQPushConsumerImpl.getOffsetStore().persist(mq);
 15:     this.defaultMQPushConsumerImpl.getOffsetStore().removeOffset(mq);
 16:     // 集群模式下,顺序消费移除时,解锁对队列的锁定
 17:     if (this.defaultMQPushConsumerImpl.isConsumeOrderly()
 18:         && MessageModel.CLUSTERING.equals(this.defaultMQPushConsumerImpl.messageModel())) {
 19:         try {
 20:             if (pq.getLockConsume().tryLock(1000, TimeUnit.MILLISECONDS)) {
 21:                 try {
 22:                     return this.unlockDelay(mq, pq);
 23:                 } finally {
 24:                     pq.getLockConsume().unlock();
 25:                 }
 26:             } else {
 27:                 log.warn("[WRONG]mq is consuming, so can not unlock it, {}. maybe hanged for a while, {}", //
 28:                     mq, //
 29:                     pq.getTryUnlockTimes());
 30: 
 31:                 pq.incTryUnlockTimes();
 32:             }
 33:         } catch (Exception e) {
 34:             log.error("removeUnnecessaryMessageQueue Exception", e);
 35:         }
 36: 
 37:         return false;
 38:     }
 39:     return true;
 40: }
 41: 
 42: // ⬇️⬇️⬇️【RebalancePushImpl.java】
 43: /**
 44:  * 延迟解锁 Broker 消息队列锁
 45:  * 当消息处理队列不存在消息,则直接解锁
 46:  *
 47:  * @param mq 消息队列
 48:  * @param pq 消息处理队列
 49:  * @return 是否解锁成功
 50:  */
 51: private boolean unlockDelay(final MessageQueue mq, final ProcessQueue pq) {
 52:     if (pq.hasTempMessage()) { // TODO 疑问:为什么要延迟移除
 53:         log.info("[{}]unlockDelay, begin {} ", mq.hashCode(), mq);
 54:         this.defaultMQPushConsumerImpl.getmQClientFactory().getScheduledExecutorService().schedule(new Runnable() {
 55:             @Override
 56:             public void run() {
 57:                 log.info("[{}]unlockDelay, execute at once {}", mq.hashCode(), mq);
 58:                 RebalancePushImpl.this.unlock(mq, true);
 59:             }
 60:         }, UNLOCK_DELAY_TIME_MILLS, TimeUnit.MILLISECONDS);
 61:     } else {
 62:         this.unlock(mq, true);
 63:     }
 64:     return true;
 65: }
  • ⬆️⬆️⬆️
  • 第 20 至 32 行 :获取消息队列消费锁,避免和消息队列消费冲突。如果获取锁失败,则移除消息队列失败,等待下次重新分配消费队列时,再进行移除。如果未获得锁而进行移除,则可能出现另外的 Consumer 和当前 Consumer 同时消费该消息队列,导致消息无法严格顺序消费。
  • 第 51 至 64 行 :解锁 Broker 消息队列锁。如果消息处理队列存在剩余消息,则延迟解锁 Broker 消息队列锁。❓为什么消息处理队列存在剩余消息不能直接解锁呢?��我也不知道,百思不得其解。如果有知道的同学麻烦教育下俺。

3.3 消费消息队列

��本节会类比并发消费消费队列,建议对照 PushConsumer并发消费消息 一起理解。

3.1.1 消费消息

顺序消费活动图-消费消息

  1: // ⬇️⬇️⬇️【ConsumeMessageOrderlyService.java】
  2: class ConsumeRequest implements Runnable {
  3: 
  4:     /**
  5:      * 消息处理队列
  6:      */
  7:     private final ProcessQueue processQueue;
  8:     /**
  9:      * 消息队列
 10:      */
 11:     private final MessageQueue messageQueue;
 12: 
 13:     @Override
 14:     public void run() {
 15:         if (this.processQueue.isDropped()) {
 16:             log.warn("run, the message queue not be able to consume, because it's dropped. {}", this.messageQueue);
 17:             return;
 18:         }
 19: 
 20:         // 获得 Consumer 消息队列锁
 21:         final Object objLock = messageQueueLock.fetchLockObject(this.messageQueue);
 22:         synchronized (objLock) {
 23:             // (广播模式) 或者 (集群模式 && Broker消息队列锁有效)
 24:             if (MessageModel.BROADCASTING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())
 25:                 || (this.processQueue.isLocked() && !this.processQueue.isLockExpired())) {
 26:                 final long beginTime = System.currentTimeMillis();
 27:                 // 循环
 28:                 for (boolean continueConsume = true; continueConsume; ) {
 29:                     if (this.processQueue.isDropped()) {
 30:                         log.warn("the message queue not be able to consume, because it's dropped. {}", this.messageQueue);
 31:                         break;
 32:                     }
 33: 
 34:                     // 消息队列分布式锁未锁定,提交延迟获得锁并消费请求
 35:                     if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())
 36:                         && !this.processQueue.isLocked()) {
 37:                         log.warn("the message queue not locked, so consume later, {}", this.messageQueue);
 38:                         ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 10);
 39:                         break;
 40:                     }
 41:                     // 消息队列分布式锁已经过期,提交延迟获得锁并消费请求
 42:                     if (MessageModel.CLUSTERING.equals(ConsumeMessageOrderlyService.this.defaultMQPushConsumerImpl.messageModel())
 43:                         && this.processQueue.isLockExpired()) {
 44:                         log.warn("the message queue lock expired, so consume later, {}", this.messageQueue);
 45:                         ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 10);
 46:                         break;
 47:                     }
 48: 
 49:                     // 当前周期消费时间超过连续时长,默认:60s,提交延迟消费请求。默认情况下,每消费1分钟休息10ms。
 50:                     long interval = System.currentTimeMillis() - beginTime;
 51:                     if (interval > MAX_TIME_CONSUME_CONTINUOUSLY) {
 52:                         ConsumeMessageOrderlyService.this.submitConsumeRequestLater(processQueue, messageQueue, 10);
 53:                         break;
 54:                     }
 55: 
 56:                     // 获取消费消息。此处和并发消息请求不同,并发消息请求已经带了消费哪些消息。
 57:                     final int consumeBatchSize = ConsumeMessageOrderlyService.this.defaultMQPushConsumer.getConsumeMessageBatchMaxSize();
 58:                     List msgs = this.processQueue.takeMessags(consumeBatchSize);
 59:                     if (!msgs.isEmpty()) {
 60:                         final ConsumeOrderlyContext context = new ConsumeOrderlyContext(this.messageQueue);
 61: 
 62:                         ConsumeOrderlyStatus status = null;
 63: 
 64:                         // ....省略代码:Hook:before
 65: 
 66:                         // 执行消费
 67:                         long beginTimestamp = System.currentTimeMillis();
 68:                         ConsumeReturnType returnType = ConsumeReturnType.SUCCESS;
 69:                         boolean hasException = false;
 70:                         try {
 71:                             this.processQueue.getLockConsume().lock(); // 锁定队列消费锁
 72: 
 73:                             if (this.processQueue.isDropped()) {
 74:                                 log.warn("consumeMessage, the message queue not be able to consume, because it's dropped. {}",
 75:                                     this.messageQueue);
 76:                                 break;
 77:                             }
 78: 
 79:                             status = messageListener.consumeMessage(Collections.unmodifiableList(msgs), context);
 80:                         } catch (Throwable e) {
 81:                             log.warn("consumeMessage exception: {} Group: {} Msgs: {} MQ: {}", //
 82:                                 RemotingHelper.exceptionSimpleDesc(e), //
 83:                                 ConsumeMessageOrderlyService.this.consumerGroup, //
 84:                                 msgs, //
 85:                                 messageQueue);
 86:                             hasException = true;
 87:                         } finally {
 88:                             this.processQueue.getLockConsume().unlock(); // 锁定队列消费锁
 89:                         }
 90: 
 91:                         // ....省略代码:解析消费结果状态
 92: 
 93:                         // ....省略代码:Hook:after
 94: 
 95:                         ConsumeMessageOrderlyService.this.getConsumerStatsManager()
 96:                             .incConsumeRT(ConsumeMessageOrderlyService.this.consumerGroup, messageQueue.getTopic(), consumeRT);
 97: 
 98:                         // 处理消费结果
 99:                         continueConsume = ConsumeMessageOrderlyService.this.processConsumeResult(msgs, status, context, this);
100:                     } else {
101:                         continueConsume = false;
102:                     }
103:                 }
104:             } else {
105:                 if (this.processQueue.isDropped()) {
106:                     log.warn("the message queue not be able to consume, because it's dropped. {}", this.messageQueue);
107:                     return;
108:                 }
109: 
110:                 ConsumeMessageOrderlyService.this.tryLockLaterAndReconsume(this.messageQueue, this.processQueue, 100);
111:             }
112:         }
113:     }
114: 
115: }
  • ⬆️⬆️⬆️
  • 第 20 行 :获得 Consumer 消息队列锁。
  • 第 58 行 :从消息处理队列顺序获得消息。和并发消费获得消息不同。并发消费请求在请求创建时,已经设置好消费哪些消息。
  • 第 71 行 :获得 Consumer 消息处理队列消费锁。相比【Consumer消息队列锁】,其粒度较小。这就是上文提到的❓为什么有Consumer消息队列锁还需要有 Consumer 消息队列消费锁呢的原因。
  • 第 79 行 :执行消费。
  • 第 99 行 :处理消费结果。

3.1.2 处理消费结果

顺序消费消息结果 (ConsumeOrderlyStatus) 有四种情况:

  • SUCCESS :消费成功但不提交。
  • ROLLBACK :消费失败,消费回滚。
  • COMMIT :消费成功提交并且提交。
  • SUSPEND_CURRENT_QUEUE_A_MOMENT :消费失败,挂起消费队列一会会,稍后继续消费。

考虑到 ROLLBACK 、COMMIT 暂时只使用在 MySQL binlog 场景,官方将这两状态标记为 @Deprecated。当然,相应的实现逻辑依然保留。

在并发消费场景时,如果消费失败,Consumer 会将消费失败消息发回到 Broker 重试队列,跳过当前消息,等待下次拉取该消息再进行消费。

但是在完全严格顺序消费消费时,这样做显然不行。也因此,消费失败的消息,会挂起队列一会会,稍后继续消费。

不过消费失败的消息一直失败,也不可能一直消费。当超过消费重试上限时,Consumer 会将消费失败超过上限的消息发回到 Broker 死信队列。

让我们来看看代码:

  1: // ⬇️⬇️⬇️【ConsumeMessageOrderlyService.java】
  2: /**
  3:  * 处理消费结果,并返回是否继续消费
  4:  *
  5:  * @param msgs 消息
  6:  * @param status 消费结果状态
  7:  * @param context 消费Context
  8:  * @param consumeRequest 消费请求
  9:  * @return 是否继续消费
 10:  */
 11: public boolean processConsumeResult(//
 12:     final List msgs, //
 13:     final ConsumeOrderlyStatus status, //
 14:     final ConsumeOrderlyContext context, //
 15:     final ConsumeRequest consumeRequest//
 16: ) {
 17:     boolean continueConsume = true;
 18:     long commitOffset = -1L;
 19:     if (context.isAutoCommit()) {
 20:         switch (status) {
 21:             case COMMIT:
 22:             case ROLLBACK:
 23:                 log.warn("the message queue consume result is illegal, we think you want to ack these message {}", consumeRequest.getMessageQueue());
 24:             case SUCCESS:
 25:                 // 提交消息已消费成功到消息处理队列
 26:                 commitOffset = consumeRequest.getProcessQueue().commit();
 27:                 // 统计
 28:                 this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size());
 29:                 break;
 30:             case SUSPEND_CURRENT_QUEUE_A_MOMENT:
 31:                 // 统计
 32:                 this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size());
 33:                 if (checkReconsumeTimes(msgs)) { // 计算是否暂时挂起(暂停)消费N毫秒,默认:10ms
 34:                     // 设置消息重新消费
 35:                     consumeRequest.getProcessQueue().makeMessageToCosumeAgain(msgs);
 36:                     // 提交延迟消费请求
 37:                     this.submitConsumeRequestLater(//
 38:                         consumeRequest.getProcessQueue(), //
 39:                         consumeRequest.getMessageQueue(), //
 40:                         context.getSuspendCurrentQueueTimeMillis());
 41:                     continueConsume = false;
 42:                 } else {
 43:                     commitOffset = consumeRequest.getProcessQueue().commit();
 44:                 }
 45:                 break;
 46:             default:
 47:                 break;
 48:         }
 49:     } else {
 50:         switch (status) {
 51:             case SUCCESS:
 52:                 this.getConsumerStatsManager().incConsumeOKTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size());
 53:                 break;
 54:             case COMMIT:
 55:                 // 提交消息已消费成功到消息处理队列
 56:                 commitOffset = consumeRequest.getProcessQueue().commit();
 57:                 break;
 58:             case ROLLBACK:
 59:                 // 设置消息重新消费
 60:                 consumeRequest.getProcessQueue().rollback();
 61:                 this.submitConsumeRequestLater(//
 62:                     consumeRequest.getProcessQueue(), //
 63:                     consumeRequest.getMessageQueue(), //
 64:                     context.getSuspendCurrentQueueTimeMillis());
 65:                 continueConsume = false;
 66:                 break;
 67:             case SUSPEND_CURRENT_QUEUE_A_MOMENT: // 计算是否暂时挂起(暂停)消费N毫秒,默认:10ms
 68:                 this.getConsumerStatsManager().incConsumeFailedTPS(consumerGroup, consumeRequest.getMessageQueue().getTopic(), msgs.size());
 69:                 if (checkReconsumeTimes(msgs)) {
 70:                     // 设置消息重新消费
 71:                     consumeRequest.getProcessQueue().makeMessageToCosumeAgain(msgs);
 72:                     // 提交延迟消费请求
 73:                     this.submitConsumeRequestLater(//
 74:                         consumeRequest.getProcessQueue(), //
 75:                         consumeRequest.getMessageQueue(), //
 76:                         context.getSuspendCurrentQueueTimeMillis());
 77:                     continueConsume = false;
 78:                 }
 79:                 break;
 80:             default:
 81:                 break;
 82:         }
 83:     }
 84: 
 85:     // 消息处理队列未dropped,提交有效消费进度
 86:     if (commitOffset >= 0 && !consumeRequest.getProcessQueue().isDropped()) {
 87:         this.defaultMQPushConsumerImpl.getOffsetStore().updateOffset(consumeRequest.getMessageQueue(), commitOffset, false);
 88:     }
 89: 
 90:     return continueConsume;
 91: }
 92: 
 93: private int getMaxReconsumeTimes() {
 94:     // default reconsume times: Integer.MAX_VALUE
 95:     if (this.defaultMQPushConsumer.getMaxReconsumeTimes() == -1) {
 96:         return Integer.MAX_VALUE;
 97:     } else {
 98:         return this.defaultMQPushConsumer.getMaxReconsumeTimes();
 99:     }
100: }
101: 
102: /**
103:  * 计算是否要暂停消费
104:  * 不暂停条件:存在消息都超过最大消费次数并且都发回broker成功
105:  *
106:  * @param msgs 消息
107:  * @return 是否要暂停
108:  */
109: private boolean checkReconsumeTimes(List msgs) {
110:     boolean suspend = false;
111:     if (msgs != null && !msgs.isEmpty()) {
112:         for (MessageExt msg : msgs) {
113:             if (msg.getReconsumeTimes() >= getMaxReconsumeTimes()) {
114:                 MessageAccessor.setReconsumeTime(msg, String.valueOf(msg.getReconsumeTimes()));
115:                 if (!sendMessageBack(msg)) { // 发回失败,中断
116:                     suspend = true;
117:                     msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
118:                 }
119:             } else {
120:                 suspend = true;
121:                 msg.setReconsumeTimes(msg.getReconsumeTimes() + 1);
122:             }
123:         }
124:     }
125:     return suspend;
126: }
127: 
128: /**
129:  * 发回消息。
130:  * 消息发回broker后,对应的消息队列是死信队列。
131:  *
132:  * @param msg 消息
133:  * @return 是否发送成功
134:  */
135: public boolean sendMessageBack(final MessageExt msg) {
136:     try {
137:         // max reconsume times exceeded then send to dead letter queue.
138:         Message newMsg = new Message(MixAll.getRetryTopic(this.defaultMQPushConsumer.getConsumerGroup()), msg.getBody());
139:         String originMsgId = MessageAccessor.getOriginMessageId(msg);
140:         MessageAccessor.setOriginMessageId(newMsg, UtilAll.isBlank(originMsgId) ? msg.getMsgId() : originMsgId);
141:         newMsg.setFlag(msg.getFlag());
142:         MessageAccessor.setProperties(newMsg, msg.getProperties());
143:         MessageAccessor.putProperty(newMsg, MessageConst.PROPERTY_RETRY_TOPIC, msg.getTopic());
144:         MessageAccessor.setReconsumeTime(newMsg, String.valueOf(msg.getReconsumeTimes()));
145:         MessageAccessor.setMaxReconsumeTimes(newMsg, String.valueOf(getMaxReconsumeTimes()));
146:         newMsg.setDelayTimeLevel(3 + msg.getReconsumeTimes());
147: 
148:         this.defaultMQPushConsumer.getDefaultMQPushConsumerImpl().getmQClientFactory().getDefaultMQProducer().send(newMsg);
149:         return true;
150:     } catch (Exception e) {
151:         log.error("sendMessageBack exception, group: " + this.consumerGroup + " msg: " + msg.toString(), e);
152:     }
153: 
154:     return false;
155: }
  • ⬆️⬆️⬆️
  • 第 21 至 29 行 :消费成功。在自动提交进度( AutoCommit )的情况下,COMMIT、ROLLBACK、SUCCESS 逻辑已经统一。
  • 第 30 至 45 行 :消费失败。当消息重试次数超过上限(默认 :16次)时,将消息发送到 Broker 死信队列,跳过这些消息。此时,消息队列无需挂起,继续消费后面的消息。
  • 第 85 至 88 行 :提交消费进度。

3.13 消息处理队列核心方法

��涉及到的四个核心方法的源码:

  1: // ⬇️⬇️⬇️【ProcessQueue.java】
  2: /**
  3:  * 消息映射
  4:  * key:消息队列位置
  5:  */
  6: private final TreeMap msgTreeMap = new TreeMap<>();    /**
  7:  * 消息映射临时存储(消费中的消息)
  8:  */
  9: private final TreeMap msgTreeMapTemp = new TreeMap<>();
 10: 
 11: /**
 12:  * 回滚消费中的消息
 13:  * 逻辑类似于{@link #makeMessageToCosumeAgain(List)}
 14:  */
 15: public void rollback() {
 16:     try {
 17:         this.lockTreeMap.writeLock().lockInterruptibly();
 18:         try {
 19:             this.msgTreeMap.putAll(this.msgTreeMapTemp);
 20:             this.msgTreeMapTemp.clear();
 21:         } finally {
 22:             this.lockTreeMap.writeLock().unlock();
 23:         }
 24:     } catch (InterruptedException e) {
 25:         log.error("rollback exception", e);
 26:     }
 27: }
 28: 
 29: /**
 30:  * 提交消费中的消息已消费成功,返回消费进度
 31:  *
 32:  * @return 消费进度
 33:  */
 34: public long commit() {
 35:     try {
 36:         this.lockTreeMap.writeLock().lockInterruptibly();
 37:         try {
 38:             // 消费进度
 39:             Long offset = this.msgTreeMapTemp.lastKey();
 40: 
 41:             //
 42:             msgCount.addAndGet(this.msgTreeMapTemp.size() * (-1));
 43: 
 44:             //
 45:             this.msgTreeMapTemp.clear();
 46: 
 47:             // 返回消费进度
 48:             if (offset != null) {
 49:                 return offset + 1;
 50:             }
 51:         } finally {
 52:             this.lockTreeMap.writeLock().unlock();
 53:         }
 54:     } catch (InterruptedException e) {
 55:         log.error("commit exception", e);
 56:     }
 57: 
 58:     return -1;
 59: }
 60: 
 61: /**
 62:  * 指定消息重新消费
 63:  * 逻辑类似于{@link #rollback()}
 64:  *
 65:  * @param msgs 消息
 66:  */
 67: public void makeMessageToCosumeAgain(List msgs) {
 68:     try {
 69:         this.lockTreeMap.writeLock().lockInterruptibly();
 70:         try {
 71:             for (MessageExt msg : msgs) {
 72:                 this.msgTreeMapTemp.remove(msg.getQueueOffset());
 73:                 this.msgTreeMap.put(msg.getQueueOffset(), msg);
 74:             }
 75:         } finally {
 76:             this.lockTreeMap.writeLock().unlock();
 77:         }
 78:     } catch (InterruptedException e) {
 79:         log.error("makeMessageToCosumeAgain exception", e);
 80:     }
 81: }
 82: 
 83: /**
 84:  * 获得持有消息前N条
 85:  *
 86:  * @param batchSize 条数
 87:  * @return 消息
 88:  */
 89: public List takeMessags(final int batchSize) {
 90:     List result = new ArrayList<>(batchSize);
 91:     final long now = System.currentTimeMillis();
 92:     try {
 93:         this.lockTreeMap.writeLock().lockInterruptibly();
 94:         this.lastConsumeTimestamp = now;
 95:         try {
 96:             if (!this.msgTreeMap.isEmpty()) {
 97:                 for (int i = 0; i < batchSize; i++) {
 98:                     Map.Entry entry = this.msgTreeMap.pollFirstEntry();
 99:                     if (entry != null) {
100:                         result.add(entry.getValue());
101:                         msgTreeMapTemp.put(entry.getKey(), entry.getValue());
102:                     } else {
103:                         break;
104:                     }
105:                 }
106:             }
107: 
108:             if (result.isEmpty()) {
109:                 consuming = false;
110:             }
111:         } finally {
112:             this.lockTreeMap.writeLock().unlock();
113:         }
114:     } catch (InterruptedException e) {
115:         log.error("take Messages exception", e);
116:     }
117: 
118:     return result;
119: }

666. 彩蛋

知识星球


������关注微信公众号:【芋道源码】有福利:
1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
4. 新的源码解析文章实时收到通知。每周更新一篇左右。
5. 认真的源码交流微信群。

阿里专家分享内部绝密RocketMQ核心原理与最佳实践PDF 前言 本文源码以RocketMQ 4.2.0 和 RocketMQ 4.3.0 为 基 础 , 从RocketMQ的实际使用到RocketMQ的源码分析,再到RocketMQ企业落地实践方案,逐步讲解。使读者由浅入深地了解RocketMQ。 本文在源码分析过程中,先讲整体流程,再按模块、步骤进行详细讲解,希望读者在阅读时能举一反三,能知其然且知其所以然。 本文总共九章,分为五部分,第一部分讲解消息队列入门和RocketMQ生产、消费原理与最佳实践;第二部分从整体角度讲解RocketMQ架构;第三部分讲 阅读详情

相关推荐

Rocketmq 源码分析之发送消息

在具体的实现过程中,RocketMQ并不直接将消息发送给一个指定的 Broker 节点,而是采用 Broker 的路由机制,通过 NameServer 自动获取到目标 Broker 上存储 Topic 相应的消息队列,在将消息发送到队列中。例如,消息的选择、创建、序列化等过程。3. 查找 Broker 端所在服务(根据 Topic 进行查找),然后从 Broker 端中获取到 Topic 所分配的消息队列(Message Queue)列表,并根据消息发送的策略,选择正确的消息队列进行消息发送。

程序员Linc 670

消息中间件 RocketMQ源码解析:Message顺序发送与消费

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34115824的博客 422

【檀越剑指大厂--RocketMQ】RocketMQ高阶篇

消息队列是一种“先进先出”的数据结构其应用场景主要包含以下 3 个方面系统的耦合性越高,容错性就越低。以电商应用为例,用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障或者因为升级等原因暂时不可用,都会造成下单操作异常,影响用户使用体验。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kGOkVKkV-1685157541998)(http://qinyingjie.top/blogImg/%E8%A7%A3%E8%80%A61.png)]使用消息

优快云站内信: https://i.youkuaiyun.com/#/msg/chat/qyj19920704 3万+

消息中间件RocketMQ的顺序消息

单靠脑袋是记不住的鸭~ 文章学习总结于视频:B站学习视频 文章目录顺序消费的原理解析示例1. 创建订单类2. 创建 Producer2.1 在 Producer 类中创建构建订单的方法2.2 在 Producer 类中实现发送消息3. 消费者实现 顺序消息指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。 顺序消费的原理解析 在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的.

江湖人称小程的博客 591

Rk3568 百兆以太网RMII模式DTS配置详解

本文详细解析了RK3568芯片在百兆以太网RMII模式下的DTS配置方法,包括时钟树配置、PHY接口模式定义及实际工程中的进阶设置。通过实战案例和调试技巧,帮助开发者快速解决RMII模式下的常见问题,提升工业控制设备的网络稳定性。

weixin_29189377的博客 422

工程制图习题集张大庆课后习题答案解析

所以,我想向大家求助,希望能得到一些学习资料、学习方法和学习经验,帮助我更好地理解和掌握这门课程。在寻找学习资料的过程中,我发现了一款叫做资小料的APP,它里面有很多关于工程制图的学习资料,包括视频教程、习题解答和考试真题等。通过使用资小料,我逐渐掌握了工程制图的基本理论和方法,解决了很多学习中遇到的问题。在这里,我想推荐给和我一样在学习《工程制图习题集》这门课的同学们,希望大家也能从这款APP中受益,共同进步。上传者: 『Consentesdei』蒼迷穀。出版社: 清华大学出版社。

weixin_58833001的博客 926

消息中间件篇之Kafka-消费顺序性

1. 即时消息中的单对单聊天和群聊,保证发送方消息发送顺序与接收方的顺序一致。2. 充值转账两个渠道在同一个时间进行余额变更,短信通知必须要有顺序。

冲鸭的博客 1247

QT6实现USB摄像头接入显示与抓拍功能实现

QT实现USB摄像头接入显示 一、UVC协议简介 UVC全称是USB Video Class(USB视频类),是一种标准化的USB视频设备通信协议,它定义了摄像头与主机之间的数据传输协议和格式。 UVC协议的出现,解决了摄像头厂商之间互不兼容,以及摄像头应用开发与平台之间的散乱问题。 UVC标准是由USB-IF主导制定的,目前最新的版本是UVC 1.5,UVC的缺省设置类容易实现,不需要额外的驱动程序。

热土程序园,利他愉己~ 3418

深入理解Java消息中间件-消息的顺序性

消息的顺序性指的是确保消息按照发送的先后顺序被接收和处理。这听起来简单,但在分布式系统中,由于网络延迟、系统故障、并发处理等因素,实现这一点往往非常具有挑战性。

u011532237的博客 903

OD图 arcgis曲线制作插件 od图曲线 路径图曲线制作插件 航线图曲线制作插件

在ArcGIS 10.2 中导入节点数据,打开插件,选择输入起始节点和目标节点的节点文件Point,以Name字段(地名)为连接线的起始字段和目标字段,(Input Table File)选择连接关系表格SDLine.csv,(Origin Column 和 Destination Column)为起始节点和目标节点,(Output Path)为输出路径,应选择.gdb数据库路径,否则容易报错,(Chord Coefficient)为连线的曲率,数值越大,曲率越高。运行环境:ArcGIS10.2。...

JData_Engineer的博客 2670

OpenFeign整合Sentinel实现熔断降级

常用于实现熔断降级的框架有Hystrix、Sentinel,我们常说的Spring Cloud项目说的其实是Spring Cloud Netflix,Hystrix以及前面学习过的Ribbon都是Netflix系的家族成员,所以使用Hystrix可以非常简单的与当前项目中使用到的OpenFeign、Ribbon整合,但笔者并没有选择Hystrix,而是选择阿里系的Sentinel。 为什么选择Sentinel而不是Hystrix?从接入简单考虑可能选择Hystrix是不错的选择,但笔者对Hyst...

吴就业 6718

分布式消息队列 RocketMQ 源码分析——Message 顺序发送与消费

  RocketMQ 提供了两种顺序级别:   普通顺序消息 :Producer 将相关联的消息发送到相同的消息队列。完全严格顺序 :在 普通顺序消息 的基础上,Consumer 严格顺序消费。   绝大部分场景下只需要用到普通顺序消息。   例如说:给用户发送短信消息 + 发送推送消息,将两条消息发送到不同的消息队列,若其中一条消息队列消费较慢造成堵塞,用户可能会收到两条消息会存在一定的时间差,带来的体验会相对较差。当然类似这种场景,即使有一定的时间差,不会产生系统逻辑上BUG。另外,普通顺...

zouduchen的博客 3780

淋巴结病理数字玻片读取与处理代码

Whole-slide images are generally stored in a multi-resolution pyramid structure. 首先安装openslide模块 pip install openslide-python import os.path as osp import openslide from pathlib import Path...

猫头军师兵书 1276

RocketMQ 源码分析 —— Message 顺序发送与消费

本文详细介绍了RocketMQ顺序消息的实现机制。顺序消息要求消费顺序与生产顺序一致,适用于订单处理、数据同步等场景。RocketMQ通过"局部顺序"设计实现全局扩展性:发送端使用MessageQueueSelector确保同一业务ID消息进入同一队列;Broker端通过CommitLog和ConsumeQueue保证存储顺序;消费端采用队列锁和顺序消费服务确保顺序处理。系统还包含完善的异常处理、重试机制和性能优化方案,如队列数量调优、批处理设置等,在保证顺序性的同时兼顾系统性能。典型问

油墨香^-^的博客 948

Google Earth Engine(GEE)——全球影像数据正确下载方式和注意事项

今天看到有人担心全球影像能不能下载的问题? 有时候我们会用到全球的数据,但是如何下载全球的数据呢? 首先如果没有全球的矢量边界我们会不会无法下载全球的数据,答案当然是可以的,因为我们在边界筛选的过程中filterBounds(),就是为了筛选边界,当我们不进行边界筛选的时候,自然而然默认的边界范围就是全球的,但是紧接着会面临其他两个问题:1如何下载?2如何在不缩小分辨率的情况下下载成功? 对于第一个问题我们如何下载?还是按照正常的逻辑利用export直接倒出到Google硬盘当中, 代码:

此星光明博客 3773

基于ESP32和OV5640的目标检测系统

本文详细介绍了基于ESP32-S3和OV5640摄像头构建目标检测系统的完整方案。系统采用YOLOv8-Nano轻量级模型,通过INT8量化实现边缘部署,在UXGA分辨率下达到25fps帧率。文章从硬件选型、图像采集优化、模型量化部署、实时推理流水线构建等维度展开,重点解决了内存管理、电源稳定性和异常处理等嵌入式系统关键问题。该方案在资源受限条件下实现了150ms以内的端到端延迟,为物联网视觉应用提供了低成本、低功耗的解决方案,适用于智能家居、工业检测等多个场景。

reset2021的博客 329

RocketMQ源码分析之顺序消费

概述RocketMQ按照顺序消费有两种顺序级别,一种是普通顺序消息。另外一种是更完全严格顺序 普通顺序消息指的是Producer将消息发送到相对应的消息队列上面 完全严格顺序:在普通顺序消息的基础上,Consumer严格进行顺序消费 在绝大部分情况下只需要用到普通顺序消息,大部分的应用都能容忍短暂的乱序;官方文档给出的说明中表示目前只有数据库的Binlog同步会强依赖完全严格顺序(要保证数据库事务

1Q84emo的博客 1020

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

RocketMQ高级功能与源码分析

1. 高级功能 1.1 消息存储 分布式队列因为有高可靠性的要求,所以数据要进行持久化存储。 消息生成者发送消息 MQ收到消息,将消息进行持久化,在存储中新增一条记录 返回ACK给生产者 MQ push 消息给对应的消费者,然后等待消费者返回ACK 如果消息消费者在指定时间内成功返回ack,那么MQ认为消息消费成功,在存储中删除消息,即执行第6步;如果MQ在指定时间内没有收到ACK,则认为消息消费失败,会尝试重新push消息,重复执行4、5、6步骤 MQ删除消息 1.1.1 存储介质 关系型数据库D

王鹏鹏-软件研发工程师 838

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

RocketMQ架构讲解与源码分析

1. 高级功能 1.1 消息存储 分布式队列因为有高可靠性的要求,所以数据要进行持久化存储。 消息生成者发送消息 MQ收到消息,将消息进行持久化,在存储中新增一条记录 返回ACK给生产者 MQ push 消息给对应的消费者,然后等待消费者返回ACK 如果消息消费者在指定时间内成功返回ack,那么MQ认为消息消费成功,在存储中删除消息,即执行第6步;如果MQ在指定时间内没有收到ACK,则认为消息消费失败,会尝试重新push消息,重复执行4、5、6步骤 MQ删除消息 1.1.1 存储介质 关系型数据库D

ZHONGZEWEI的博客 579

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

RocketMQ源码分析之基础知识介绍(一)

RocketMQ之NameSrv启动流程源码分析 本文章会从如下几个方面讲解NameSrv启动流程 NameSrv的优势 NameSrv启动UML图解 NameSrv启动源码讲解 一、NameSrv的优势 RocketMq经历了三个主要版本的迭代,Metaq 1.x , MetaQ 2.x , RocketMQ 3.x , 在 Metaq 1.x 和 2.x版本中,用zookeep...

yewandemty的博客 2267

分布式消息队列 RocketMQ 源码分析 —— Message 拉取与消费(上)

摘要: 原创出处 http://www.iocoder.cn/RocketMQ/message-pull-and-consume-first/ 「芋道源码」欢迎转载,保留摘要,谢谢! 本文主要基于 RocketMQ 4.0.x 正式版 1、概述 2、ConsumeQueue 结构 3、ConsumeQueue 存储 ReputMessageService DefaultMessage...

芋艿V 868

RocketMQ 源码合集

消息队列中间件 RocketMQ 源码分析 —— Message 发送与接收消息队列中间件 RocketMQ 源码分析 —— Message 存储分布式消息队列 RocketMQ 源码分析 —— Message 拉取与消费(上)分布式消息队列 RocketMQ 源码分析 —— Message 拉取与消费(下)分布式消息队列 RocketMQ 源码分析 —— Message 顺序发送与消费分布式消息

芋道源码 1331

RocketMQ消息重试机制

生产者消费者NameServer - 收集broker的信息(IP、端口等)、客户端从NameServer上获取Broker的信息并找到可用Broker去发/拉消息;主要负责服务发现和路由功能,而Broker则负责消息的存储和路由分发。这两个组件共同协作,确保RocketMQ能够高效、可靠地处理消息的产生、存储和消费。Broker - 实际处理消息的服务,响应客户端请求的。

极客专栏 1440

全网首发,纯手打“RocketMQ笔记”,帮你解决95%以上的问题!

消息队列 RocketMQ 是阿里巴巴集团基于高可用分布式集群技术,自主研发的云正式商用的专业消息中间件,既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性,是阿里巴巴双 11 使用的核心产品。 6月初偶然从朋友那里得到一份纯手打“RocketMQ笔记”(貌似全网都没有这份学习笔记,中大奖了,哈哈哈),花了十天时间刷了第一遍,感觉挺不错的,今天拿出来献个宝。全网首发,纯手打“RocketMQ笔记”,帮你解决95%以上的问题! 这份纯.

mifffy_java的博客 652
上一篇: 【Netty 专栏】深入浅出 Netty read
下一篇: 一起来学 SpringBoot 2.x | 第二篇:SpringBoot 配置详解
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值