分布式消息队列 RocketMQ 源码分析 —— 定时消息与消息重试

互联网全景消息(6)之RocketMq-NameServer源码分析 RocketMQ 是阿里巴巴集团基于高可用分布式集群技术,自主研发的云正式商用的专业消息中间件,既可为分布式应用系统提供异步解耦和削峰填谷的能力,同时也具备互联网应用所需的海量消息堆积、高吞吐、可靠重试等特性,是阿里巴巴双 11 使用的核心产品。 阅读详情

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

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


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

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

1. 概述

建议前置阅读内容:

�� 为什么把定时消息与消息重试放在一起?你猜。
�� 你猜我猜不猜。

2. 定时消息

定时消息是指消息发到 Broker 后,不能立刻被 Consumer 消费,要到特定的时间点或者等待特定的时间后才能被消费。

下图是定时消息的处理逻辑图:

定时消息逻辑图.png

2.1 延迟级别

RocketMQ 目前只支持固定精度的定时消息。官方说法如下:

如果要支持任意的时间精度,在 Broker 层面,必须要做消息排序,如果再涉及到持久化,那么消息排序要不可避免的产生巨大性能开销。

  • 延迟级别:
延迟级别时间
11s
25s
310s
430s
51m
62m
73m
84m
95m
106m
117m
128m
139m
1410m
1520m
1630m
171h
182h

* 核心源码如下:

```Java
  1: // ⬇️⬇️⬇️【MessageStoreConfig.java】
  2: /**
  3:  * 消息延迟级别字符串配置
  4:  */
  5: private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
  6: 
  7: // ⬇️⬇️⬇️【ScheduleMessageService.java】
  8: /**
  9:  * 解析延迟级别
 10:  *
 11:  * @return 是否解析成功
 12:  */
 13: public boolean parseDelayLevel() {
 14:     HashMap timeUnitTable = new HashMap<>();
 15:     timeUnitTable.put("s", 1000L);
 16:     timeUnitTable.put("m", 1000L * 60);
 17:     timeUnitTable.put("h", 1000L * 60 * 60);
 18:     timeUnitTable.put("d", 1000L * 60 * 60 * 24);
 19: 
 20:     String levelString = this.defaultMessageStore.getMessageStoreConfig().getMessageDelayLevel();
 21:     try {
 22:         String[] levelArray = levelString.split(" ");
 23:         for (int i = 0; i < levelArray.length; i++) {
 24:             String value = levelArray[i];
 25:             String ch = value.substring(value.length() - 1);
 26:             Long tu = timeUnitTable.get(ch);
 27: 
 28:             int level = i + 1;
 29:             if (level > this.maxDelayLevel) {
 30:                 this.maxDelayLevel = level;
 31:             }
 32:             long num = Long.parseLong(value.substring(0, value.length() - 1));
 33:             long delayTimeMillis = tu * num;
 34:             this.delayLevelTable.put(level, delayTimeMillis);
 35:         }
 36:     } catch (Exception e) {
 37:         log.error("parseDelayLevel exception", e);
 38:         log.info("levelString String = {}", levelString);
 39:         return false;
 40:     }
 41: 
 42:     return true;
 43: }
```

2.2 Producer 发送定时消息

  • ��发送时,设置消息的延迟级别。
Message msg = new Message(...);
msg.setDelayTimeLevel(level);

2.3 Broker 存储定时消息

  • �� 存储消息时,延迟消息进入 Topic 为 SCHEDULE_TOPIC_XXXX。
  • �� 延迟级别 与 消息队列编号 做固定映射:QueueId = DelayLevel - 1。

