rocketmq事务消息源码解析

开篇

rocketmq 事务消息是常用的一种实现分布式事务弱一致性的技术方案,以rocketmq broker为事务管理器的角色,以两阶段的方式来做事务管理,本文默认你已经熟悉rocketmq的使用,我们一起做更深入的源码探讨。

我们为什么要用它

大部分技术或者说从我们现在可以接触到的来说,还都是在别人已经走的路上,既然是路那么一定有一个方向和目的,我们的目的就是为了解决分布式环境中的分布式事务问题,随着分布式的普及,系统支持横行扩容基本成为系统的基本属性,高的扩展性是我们所必须的,但是他也会带来一些问题,就是以前常用的一些的基本操作,事务和线程同步,都不在有效,我们必须也要寻找一种更高一层纬度的实现,当然这其中也设计到一个cap理论,我们的事务消息也就是一种分布式事务的实现方案

交互流程

这里只做大概的说明,他的主要思路就是大事务 = 小事务 + 异步,中间在加一些保证机制,保证这些小事务都可以成功,如果不可以成功就一起失败,主要交互如下
在这里插入图片描述
可以看出他主要是通过,mq自己回查判断,上游事务是否执行完成,来判断要不要发送消息触发下游事务,而下游事务主要通过rocketmq的对于事务可达性的保证来,不断重复调用,在一定配置内,直到下游事务返回事务执行成功。可以看出主要工作都是rocketmq来做的,由他来确保上游是否执行成功,在由他来确保下游一定执行成功,可以看出这种方式,不会对上游进行回滚,因此适合有主从概念的场景,比如支付和发短信。

说之前要了解的基础

在了解这三大步骤之前我们要知道一些基础的类

message

我们消息传递的消息体,这个是最基本的,在rocketmq中,主要有一下分类
在这里插入图片描述
他们的类关系图如下
在这里插入图片描述
可以看出message就是最基本的一个消息的定义,真正我们在broker中使用的是messageExt,它里面包含了几乎全部我们broker要处理和判断的属性,而MessageExtBrokerInner 只是messageExt的序列化实现,主要包含一个字符串,存储我们的全部属性,方便我们消息存入文件。

messageExt

我们重点说明一下messageExt这个类中属性,如下
在这里插入图片描述
最后一个属性补充如下,preparedTransactionOffset ,就是事务消息中的位点大小,可以看出一条消息独自存储了,所有自己的相关属性

三个队列

我们要知道message在broker中存储的方式,和mq(消息队列)对于消息队列的定义方式,只有了解这些我们才方便了解他是怎么处理事务消息的,三个队列如下
三个队列

commitLog rocketmq存储消息的文件

这就是一个二进制的序列化后的文件,保存了全部的消息,通常是1G一个文件,这个大小可以自己自由配置,下面简单的放一下结构,了解就可以
在这里插入图片描述
其实我们也可以看到和我们messageExt对象可以对应的起来,他的每一个字节大小都是有自己独特的规定的,不能乱,一旦乱了,会导致整个commitLog文件损坏不可以读,物理存储如下
在这里插入图片描述

consumeQueue 存储的是commitLog的索引,代表的是一个单独的队列

我们的topic很多,但是现在消息都实际存储在commitLog中,不可能所有消息当要读取的时候,都要扫描我们的commitLog,来找到自己的topic存储的消息,所以我们多来一个consumeQueue,单独存取我们同一个topic下的消息,我们只需要扫描consumeQueue来就可以找到我们的同一个topic下的消息,但是一个消息队列,也就是一个topic也有大量的读取和写入,如果只有一个文件的话,也会有并发问题,所以我们在对consumeQueue 进行拆分,牺牲全局顺序性,来满足消息队列最重要的高性能,这样我们就支持进一步支持来大的并发。物理存储如下
在这里插入图片描述
下面在展示一下他的存储结构
在这里插入图片描述
以上就是rocketMq的两个物理存储文件

messageQueue 消息的逻辑队列

