【RocketMQ】消息的发送
RocketMQ是通过DefaultMQProducer
进行消息发送的,它实现了MQProducer
接口,MQProducer
接口中定义了消息发送的方法,方法主要分为三大类:
send
同步进行消息发送,向Broker发送消息之后等待响应结果;send
异步进行消息发送,向Broker发送消息之后立刻返回,当消息发送成功/失败之后触发回调函数;sendOneway
单向发送,也是异步消息发送,向Broker发送消息之后立刻返回,但是没有回调函数;
public interface MQProducer extends MQAdmin {
// 同步发送消息
SendResult send(final Message msg) throws MQClientException, RemotingException, MQBrokerException,
InterruptedException;
// 异步发送消息,SendCallback为回调函数
void send(final Message msg, final SendCallback sendCallback) throws MQClientException,
RemotingException, InterruptedException;
// 异步发送消息,没有回调函数
void sendOneway(final Message msg) throws MQClientException, RemotingException,
InterruptedException;
}
接下来以同步发送为例,看下生产者发送消息的过程。
生产者发送消息
Topic主题
一般会为同一业务类型设定一个Topic,将不同的业务类型的数据放到不同的Topic中管理,不过主题只是一个逻辑概念,并不是实际的消息容器。
主题内部由多个消息队列(MessageQueue)组成,消息队列是消息存储的实际容器,消息队列与Kafka的分区(Partition)类似。
获取主题的发布信息(TopicPublishInfo)
Broker在启动时会向NameServer发送注册信息并定时向NameServer发送心跳包,Broker向NameServer注册信息中包括了该Broker的IP、Name以及Topic的配置信息,
生产者和消费者默认每30s从NameServer更新一次路由信息,可以知道消息所在的Topic分布在哪些Broker上。
生产者有一个主题路由信息表topicPublishInfoTable
,缓存从NameServer拉取到的路由信息,它是ConcurrentMap类型的,KEY为topic主题名称, value为该Topic的发布信息,是TopicPublishInfo
类型。
当生产者向Broker发送消息之前,首先需要知道消息所属的Topic的路由信息,有了Topic的路由信息才能知道Topic分布在哪个Broker上,生产者往哪个Broker上发,而topicPublishInfoTable
中记录了每个主题的相关信息,可以从topicPublishInfoTable
中查找Topic的路由信息。
如果从topicPublishInfoTable
中查找成功,就可以继续后续的步骤,如果查找失败,此时生产者需要从NameServer中查询该Topic的路由信息:
- 如果查询成功,会判断路由信息是否发生了变化,如果发生变化,生产者会更新本地缓存的该Topic的路由信息;
- 如果依旧未查询到,它会有一个默认的主题,会使用这个默认的主题进行消息发送;
选取消息队列
前面知道,一个Topic一般由多个消息队列组成,所以主题的发布信息数据TopicPublishInfo
获取到之后,需要从中选取一个消息队列,然后获取此消息队列所属的Broker,与Broker通信将消息投递到对应的消息队列中。
未启用故障延迟机制
在每个Topic内部,设置了一个计数器sendWhichQueue
用于轮询从消息队列集合中选取队列。
在未启用故障延迟机制的时候,如果上一次选择的BrokerName为空,也就是首次发送消息时,处理逻辑如下:
- 对计数器增一;
- 根据计数器的值对消息队列列表的长度取余得到下标值
pos
,从队列列表中获取pos
位置的元素,以此达到轮询从消息队列列表中选择消息队列的目的; - 返回第2步中获取到的消息队列;
- 在调用获取消息队列的地方,会记录本次选择消息队列所在的BrokerName;
如果上一次选择的BrokerName不为空,表示上次发送消息时就发送给了此Broker,此时的处理逻辑与上面的不同点在第3步,通过从队列列表中获取pos
位置的元素之后,并没有直接把选取到的消息队列返回,而是再增加一个判断,判断当前选取到的Broker是否与上次选择的Broker名称一致,如果一致会继续循环,轮询选择下一个消息队列,如果不一致则直接返回:
- 对计数器增一;
- 根据计数器的值对消息队列列表的长度取余得到下标值
pos
,从队列列表中获取pos
位置的元素; - 对第2步获取到的消息队列进行判断:
- 如果本次选取到的队列与上次发送消息的Broker一致,回到第1步继续选择下一个队列,如果一直未选出满足要求的消息队列,则不作判断,使用上面的方式轮询选择一个队列返回;
- 如果本次选取到的队列与上次发送消息的Broker不一致,返回当前的队列;
- 在调用获取消息队列的地方,会记录本次选择消息队列所在的BrokerName;
总结
在未启用故障延迟机制时,从该消息所属的Topic下的所有消息队列集合中,轮询选择消息队列进行发送,如果上一次选择了某个Broker发送消息,本次将不会再选择这个Broker,当然如果最后仍未找到满足要求的消息队列,将会跳过这个判断,直接从队列中轮询获取消息队列返回。
开启故障延迟机制
在生产者进行发送消息的时候,无论消息是否发送成功与否都会记录向每个Broker的发送消息的条目信息FaultItem
,有一个失败条目表faultItemTable
,faultItemTable
记录了每个Broker对应的失败条目FaultItem
,FaultItem
中主要有以下信息:
- name:Broker的名称;
- currentLatency:延迟时间,可以理解为是本次向该Broker发送消息耗时时间:发送消息结束时间 - 消息发送开始时间;
- startTimestamp:规避故障时间,一般为
当前时间 + 不可用的持续时间
,不可用的持续时间有两种情况,分别为30000ms或者使用currentLatency延迟时间(也就是上次发送消息所用的时间),一般在出现异常的时候,会将不可用的持续时间设置为30000ms,消息正常发送的时候使用currentLatency
延迟时间。
设置规避故障时间主要是为了在某个时间段内规避某个Broker,假设向某个Broker发送失败/或者向此Broker发生消息的耗时比较长,生产者认为此Broker可能暂时处于异常状态/或者该时间段内此Broker的性能不高,在下次发送消息时尽量规避这个Broker,避免向此Broker上投递消息。
每次消息发送之后会更新该Broker的失败条目的处理逻辑如下:
- 根据Broker名称从
faultItemTable
获取对应的FaultItem
对象; - 如果上一步获取为空,说明之前没有记录过该Broker的信息,需要新建对应
FaultItem
对象,此时需要设置name、currentLatency延迟时间、startTimestamp规避故障时间; - 如果第1步中获取到该Broker对应的
FaultItem
对象,直接更新里面的currentLatency延迟时间、startTimestamp规避故障时间即可;
接下来看如何使用FaultItem
中记录的信息,来实现故障规避。
使用故障规避,需要启用故障延迟机制,此时从队列集合中选择消息队列的处理逻辑如下:
对计数器增一;
根据计数器的值对消息队列列表的长度取余得到下标值
pos
,从队列列表中获取pos
位置的元素,依旧轮询从消息队列列表中选择消息队列,这两步与未开启故障时逻辑一致;选择出消息队列之后,会获取该队列所在的Broker名称,上面说到,生产者每次与Broker通信发送消息时,会记录消息发发送情况,此时可以根据Broker的名称,从失败条目表
faultItemTable
中获取该Broker的FaultItem
,用来判断当前选择的消息队列是否可用,FaultItem
中有一个规避故障时间,来看两种情况:- 情况一:上次向此Broker发送消息失败,那么这个时间的值为
发送消息失败时的时间 + 30000ms
,判断当前时间有没有超过故障规避设置的时间,如果超过了当前选择的消息队列可用,那么就会返回当前选择的这个消息队列,如果未超过表示该Broker暂时不可用所以不能使用当前选择的消息队列,需要回到第1步继续选择下一个队列; - 情况二:上次向此Broker发送消息成功,那么这个时间的值为
发送消息失败时的时间 + 上次发送消息的耗时时间
,判断当前时间有没有超过故障规避设置的时间,这个依赖于上次发送消息的耗时时间
的长短,如果耗时比较长,可能还未超过规避时间,本次就不能选择向此Broker发送消息同样需要回到第1步选择下一个队列,如果耗时比较短,可能现在已经过了规避时间,那么就可以选择当前的消息队列返回;
- 情况一:上次向此Broker发送消息失败,那么这个时间的值为
如果进行到这一步,以上步骤没有选择到可用的消息队列,此时需要通过以下方式再次选择消息队列:
(1)遍历faultItemTable
失败条目表,将每一个Broker对应的FaultItem加入一个LinkedList
链表;
(2)对链表进行排序,FaultItem
实现Comparable
就是为了在这里进行排序,值小的排在链表前面,值的大小判断规则如下:- 对比是否有超过规避时间的Broker(调用
isAvailable
可以判断),如果有表示值比较小,会排在前面,之后被优先选择,如果所有的Broker都为超过规避时间,进入下一个对比条件; - 对比
currentLatency
的值,值越小排序的时候越靠前,也就是尽量选择发送消息耗时短的那个Broker,如果值相等进入下一个对比条件; - 对比
startTimestamp
的值,同样值越小排序的时候越靠前,尽量选择规避时间较短的那个Broker;
(3)经过以上的规则进行排序后,会根据链表的总大小,计算一个中间值:
- 如果half值小于等于0,取链表中的第一个元素;
- 如果half值大于0,从前half个元素中轮询选择元素;
(4)在链表中越靠前的元素,表示发送消息的延迟越低,在选择时优先级就越高,如果half值小于等于0的时候,取链表中的第一个元素,half值大于0的时候,处于链表前half个的Broker,延迟都是相对较低的,此时轮询从前haft个Broker中选择一个Broker,总之经过这么多处理就是为了选择一个延迟相对较低的Broker;
(5)获取上一步选取到的那个Broker,获取Broker可写的队列数量:
- 如果数量小于0表示该Broker不可用,需要移除然后进入下一步;
- 如果数量大于0,表示该Broker可用,然后重新轮询从消息队列列表中选取一个队列,将本次选取到的消息队列所属的Broker设置为第(4)步中选取到的那个Broker,也就是将这个消息队列及Topic重置到新的Broker中(认为原本所属的Broker不可用,需要设置一个新的Broker),然后返回当前选取的消息队列;
- 对比是否有超过规避时间的Broker(调用
如果经过第4步依旧未选出可用的消息队列,那么就跳过故障延迟机制,直接从该Topic的所有队列中轮询选择一个返回;
总结
故障延迟机制指的是在发送消息时记录每个Broker的耗时时间,如果某个Broker发生故障,但是生产者还未感知(NameServer 30s检测一次心跳,有可能Broker已经发生故障但未到检测时间,所以会有一定的延迟),用耗时时间做为一个故障规避时间(也可以是30000ms),此时消息会发送失败,在重试或者下次选择消息队列的时候,如果在规避时间内,可以避免再次选择到此Broker,以此达到故障规避的目的。
如果某个Topic所在的所有Broker都处于不可用状态,此时尽量选择延迟时间最短、规避时间最短(排序后的失败条目中靠前的元素)的Broker作为此次发送消息消息的Broker。
对应的相关源码可参考:
参考
【RocketMQ】消息的发送的更多相关文章
- rocketMq消息的发送和消息消费
rocketMq消息的发送和消息消费 一.消息推送 public void pushMessage() { String message = "推送消息内容!"; try { De ...
- MQ系列5:RocketMQ消息的发送模式
MQ系列1:消息中间件执行原理 MQ系列2:消息中间件的技术选型 MQ系列3:RocketMQ 架构分析 MQ系列4:NameServer 原理解析 在之前的篇章中,我们学习了RocketMQ的原理, ...
- RocketMQ中Producer消息的发送
上篇博客介绍过Producer的启动,这里涉及到相关内容就不再累赘了 [RocketMQ中Producer的启动源码分析] Producer发送消息,首先需要生成Message实例: public c ...
- RocketMQ之九:RocketMQ消息发送流程解读
在讨论这个问题之前,我们先看一下Client的整体架构. Producer与Consumer类体系 从下图可以看出以下几点:(1)Producer与Consumer的共同逻辑,封装在MQClientI ...
- RabbitMQ,RocketMQ,Kafka 事务性,消息丢失和消息重复发送的处理策略
消息队列常见问题处理 分布式事务 什么是分布式事务 常见的分布式事务解决方案 基于 MQ 实现的分布式事务 本地消息表-最终一致性 MQ事务-最终一致性 RocketMQ中如何处理事务 Kafka中如 ...
- 一张图进阶 RocketMQ - 消息发送
前 言 三此君看了好几本书,看了很多遍源码整理的 一张图进阶 RocketMQ 图片链接,关于 RocketMQ 你只需要记住这张图!觉得不错的话,记得点赞关注哦. [重要]视频在 B 站同步更新,欢 ...
- RocketMQ消息发送流程和高可用设计
(源码阅读先看主线 再看支线 先点到为止 后面再详细分解) 高可用的设计就是:当producer发送消息到broker上,broker却宕机,那下一次发送如何避免发送到这个broker上,就是采用La ...
- RocketMQ源码 — 八、 RocketMQ消息重试
RocketMQ的消息重试包含了producer发送消息的重试和consumer消息消费的重试. producer发送消息重试 producer在发送消息的时候如果发送失败了,RocketMQ会自动重 ...
- 如何在优雅地Spring 中实现消息的发送和消费
本文将对rocktmq-spring-boot的设计实现做一个简单的介绍,读者可以通过本文了解将RocketMQ Client端集成为spring-boot-starter框架的开发细节,然后通过一个 ...
- 程序重启RocketMQ消息重复消费
最近在调试RocketMQ消息发送与消费的Demo时,发现一个问题:只要重启程序,RocketMQ消息就会重复消费. 那么这是什么原因导致的,又该如何解决呢? 经过一番排查,发现程序使用的Rocket ...
随机推荐
- 2022-08-23:以下go语言代码输出什么?A:map[baz:2 foo:0];B:map[bar:1 baz:2];C:map[baz:2];D:不确定。 package main impo
2022-08-23:以下go语言代码输出什么?A:map[baz:2 foo:0]:B:map[bar:1 baz:2]:C:map[baz:2]:D:不确定. package main impor ...
- 2021-05-26:给定一个char[][] matrix
2021-05-26:给定一个char[][] matrix,也就是char类型的二维数组,再给定一个字符串word,可以从任何一个某个位置出发,可以走上下左右,能不能找到word?char[][] ...
- 2022-01-25:序列化和反序列化 N 叉树。 序列化是指将一个数据结构转化为位序列的过程,因此可以将其存储在文件中或内存缓冲区中,以便稍后在相同或不同的计算机环境中恢复结构。 设计一个序列化和反
2022-01-25:序列化和反序列化 N 叉树. 序列化是指将一个数据结构转化为位序列的过程,因此可以将其存储在文件中或内存缓冲区中,以便稍后在相同或不同的计算机环境中恢复结构. 设计一个序列化和反 ...
- 2021-11-05:摆动排序 II。给你一个整数数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]... 的顺序。你可以假设所有输入数组都可以
2021-11-05:摆动排序 II.给你一个整数数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]- 的顺序.你可以假设所有 ...
- 2021-10-04:解码方法 II。‘A‘ -> 1,‘B‘ -> 2,...‘Z‘ -> 26。*是1-9,不包含0。给你一个字符串 s ,由数字和 ‘*‘ 字符组成,返回 解码 该字符串的方法
2021-10-04:解码方法 II.'A' -> 1,'B' -> 2,-'Z' -> 26.是1-9,不包含0.给你一个字符串 s ,由数字和 '' 字符组成,返回 解码 该字符 ...
- ping不通能curl通
今天发现一个域名或ip居然在ping不通的情况下能curl通,以前的思维定式直接给整破防了啊!!! 涨见识了,具体原因和原理后续补充~
- PHP反序列化字符逃逸 学习记录
PHP反序列化字符逃逸的原理 当开发者使用先将对象序列化,然后将对象中的字符进行过滤, 最后再进行反序列化.这个时候就有可能会产生PHP反序列化字符逃逸的漏洞. 详解PHP反序列化字符逃逸 过滤后字符 ...
- uniapp主题切换功能的第二种实现方式(scss变量+require)
在上一篇 "uniapp主题切换功能的第一种实现方式(scss变量+vuex)" 中介绍了第一种如何切换主题,但我们总结出一些不好的地方,例如扩展性不强,维护起来也困难等等,那么接 ...
- CANoe学习笔记(六):如何实现LIN和CAN的多帧传输-----LIN
内容: 1.实现LIN的多帧传输 一.新建一个基于LIN的CANoe工程 二.接下来创建一些工程用得上的变量.文件: 2.1 LDF文件: 这部分注意:包含三个调度表,①3C诊断请求帧②3D诊断响应帧 ...
- C++ 学习笔记 (一)
C++标准化组织 https://isocpp.org/std/status http://open-std.org/JTC1/SC22/WG21/ why C++王者归来? https://cool ...