核心代码如下:

  1: // ⬇️⬇️⬇️【CommitLog.java】
  2: /**
  3:  * 添加消息,返回消息结果
  4:  *
  5:  * @param msg 消息
  6:  * @return 结果
  7:  */
  8: public PutMessageResult putMessage(final MessageExtBrokerInner msg) {
  9:     // ....(省略代码) 
 10: 
 11:     // 定时消息处理
 12:     final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
 13:     if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE//
 14:         || tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
 15:         // Delay Delivery
 16:         if (msg.getDelayTimeLevel() > 0) {
 17:             if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
 18:                 msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
 19:             }
 20: 
 21:             // 存储消息时,延迟消息进入 `Topic` 为 `SCHEDULE_TOPIC_XXXX` 。
 22:             topic = ScheduleMessageService.SCHEDULE_TOPIC;
 23: 
 24:             // 延迟级别 与 消息队列编号 做固定映射
 25:             queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());
 26: 
 27:             // Backup real topic, queueId
 28:             MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
 29:             MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
 30:             msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
 31: 
 32:             msg.setTopic(topic);
 33:             msg.setQueueId(queueId);
 34:         }
 35:     }
 36: 
 37:     // ....(省略代码) 
 38: }
 39: 
 40: // ⬇️⬇️⬇️【ScheduleMessageService.java】
 41: /**
 42:  * 根据 延迟级别 计算 消息队列编号
 43:  * QueueId = DelayLevel - 1
 44:  *
 45:  * @param delayLevel 延迟级别
 46:  * @return 消息队列编号
 47:  */
 48: public static int delayLevel2QueueId(final int delayLevel) {
 49:     return delayLevel - 1;
 50: }

  • �� 生成 ConsumeQueue 时,每条消息的 tagsCode 使用【消息计划消费时间】。这样,ScheduleMessageService 在轮询 ConsumeQueue 时,可以使用 tagsCode 进行过滤。

核心代码如下:

  1: // ⬇️⬇️⬇️【CommitLog.java】
  2: /**
  3:  * check the message and returns the message size
  4:  *
  5:  * @return 0 Come the end of the file // >0 Normal messages // -1 Message checksum failure
  6:  */
  7: public DispatchRequest checkMessageAndReturnSize(ByteBuffer byteBuffer, final boolean checkCRC, final boolean readBody) {
  8:     try {
  9:         // // ....(省略代码)
 10: 
 11:         // 17 properties
 12:         short propertiesLength = byteBuffer.getShort();
 13:         if (propertiesLength > 0) {
 14:             // ....(省略代码)
 15:             String tags = propertiesMap.get(MessageConst.PROPERTY_TAGS);
 16:             if (tags != null && tags.length() > 0) {
 17:                 tagsCode = MessageExtBrokerInner.tagsString2tagsCode(MessageExt.parseTopicFilterType(sysFlag), tags);
 18:             }
 19: 
 20:             // Timing message processing
 21:             {
 22:                 String t = propertiesMap.get(MessageConst.PROPERTY_DELAY_TIME_LEVEL);
 23:                 if (ScheduleMessageService.SCHEDULE_TOPIC.equals(topic) && t != null) {
 24:                     int delayLevel = Integer.parseInt(t);
 25: 
 26:                     if (delayLevel > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
 27:                         delayLevel = this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel();
 28:                     }
 29: 
 30:                     if (delayLevel > 0) {
 31:                         tagsCode = this.defaultMessageStore.getScheduleMessageService().computeDeliverTimestamp(delayLevel,
 32:                             storeTimestamp);
 33:                     }
 34:                 }
 35:             }
 36:         }
 37: 
 38:         // ....(省略代码)
 39: 
 40:         return new DispatchRequest(//
 41:             topic, // 1
 42:             queueId, // 2
 43:             physicOffset, // 3
 44:             totalSize, // 4
 45:             tagsCode, // 5
 46:             storeTimestamp, // 6
 47:             queueOffset, // 7
 48:             keys, // 8
 49:             uniqKey, //9
 50:             sysFlag, // 9
 51:             preparedTransactionOffset// 10
 52:         );
 53:     } catch (Exception e) {
 54:     }
 55: 
 56:     return new DispatchRequest(-1, false /* success */);
 57: }
 58: 
 59: // ⬇️⬇️⬇️【ScheduleMessageService.java】
 60: /**
 61:  * 计算 投递时间【计划消费时间】
 62:  *
 63:  * @param delayLevel 延迟级别
 64:  * @param storeTimestamp 存储时间
 65:  * @return 投递时间【计划消费时间】
 66:  */
 67: public long computeDeliverTimestamp(final int delayLevel, final long storeTimestamp) {
 68:     Long time = this.delayLevelTable.get(delayLevel);
 69:     if (time != null) {
 70:         return time + storeTimestamp;
 71:     }
 72: 
 73:     return storeTimestamp + 1000;
 74: }