messageQueue是在内存中的,前面我们了解了,我们已经在物理方面对mq的文件存储性能进行来扩展,让他可以最大限度的支持多线程的读写,那么我们怎么设计我们多个消费者组,或者同一个消费者组内部多个消费者对于同一个topic消费呢,答案就是引入逻辑队列messageQueue。
** 注意 在集群模式下,一个messageQueue 只对应一个consumer**
通过上面的说明我们知道,一个topic在一个broker上去会有多个 物理上consumeQueue,consumeQueue就是一排消息索引,没有什么状态,我们要消费一条消息,或者说找到一条消息,就要知道这个消息是在哪一个consumeQueue上哪一个offset,从这里我们看出来 consumeQueue 设计出来是为了保存消息的,那他怎么处理当我有多个不同的consumeGroup的consumer来消费同一个consumeQueue,保证每一个consumer都可以消费自己的消息,那怕consumeQueue 内部consumer切换,也可以做到不乱消费呢,答案就是用messageQueue。messageQueue会为每一个来消费的consumeGroup保存一个 offset,记录他的consumeGroup在这个consumeQueue上消费到一个位置,这样就可以让每个人都以自己的offset来消费,但是队列都是相同的。
下面我们来看看messageQueue的结构,和他怎么管理不同consumer的offset
在这里插入图片描述

两个特殊的topic

在事务消息中会涉及到两个特殊的topic,我们会简单的说说他

RMQ_SYS_TRANS_OP_HALF_TOPIC(op队列)

这个是事务消息本地事务完成确认队列,所有收到的事务消息本地事务最终状态都会新建一个body为messageExt.getQueueOffset(),topic为RMQ_SYS_TRANS_OP_HALF_TOPIC的消息存下来

RMQ_SYS_TRANS_HALF_TOPIC(half队列)

这个是事务消息待确认消息队列,主要是所有收到的事务消息预提交,就是还没有确认本地事务执行结果的消息都会把他的topic改为他,在保存起来

源码分析

下面我们开始源码分析,从上面可以看出rocketmq 主要有三大交互

  1. producer 发送事务消息给 broker
  2. broker 发送消息给 producer,询问本地事务是否执行成功
  3. producer 告知broker本地事务执行结果,这里包含两个触发
    1. 本地事务支持完成,自动发送给broker
    2. 收到broker的询问,执行ckeck方法,告知本地事务执行结果

第一个流程: producer 发送事务消息给 broker

这个流程主要是,broker接受消息时会判断是不是事务消息,如果是的话,就复制一条消息,修改这条消息的topic为RMQ_SYS_TRANS_OP_HALF_TOPIC,在把原来的topic放入我们的message属性集中主要流程如下
在这里插入图片描述
上面就是我一步一步对着源码花的流程图,创作不易,请给我点赞和评论呀

第二个流程: broker 发送消息给 producer,询问本地事务是否执行成功

这个主要是内部job触发一分钟一次,扫描和比对两个队列(op和half队列),来判断那些消息还没有收到客户端的本地事务消息确认回答,找到了就把这一批汇总发送给客户端,进行确认

定时任务

broker内部有一个定时任务框架 serviceThread,主要是用thread和CountDownLatch2来实现的,事务消息check是1分钟执行一次,代码如下
在这里插入图片描述
可以看出就是执行前打一个标志,然后不停的等,等待超时后在重新执行

主要流程

在这里插入图片描述
上面就是我画的流程图,创作不易,各位点赞呀,可以对比这源码一步一步的来看,这个是最复杂的一个流程,它复杂在主要是对一个消息是否触发回调check的判断,包含了很多纬度

  1. 消息回调的次数
  2. 消息已经存活的时间
  3. 本地事务消息设置的超时时间
  4. 全局事务消息设置的超时时间
  5. 服务器双方的时间
    等还有一些很细节的都标注在图了,这一块一定到细心

第三个流程: producer 告知broker本地事务执行结果,这里包含两个触发

这个结合这前面两个看就不要简单了,他就是当收到producer发送过来的本地事务消息确认时,把消息放到op队列中就没了,流程如下
在这里插入图片描述

总结

基本上上面就是一次事务消息broker的整个处理流程,如果自己在生产中有问题,可以对着图来进行比对,也可以联系我,加我微信,我们一起分析,克服问题共同成长,可以的话,求打赏

在这里插入图片描述
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章