RocketMQ

Posted on Fri, Jun 24, 2022 learning

Time @2022/06/23

使用消息队列的好处:

采用了发布订阅模型:

RocketMQ中的消息模型:

RocketMQ中的基本概念:

四个角色在RockerMQ中的架构如图所示。

顺序消费

重复消费

分布式事务

事务要么都执行,要么都不执行。

分布式事务要解决事务在不同服务之间的问题。

在RocketMQ中采用了 事务消息+事务反查机制 来解决分布式事务问题。

以此图为例说明:

1.实线1向MQ发送prepare消息(此消息对消费者不可见,因此不会被消费),然后再执行producer本地事务。(只有prepare消息发送成功,本地事务才会执行)。

2.执行producer回调函数executeLocalTransaction(),将本地事务执行状态返回给broker。

本地事务状态:

COMMIT_MESSAGE:将不可见消息变为可见

ROLLBACK_MESSAGE:broker回收步骤1中的不可见消息

UNKNOW或者网络异常:broker调用producer中的回调函数checkLocalTransaction()判断producer本地事务是否正常(一般是通过查询数据库中的数据是否已经持久化)

3.消费者消费消息

4.如果有消费者消费失败,则将失败的消息传给broker,即重新写入commitLog,消费者将重新消费;如果此时consumer和broker连接断开,consumer调用submitConsumeRequestLater()方法,在consumer处重新消费,16次之后,则必须通过工单、日志等方式进行人工干预使producer事务进行回退。如果此时consumer和broker网络断开,consumer会将该消息存入失败队列,并重新消费失败队列中的消息,并且继续调用submitConsumeRequestLater()。

消息堆积问题

生产者生产太快:在生产者部分使用限流降级的方法。

消费者消费太慢:

刷盘策略

如上图所示,同步刷盘小等待一个刷盘成功的ACK,同步刷盘对MQ消息可靠性是很好的保障,因此在性能上会有较大影响,一般适用于金融等特定业务场景。

异步刷盘则是开启一个线程异步执行刷盘操作,采用后台异步线程提交的方式进行,降低了读写延迟,提高了MQ的性能和吞吐量,适用于发验证码等对于消息保证要求不高的业务场景。只有在broker宕机的时候会丢失部分数据。

RocketMQ默认采用异步刷盘策略

同步复制和异步复制

这里指broker中的主从同步。

同步复制:只有消息同步双写到主从节点上时才返回写入成功。

异步复制:消息写入主节点后即返回写入成功。

异步复制时,当主节点挂掉,消费者会从从节点进行消费(producer不能再生产消息了),待主节点重启后,从节点会继续复制主从节点不同步的部分。

使用多主从设置(同一个topic可以分布在不同的broker中,设置多主从,一个broker的主节点挂掉,部署在其他broker的主节点依旧可用),可以保证producer的生产能够正常进行。但是这种方式又无法保证顺序(如之前的部分所述,为保证消息的顺序性,我们通常将一类语义信息发送到同一个队列,举个例子,主节点A负责订单A类,其他节点是无法代替主节点A,设置多主节点A1、A2、A3的话,订单A类的消费顺序就无法得到保证)。

RocketMQ中采用的Dledger解决了这个问题:在写入消息过程中,要求消息最少复制到半数以上节点(会牺牲一些效率),才给producer返回写入成功,并且它支持通过选举切换主节点。

存储机制

RocketMQ采用了混合型存储结构,broker单个实例下所有的队列共用一个日志数据文件来存储消息。

Kafka则为每个Topic分配一个存储文件。

两者一个把东西赛一个大房间,另一个把东西塞到不同功能的小房间。

显而易见,前者数据写入效率更高,但也会在读取时遍历整个房间。

然而RocketMQ引入了consumerQueue作为每个队列的索引文件夹,来提升消息的读取效率,可以根据句队列的消息序号,计算出索引的全局位置(索引序号*索引固定长度20),然后直接读取这条索引,再根据索引中记录的消息的全局位置,找到消息。