2.4 Broker 发送定时消息

  • �� 对 SCHEDULE_TOPIC_XXXX 每条消费队列对应单独一个定时任务进行轮询,发送 到达投递时间【计划消费时间】 的消息。

下图是发送定时消息的处理逻辑图:

定时消息定时逻辑

实现代码如下:

  1: /**
  2:  * ⬇️⬇️⬇️ 发送(投递)延迟消息定时任务
  3:  */
  4: class DeliverDelayedMessageTimerTask extends TimerTask {
  5:     /**
  6:      * 延迟级别
  7:      */
  8:     private final int delayLevel;
  9:     /**
 10:      * 位置
 11:      */
 12:     private final long offset;
 13: 
 14:     public DeliverDelayedMessageTimerTask(int delayLevel, long offset) {
 15:         this.delayLevel = delayLevel;
 16:         this.offset = offset;
 17:     }
 18: 
 19:     @Override
 20:     public void run() {
 21:         try {
 22:             this.executeOnTimeup();
 23:         } catch (Exception e) {
 24:             // XXX: warn and notify me
 25:             log.error("ScheduleMessageService, executeOnTimeup exception", e);
 26:             ScheduleMessageService.this.timer.schedule(new DeliverDelayedMessageTimerTask(
 27:                 this.delayLevel, this.offset), DELAY_FOR_A_PERIOD);
 28:         }
 29:     }
 30: 
 31:     /**
 32:      * 纠正可投递时间。
 33:      * 因为发送级别对应的发送间隔可以调整,如果超过当前间隔,则修正成当前配置,避免后面的消息无法发送。
 34:      *
 35:      * @param now 当前时间
 36:      * @param deliverTimestamp 投递时间
 37:      * @return 纠正结果
 38:      */
 39:     private long correctDeliverTimestamp(final long now, final long deliverTimestamp) {
 40:         long result = deliverTimestamp;
 41: 
 42:         long maxTimestamp = now + ScheduleMessageService.this.delayLevelTable.get(this.delayLevel);
 43:         if (deliverTimestamp > maxTimestamp) {
 44:             result = now;
 45:         }
 46: 
 47:         return result;
 48:     }
 49: 
 50:     public void executeOnTimeup() {
 51:         ConsumeQueue cq = ScheduleMessageService.this.defaultMessageStore.findConsumeQueue(SCHEDULE_TOPIC,  delayLevel2QueueId(delayLevel));
 52: 
 53:         long failScheduleOffset = offset;
 54: 
 55:         if (cq != null) {
 56:             SelectMappedBufferResult bufferCQ = cq.getIndexBuffer(this.offset);
 57:             if (bufferCQ != null) {
 58:                 try {
 59:                     long nextOffset = offset;
 60:                     int i = 0;
 61:                     for (; i < bufferCQ.getSize(); i += ConsumeQueue.CQ_STORE_UNIT_SIZE) {
 62:                         long offsetPy = bufferCQ.getByteBuffer().getLong();
 63:                         int sizePy = bufferCQ.getByteBuffer().getInt();
 64:                         long tagsCode = bufferCQ.getByteBuffer().getLong();
 65: 
 66:                         long now = System.currentTimeMillis();
 67:                         long deliverTimestamp = this.correctDeliverTimestamp(now, tagsCode);
 68: 
 69:                         nextOffset = offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE);
 70: 
 71:                         long countdown = deliverTimestamp - now;
 72: 
 73:                         if (countdown <= 0) { // 消息到达可发送时间
 74:                             MessageExt msgExt = ScheduleMessageService.this.defaultMessageStore.lookMessageByOffset(offsetPy, sizePy);
 75:                             if (msgExt != null) {
 76:                                 try {
 77:                                     // 发送消息
 78:                                     MessageExtBrokerInner msgInner = this.messageTimeup(msgExt);
 79:                                     PutMessageResult putMessageResult = ScheduleMessageService.this.defaultMessageStore.putMessage(msgInner);
 80:                                     if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) { // 发送成功
 81:                                         continue;
 82:                                     } else { // 发送失败
 83:                                         // XXX: warn and notify me
 84:                                         log.error("ScheduleMessageService, a message time up, but reput it failed, topic: {} msgId {}", msgExt.getTopic(), msgExt.getMsgId());
 85: 
 86:                                         // 安排下一次任务
 87:                                         ScheduleMessageService.this.timer.schedule(new DeliverDelayedMessageTimerTask(this.delayLevel, nextOffset), DELAY_FOR_A_PERIOD);
 88: 
 89:                                         // 更新进度
 90:                                         ScheduleMessageService.this.updateOffset(this.delayLevel, nextOffset);
 91:                                         return;
 92:                                     }
 93:                                 } catch (Exception e) {
 94:                                     // XXX: warn and notify me
 95:                                     log.error("ScheduleMessageService, messageTimeup execute error, drop it. msgExt="
 96:                                             + msgExt + ", nextOffset=" + nextOffset + ",offsetPy=" + offsetPy + ",sizePy=" + sizePy, e);
 97:                                 }
 98:                             }
 99:                         } else {
100:                             // 安排下一次任务
101:                             ScheduleMessageService.this.timer.schedule(new DeliverDelayedMessageTimerTask(this.delayLevel, nextOffset), countdown);
102: 
103:                             // 更新进度
104:                             ScheduleMessageService.this.updateOffset(this.delayLevel, nextOffset);
105:                             return;
106:                         }
107:                     } // end of for
108: 
109:                     nextOffset = offset + (i / ConsumeQueue.CQ_STORE_UNIT_SIZE);
110: 
111:                     // 安排下一次任务
112:                     ScheduleMessageService.this.timer.schedule(new DeliverDelayedMessageTimerTask(this.delayLevel, nextOffset), DELAY_FOR_A_WHILE);
113: 
114:                     // 更新进度
115:                     ScheduleMessageService.this.updateOffset(this.delayLevel, nextOffset);
116:                     return;
117:                 } finally {
118:                     bufferCQ.release();
119:                 }
120:             } // end of if (bufferCQ != null)
121:             else { // 消费队列已经被删除部分,跳转到最小的消费进度
122:                 long cqMinOffset = cq.getMinOffsetInQueue();
123:                 if (offset < cqMinOffset) {
124:                     failScheduleOffset = cqMinOffset;
125:                     log.error("schedule CQ offset invalid. offset=" + offset + ", cqMinOffset="
126:                         + cqMinOffset + ", queueId=" + cq.getQueueId());
127:                 }
128:             }
129:         } // end of if (cq != null)
130: 
131:         ScheduleMessageService.this.timer.schedule(new DeliverDelayedMessageTimerTask(this.delayLevel, failScheduleOffset), DELAY_FOR_A_WHILE);
132:     }
133: 
134:     /**
135:      * 设置消息内容
136:      *
137:      * @param msgExt 消息
138:      * @return 消息
139:      */
140:     private MessageExtBrokerInner messageTimeup(MessageExt msgExt) {
141:         MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
142:         msgInner.setBody(msgExt.getBody());
143:         msgInner.setFlag(msgExt.getFlag());
144:         MessageAccessor.setProperties(msgInner, msgExt.getProperties());
145: 
146:         TopicFilterType topicFilterType = MessageExt.parseTopicFilterType(msgInner.getSysFlag());
147:         long tagsCodeValue =
148:             MessageExtBrokerInner.tagsString2tagsCode(topicFilterType, msgInner.getTags());
149:         msgInner.setTagsCode(tagsCodeValue);
150:         msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgExt.getProperties()));
151: 
152:         msgInner.setSysFlag(msgExt.getSysFlag());
153:         msgInner.setBornTimestamp(msgExt.getBornTimestamp());
154:         msgInner.setBornHost(msgExt.getBornHost());
155:         msgInner.setStoreHost(msgExt.getStoreHost());
156:         msgInner.setReconsumeTimes(msgExt.getReconsumeTimes());
157: 
158:         msgInner.setWaitStoreMsgOK(false);
159:         MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_DELAY_TIME_LEVEL);
160: 
161:         msgInner.setTopic(msgInner.getProperty(MessageConst.PROPERTY_REAL_TOPIC));
162: 
163:         String queueIdStr = msgInner.getProperty(MessageConst.PROPERTY_REAL_QUEUE_ID);
164:         int queueId = Integer.parseInt(queueIdStr);
165:         msgInner.setQueueId(queueId);
166: 
167:         return msgInner;
168:     }
169: }

2.5 Broker 持久化定时发送进度

  • �� 定时消息发送进度存储在文件(../config/delayOffset.json)里
  • �� 每 10s 定时持久化发送进度。

核心代码如下:

  1: // ⬇️⬇️⬇️【ScheduleMessageService.java】
  2: /**
  3: public void start() {
  4:     // 定时发送消息
  5:     for (Map.Entry entry : this.delayLevelTable.entrySet()) {
  6:         Integer level = entry.getKey();
  7:         Long timeDelay = entry.getValue();
  8:         Long offset = this.offsetTable.get(level);
  9:         if (null == offset) {
 10:             offset = 0L;
 11:         }
 12: 
 13:         if (timeDelay != null) {
 14:             this.timer.schedule(new DeliverDelayedMessageTimerTask(level, offset), FIRST_DELAY_TIME);
 15:         }
 16:     }
 17: 
 18:     // 定时持久化发送进度
 19:     this.timer.scheduleAtFixedRate(new TimerTask() {
 20: 
 21:         @Override
 22:         public void run() {
 23:             try {
 24:                 ScheduleMessageService.this.persist();
 25:             } catch (Exception e) {
 26:                 log.error("scheduleAtFixedRate flush exception", e);
 27:             }
 28:         }
 29:     }, 10000, this.defaultMessageStore.getMessageStoreConfig().getFlushDelayOffsetInterval());
 30: }

3. 消息重试

Consumer 消费消息失败后,要提供一种重试机制,令消息再消费一次。

  • �� Consumer 将消费失败的消息发回 Broker,进入延迟消息队列。即,消费失败的消息,不会立即消费。

核心代码如下:

  1: // ⬇️⬇️⬇️【SendMessageProcessor.java】
  2: /**
  3:  * 消费者发回消息
  4:  *
  5:  * @param ctx ctx
  6:  * @param request 请求
  7:  * @return 响应
  8:  * @throws RemotingCommandException 当远程调用异常
  9:  */
 10: private RemotingCommand consumerSendMsgBack(final ChannelHandlerContext ctx, final RemotingCommand request)
 11:     throws RemotingCommandException {
 12:     // ....(省略代码)
 13:     // 处理 delayLevel(独有)。
 14:     int delayLevel = requestHeader.getDelayLevel();
 15:     int maxReconsumeTimes = subscriptionGroupConfig.getRetryMaxTimes();
 16:     if (request.getVersion() >= MQVersion.Version.V3_4_9.ordinal()) {
 17:         maxReconsumeTimes = requestHeader.getMaxReconsumeTimes();
 18:     }
 19:     if (msgExt.getReconsumeTimes() >= maxReconsumeTimes//
 20:     // ....(省略代码)
 21:     } else {
 22:         if (0 == delayLevel) {
 23:             delayLevel = 3 + msgExt.getReconsumeTimes();
 24:         }
 25:         msgExt.setDelayTimeLevel(delayLevel);
 26:     }
 27: 
 28:     // ....(省略代码)
 29:     return response;
 30: }

666. 彩蛋

知识星球


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

RocketMQ学习笔记五(黑马)大神级 /创建主题//根据时间戳从队列中查找消息偏移量//查找消息队列中最大的偏移量//查找消息队列中最小的偏移量//根据偏移量查找消息//根据条件查找消息//根据消息ID和主题查找消息//启动//关闭//查找该主题下所有消息//同步发送消息//同步超时发送消息//异步发送消息//异步超时发送消息//发送单向消息//选择指定队列同步发送消息//选择指定队列异步发送消息//选择指定队列单项发送消息//批量发送消息。 阅读详情

相关推荐

RocketMQ5.0--定时消息

消息延迟级别在Broker端通过MessageStoreConfig#messageDelayLevel配置,默认为"1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h",其delayLevel=1则延迟1s,delayLevel=2则延迟5s,以此类推。org.apache.rocketmq.broker.schedule.ScheduleMessageService#load完成延迟消费进度的加载且解析延迟级别字符串,如下代码所示。

专搞技术的小兔子 932

源码分析RocketMQ之消息消费重试机制

本文主要关注业务方在消息消费失败后,返回ConsumeConcurrentlyStatus.RECONSUME_LATER,专业术语:业务方每条消息消费后要告诉MQ消费者一个结果(ack,message back),触发MQ消息消费重试机制,然后MQ消费者需要反馈给MQ(Broker) 本文主要针对的还是非顺序消息机制,顺序消息在后续专题详细分析。 代码入口:ConsumeMessageCon

中间件兴趣圈 1万+

【檀越剑指大厂--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消费失败重试源码分析。

啃了两天这个功能的源码,涉及了好几个模块,终于把这块逻辑啃完了。看完之后有种豁然开朗的感觉。 首先我们要知道一点 消息重试的功能是建立在延迟消费消息这个功能的前提条件之下的。这篇文章会穿插延迟发送消息的一些源码分析。 ① 当消费者(client)启动的时候,将会调用copySubscription()方法。 ②消费端拉取到消息后将拉取到的消息List(最多32条)扔给Cousum...

yifouhu2947的博客 1844

ESP32开发环境与微信小程序通信的完整示例

通过搭建稳定的esp32开发环境,实现其与微信小程序间的双向通信,涵盖Wi-Fi连接、数据收发与协议设计,为物联网应用提供低成本解决方案。

weixin_42126677的博客 1135

5款高性价比LDO芯片实测对比:从TPS7A4501到LM2940,哪款更适合你的项目?

本文通过实测对比了TPS7A4501、TPS72301、LM2937、LM2940等五款高性价比LDO芯片的核心性能。文章详细分析了跌落电压、输出噪声、静态电流及热性能等关键指标,并结合电池供电物联网节点、高精度数据采集等典型应用场景,提供了清晰的选型指南,帮助工程师根据项目需求选择最合适的LDO芯片。

weixin_29305337的博客 116

RocketMQ源码(23)—DefaultMQPushConsumer消费者重试消息和死信消息源码

RocketMQ DefaultMQPushConsumer消费者重试消息和死信消息源码。

终生学习践行者 2814

ArcMap新手必看:Shape属性中的点ZM值到底是什么?如何快速处理

本文深入解析ArcMap中Shape属性中的点ZM值,揭示其在GIS数据中的隐藏维度作用,包括Z值(高程值)和M值(测量值)。针对ZM值可能引发的服务发布问题,提供三种高效处理方案,帮助新手快速掌握数据导入时的ZM值管控策略及精准去除方法。

weixin_42561476的博客 432

消息中间件 RocketMQ源码解析:定时消息与消息重试

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

weixin_33772645的博客 415

数字IC设计——功耗分析

功耗是数字IC芯片的一个重要指标

知识,既贵,又便宜。 9373

SQL的基本介绍

1.什么是数据库?2.什么是数据库管理系统?3.什么是 SQL?1.数据库是保存有组织的数据的容器,通常是一个文件或一组文件,可以将其看作电子化的文件柜。用户可以对文件中的数据执行新增、删除、更新、查询等操作。2.数据库管理系统是一种操纵和管理数据库的大型软件,用于建立、使用和维护数据库,简称DBMS。它对数据库进行统一的管理和控制,以保证数据库的安全性和完整性。用户可以通过 DBMS 访问数据库中的数据,数据库管理员也可以通过 DBMS 进行数据库的维护工作。

shiwei0813的博客 2879

java延迟消息队列,分布式消息队列 RocketMQ 源码分析 —— 定时消息与消息重试...

1. 概述建议前置阅读内容:《RocketMQ 源码分析 —— Message 发送与接收》《RocketMQ 源码分析 —— Message 拉取与消费(下)》为什么把定时消息与消息重试放在一起?你猜。你猜我猜不猜。2. 定时消息定时消息是指消息发到 Broker 后,不能立刻被 Consumer 消费,要到特定的时间点或者等待特定的时间后才能被消费。下图是定时消息的处理逻辑图:2.1 延迟级别...

weixin_35782626的博客 273

uniapp - 详细手机端h5网页调用手机摄像头进行二维码识别扫描,在uniapp的H5移动端微信网页平台中,在浏览器中调用手机扫码功能详细教程,识别二维码并获取扫描结果(完整示例源码,一键复制即用

uniapp,h5,手机端网页,H5手机网页,uniapp开发h5实现扫码功能,uniapp微信h5网页扫码sdk使用教程,uniapp h5网页如何实现浏览器扫码,uniapp扫码功能怎么兼容h5_uniapph5怎么做扫码功能,uniapp的手机网页如何实现扫二维码一维码,浏览器H5实现js扫码,H5调用摄像头和扫一扫功能,亲测可用,H5调用本地摄像头实现扫一扫,uni-h5项目解决扫码功能,uniapp实现微信H5扫码,解决uniapp在H5端使用不能扫码问题,uni-app的h5扫码(二维码、条形码

✅ 精选推荐文章 1万+

RocketMQ 第三章 高级功能和源码分析 1 高级功能 1.4 消息重试

RocketMQ 第三章 高级功能和源码分析 1 高级功能 1.4 消息重试

代码改变世界 398

OpenClaw进阶实战(二十五):抖音企业号——私信回复与视频评论分析

摘要:本文介绍抖音企业号自动化运营方案,突破抖音强风控体系的技术挑战。针对内容生产、私信互动和账号矩阵三大场景,提出混合架构解决方案: 私信自动化:结合关键词触发与随机延迟(45秒±15),实现拟人化回复,支持价格咨询/商务合作等高优先级消息识别 混合技术栈:官方API(合规)+真机RPA(覆盖长尾需求)+AI辅助生成(脚本→剪辑→发布闭环) 安全策略:通过设备指纹模拟、行为间隔随机化和内容差异化处理规避风控,提供每日200条的安全操作阈值。配套OpenClaw技能实现一键部署,包含cookie配置、回复规

PeterPan的博客 145

【OpenClaw企业级智能体实战】第04篇:OpenClaw成本优化实战——Token消耗降96%!claude-mem+OpenViking从入门到精通

OpenClaw作为企业级智能体框架,凭借强大的自动化能力成为企业数字化转型的热门工具,但原生机制导致的上下文冗余问题,让众多用户面临“Token消耗失控”的困境——单周账单数千元的案例屡见不鲜。本文基于阿里云实测数据与开源工具官方文档,先深度拆解OpenClaw成本黑洞的核心成因(记忆碎片化、Token指数级膨胀),再系统讲解两款核心优化工具(claude-mem单Agent记忆管理、OpenViking多Agent集群底座)的底层原理、算法架构与实现逻辑,提供从环境搭建、工具集成、参数调优到安全部署

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 707

消息中间件第一讲:RocketMQ从入门到精通

消息中间件activemq/rabbitMQ/rocketMQ/kafka 从入门到精通 mom消息中间件 推模式 参考资料 分布式消息中间件实战(倪炜)沈剑 架构师之路 spring实战 文章目录消息中间件activemq/rabbitMQ/rocketMQ/kafka 从入门到精通1、activemq详解?(版本:5.11.2)2、通信协议很多,如db,soa,socket,rmi,为什么要选择activemq?3、jms消息发送时序图?4、activemq支持的通信机制?5、j

Jet_qi的博客 2843

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

分布式消息队列RocketMQ与Kafka的18项差异之“拨乱反正”

我们知道,阿里的RocketMQ其实源自Kafka。同时网络上一直流传着1篇阿里中间件团队所写的RocketMQ与Kafka的18项差异的文章,并且被广泛转发。比如: http://blog.youkuaiyun.com/damacheng/article/details/42846549 https://yq.aliyun.com/articles/25389 作为对Kafka有一点研究的爱好者,

gh670011677的博客 709

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

Java面试通关⑬:消息队列MQ核心全集

摘要:消息队列是分布式系统核心中间件、高并发架构必备组件,中高级面试必问架构选型、削峰填谷、解耦异步、可靠性保障。多数开发者只会简单收发消息,不了解MQ核心作用、消息丢失、重复消费、顺序性、积压问题、集群高可用,面试架构题极易失分。本篇全覆盖MQ核心思想、主流中间件对比、生产问题解决方案、架构设计、高频真题,吃透分布式消息架构,搞定高并发异步业务设计。

折哥|智能物流与Java实战,副高数据库工程师。深耕出版社物流WMS/OMS(线体/立库/VGA),也分享Java进阶与OMS电商对接。Oracle→PG实战派。相信沉淀可迁移能力,分享通用技术,不藏不虚。 616

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

为什么选择RocketMQ

消息中间件,是在分布式系统中完成消息的发送和接受的基础软件,为分布式应用系统提供异步解耦、削峰填谷的能力。

mbtlami 746

RocketMQ 源码合集

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

芋道源码 1331

庖丁解牛:RocketMQ Broker/Consumer/Producer源码深度剖析与实战

本文深入剖析了RocketMQ的核心机制,从源码层面解析了Producer、Broker和Consumer三大组件。Producer部分详细分析了消息发送流程、队列选择策略和重试机制;Broker部分重点讲解了消息存储架构(CommitLog、ConsumeQueue)、请求处理和刷盘策略;Consumer部分则解析了推/拉模式、偏移量管理和重试机制。通过实战案例展示了分布式事务消息和消息过滤功能,并提供性能优化建议。

果酱 の 博客 1083

RocketMQ学习(三)

分布式队列因为有高可靠性的要求,所以数据要进行持久化存储。Apache下开源的另外一款MQ—ActiveMQ(默认采用的KahaDB做消息存储)可选用JDBC的方式来做消息持久化,通过简单的xml配置信息即可实现JDBC消息存储。由于,普通关系型数据库(如Mysql)在单表数据量达到千万级别的情况下,其IO读写性能往往会出现瓶颈。在可靠性方面,该种方案非常依赖DB,如果一旦DB出现故障,则MQ的消息就无法落盘存储会导致线上故障目前业界较为常用的几款产品(RocketMQ/Kafka/RabbitMQ)均采用

pscool的博客 512

RocketMQ学习 专题总结(三)

RocketMQ 专题RocketMQ 专题三1. 高级功能1.1 消息存储1.1.1 存储介质1.1.3 消息的存储和发送1)消息存储1.1.4 消息存储结构1.1.5 刷盘机制1)同步刷盘2)异步刷盘1.2 高可用性机制1.2.1 消息消费高可用1.2.2 消息发送高可用1.2.3 消息主从复制1.3 负载均衡1.3.1 Producer负载均衡1.3.2 Consumer负载均衡1)集群模式...

taylor的博客 944
上一篇: 数据结构 | Java 队列 —— Queue 详细分析
下一篇: 完整的一次 HTTP 请求响应过程(一)
公众号-芋道源码
博客等级 码龄10年 1636粉丝 377原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值