RocketMQ实现事务消息
在RocketMQ4.3.0版本后,开放了事务消息这一特性,对于分布式事务而言,最常说的还是二阶段提交协议,那么RocketMQ的事务消息又是怎么一回事呢,这里主要带着以下几个问题来探究一下RocketMQ的事务消息:
事务消息是如何实现的
我们有哪些手段来监控事务消息的状态
事务消息的异常恢复机制
RocketMQ的事务消息是如何实现的
RocketMQ作为一款消息中间件,主要作用就是帮助各个系统进行业务解耦,以及对消息流量有削峰填谷的作用,而对于事务消息,主要是通过消息的异步处理,可以保证本地事务和消息发送同时成功执行或失败,从而保证数据的最终一致性,
事务消息从诞生到结束的整个时间线流程:
生产者发送消息到broker,该消息是prepare消息,且事务消息的发送是同步发送的方式。
broker接收到消息后,会将该消息进行转换,所有的事务消息统一写入Half Topic,该Topic默认是RMQ_SYS_TRANS_HALF_TOPIC ,写入成功后会给生产者返回成功状态。
本地生产获取到该消息的事务Id,进行本地事务处理。
本地事务执行成功提交Commit,失败则提交Rollback,超时提交或提交Unknow状态则会触发broker的事务回查。
若提交了Commit或Rollback状态,Broker则会将该消息写入到Op Topic,该Topic默认是RMQ_SYS_TRANS_OP_HALF_TOPIC,该Topic的作用主要记录已经Commit或Rollback的prepare消息,Broker利用Half Topic和Op Topic计算出需要回查的事务消息。如果是commit消息,broker还会将消息从Half取出来存储到真正的Topic里,从而消费者可以正常进行消费,如果是Rollback则不进行其他操作
如果本地事务执行超时或返回了Unknow状态,则broker会进行事务回查。若生产者执行本地事务超过6s则进行第一次事务回查,总共回查15次,后续回查间隔时间是60s,broker在每次回查时会将消息再在Half Topic写一次。回查次数和时间间隔都是可配置的。
执行事务回查时,生产者可以获取到事务Id,检查该事务在本地执行情况,返回状态同第一次执行本地事务一样。
从上述流程可以看到事务消息其实只是保证了生产者发送消息成功与本地执行事务的成功的一致性,消费者在消费事务消息时,broker处理事务消息的消费与普通消息是一样的,若消费不成功,则broker会重复投递该消息16次,若仍然不成功则需要人工介入。
事务消息的成功投递是需要经历三个Topic的
Half Topic:用于记录所有的prepare消息
Op Half Topic:记录已经提交了状态的prepare消息
Real Topic:事务消息真正的Topic,在Commit后会才会将消息写入该Topic,从而进行消息的投递
理解清楚事务消息在这三个Topic的流转就基本理解清楚了RocketMQ的事务消息的处理。接下来我们看看在源码中是如何使用这三个Topic的。
事务消息是如何处理回查的
在RocketMQ中,消息都是顺序写随机读的,以offset来记录消息的存储位置与消费位置,所以对于事务消息的prepare消息来说,不可能做到物理删除,broker启动时每间隔60s会开始检查一下有哪些prepare消息需要回查,从上面的分析我们知道,所有prepare消息都存储在Half Topic中,那么如何从该Topic中取出需要回查的消息进行回查呢?这就需要Op Half Topic以及一个内部的消费进度计算出需要回查的prepare消息进行回查:
Half Topic 默认Topic是RMQ_SYS_TRANS_HALF_TOPIC,建一个队列,存储所有的prepare消息
Op Half Topic默认是RMQ_SYS_TRANS_OP_HALF_TOPIC,建立的对列数与Half Topic相同,存储所有已经确定状态的prepare消息(rollback与commit状态),消息内容是该条消息在Half Topic的Offset
Half Topic消费进度,默认消费者是CID_RMQ_SYS_TRANS,每次取prepare消息判断回查时,从该消费进度开始依次获取消息。
Op Half Topic消费进度,默认消费者是CID_RMQ_SYS_TRANS,每次获取prepare消息都需要判断是否在Op Topic中已存在该消息了,若存在表示该prepare消息已结束流程,不需要再进行事务回查,每次判断都是从Op Topic中获取一定消息数量出来进行对比的,获取的消息就是从Op Topic中该消费进度开始获取的,最大一次获取32条。
获取Half Topic的所有队列,循环队列开始检测需要获取的prepare消息,实际上Half Topic只有一个队列。
获取Half Topic与Op Half Topic的消费进度。
调用fillOpRemoveMap方法,获取Op Half Topic中已完成的prepare事务消息。
从Half Topic中当前消费进度依次获取消息,与第3步获取的已结束的prepare消息进行对比,判断是否进行回查:
如果Op消息中包含该消息,则不进行回查,
如果不包含,获取Half Topic中的该消息,判断写入时间是否符合回查条件,若是新消息则不处理下次处理,并将消息重新写入Half Topic,判断回查次数是否小于15次,写入时间是否小于72h,如果不满足就丢弃消息,若满足则更新回查次数,并将消息重新写入Half Topic并进行事务回查,
在循环完后重新更新Half Topic与Op Half Topic中的消费进度,下次判断回查逻辑时,将从最新的消费进度获取信息。
生产客户端的ClientRemotingProcessor的processRequest方法会处理服务端的CHECK_TRANSACTION_STATE请求,最后会调用checkLocalTransactionState方法,该方法就是业务方可以自己实现事务消息回查逻辑的地方,并将结果最后用endTransactionOneway方法返回给Broker,该执行逻辑可以通过ClientRemotingProcessor的方法processRequest依次理解就可以了。
我们有哪些手段来监控事务消息的状态
事务消息主要有三个状态:
UNKNOW状态:表示事务消息未确定,可能是业务方执行本地事务逻辑时间耗时过长或者网络原因等引起的,该状态会导致broker对事务消息进行回查,默认回查总次数是15次,第一次回查间隔时间是6s,后续每次间隔60s,
ROLLBACK状态,该状态表示该事务消息被回滚,因为本地事务逻辑执行失败导致
COMMIT状态,表示事务消息被提交,会被正确分发给消费者。
那么监控事务消息时,主要是查看该事务消息是否是处于我们想要的状态,而在事务消息生产者发送prepare消息成功后只能拿到一个transactionId,该id不是的RocketMQ消息存储的物理offset地址,RocketMQ只有在准备写入commitlog文件时才会生成真正的msgId,而这里可以获取的transactionId和msgId都是客户端生成的一个消息的唯一标识符,我们在这里称为uniqId,在broker端,会把该uniqId作为一个msgKey写入消息,所以可以通过该uniqId来查找uniqId的一些状态:
通过DefaultMQAdminExt的viewMessage(String topic, String msgId)方法可以消息的信息,这里topic参数是RMQ_SYS_TRANS_HALF_TOPIC ,该topic是真正的Half Topic,msgId传发送prepare消息获取的uniqId,这样可以获取prepare消息在Half Topic真正的offsetMsgId,
通过第一步获取的offsetMsgId继续调用viewMessage(String topic, String msgId)方法,但是topic是RMQ_SYS_TRANS_OP_HALF_TOPIC,这样可以获取Op Half Topic中该事务消息的状态,如果存在说明prepare消息已处理,否则可能仍在回查中或已被丢弃
如果在第二步查到了信息可以用uniqId和事务消息真正Topic继续调用viewMessage(String topic, String msgId)方法获取消息真正的信息,如果存在说明消息已被投递,否则该事务消息已被回滚。只通过Op Half Topic是不能确定消息状态的,这里的sysFlag被设置0,sysFlag是用于确定事务消息状态。
通过上述三步就可以确定事务消息的状态。
事务消息的异常恢复机制
事务消息的异常状态主要有:
生产者提交prepare消息到broker成功,但是当前生产者实例宕机了
生产者提交prepare消息到broker失败,可能是因为提交的broker已宕机
生产者提交prepare消息到broker成功,执行本地事务逻辑成功,但是broker宕机了未确定事务状态
生产提交prepare消息到broker成功,但是在进行事务回查的过程中broker宕机了,未确定事务状态
异常解决:
对于1:事务消息会根据producerGroup搜寻其他的生产者实例进行回查,所以transactionId务必保存在中央存储中,并且事务消息的pid不能跟其他消息的pid混用。
对于2:当前实例会搜寻其他的可用的broker-master进行提交,因为只有提交prepare消息后才会执行本地事务,所以没有影响,注意生产者报的是超时异常时,是不会进行重发的。
对于3:因为返回状态是oneway方式,此时如果消费者未收到消息,需要用手段确定该事务消息的状态,尽快将broker重启,broker重启后会通过回查完成事务消息。
对于4:同3,尽快重启broker。
---------------------
转自:https://blog.csdn.net/qq_28632173/article/details/83790243
RocketMQ实现事务消息的更多相关文章
- RocketMQ支持事务消息机制
事务消费 我们经常支付宝转账余额宝,这是日常生活的一件普通小事,但是我们思考支付宝扣除转账的钱之后,如果系统挂掉怎么办,这时余额宝账户并没有增加相应的金额,数据就会出现不一致状况了. 上述场景在各个类 ...
- 分布式事务之如何基于RocketMQ的事务消息特性实现分布式系统的最终一致性?
导读 在之前的文章中我们介绍了如何基于RocketMQ搭建生产级消息集群,以及2PC.3PC和TCC等与分布式事务相关的基本概念(没有读过的读者详见
- RocketMQ源码分析之RocketMQ事务消息实现原理上篇(二阶段提交)
在阅读本文前,若您对RocketMQ技术感兴趣,请加入 RocketMQ技术交流群 根据上文的描述,发送事务消息的入口为: TransactionMQProducer#sendMessageInTra ...
- RocketMQ事务消息实现分析
这周RocketMQ发布了4.3.0版本,New Feature中最受关注的一点就是支持了事务消息: 今天花了点时间看了下具体的实现内容,下面是简单的总结. RocketMQ事务消息概要 通过冯嘉发布 ...
- 分布式开放消息系统RocketMQ的原理与实践(消息的顺序问题、重复问题、可靠消息/事务消息)
备注:1.如果您此前未接触过RocketMQ,请先阅读附录部分,以便了解RocketMQ的整体架构和相关术语2.文中的MQServer与Broker表示同一概念 分布式消息系统作为实现分布式系统可扩展 ...
- RocketMQ源码分析之RocketMQ事务消息实现原理中篇----事务消息状态回查
上节已经梳理了RocketMQ发送事务消息的流程(基于二阶段提交),本节将继续深入学习事务状态消息回查,我们知道,第一次提交到消息服务器时消息的主题被替换为RMQ_SYS_TRANS_HALF_TOP ...
- RocketMQ系列(七)事务消息(数据库|最终一致性)
终于到了今天了,终于要讲RocketMQ最牛X的功能了,那就是事务消息.为什么事务消息被吹的比较热呢?近几年微服务大行其道,整个系统被切成了多个服务,每个服务掌管着一个数据库.那么多个数据库之间的数据 ...
- 消息队列之事务消息,RocketMQ 和 Kafka 是如何做的?
每个时代,都不会亏待会学习的人. 大家好,我是 yes. 今天我们来谈一谈消息队列的事务消息,一说起事务相信大家都不陌生,脑海里蹦出来的就是 ACID. 通常我们理解的事务就是为了一些更新操作要么都成 ...
- RocketMQ源码 — 十一、 RocketMQ事务消息
分布式事务是一个复杂的问题,rmq实现了事务的最终一致性,rmq保证本地事务成功消息一定会发送成功并被成功消费,如果本地事务失败了,消息不会被发送. rmq事务消息的实现过程为: producer发送 ...
随机推荐
- Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/hadoop/fs/CanUnbuffer
在执行spark on hive 的时候在 sql.show()处报错 : Exception in thread "main" java.lang.NoClassDefFoun ...
- Andorid 刷新样式一
一.Gradle中的Build.gradle依赖项目 compile 'com.github.moduth:blockcanary-android:1.1.0' debugCompile 'com.s ...
- html 获取数据并发送给后端方式
一.方式一 使用ajax提交 function detailed() { var date = $("#asset_ip").text() $.ajax({ url: " ...
- python 正则表达式中反斜杠(\)的麻烦和陷阱
这里是一点小心得:由于下面两个原因,在正则表达式中使用反斜杠就会产生了一个双重转换的问题. (1).python自身处理字符串时,反斜杠是用于转义字符 (2).正则表达式也使用反斜杠来转义字符 ...
- Caused by:org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type "" available: expected at least 1 bean which qualifies as autowire candidate
项目使用spring, mybatis.因为分了多个模块,所以会这个模块引用了其它模块的现在,结果使用Junit测试的时候发现有两个模块不能自动注入dao和service问题.解决后在此记录一下. 解 ...
- Python笔记(十五):匿名函数和@property
(一)匿名函数 不想显式定义函数的时候,可以使用匿名函数. def f(x): return x*x #将匿名函数赋值给一个变量 result = lambda x:x*x print(result( ...
- python使用sax实现xml解析
之前在使用xml解析的时候,在网上搜了很多教程,最终没有能按照网上的教程实现需求. 所以呢,只好自己去看源码,在sax的__init__.py下看到这么一段代码: 1 def parse(source ...
- Windows服务器防火墙配置规范
本文属于一篇内部规范文档,整理的初衷是为了规范.统一集团的Windows服务器(仅仅SQL Server数据库服务器)防火墙设置,仅仅供内部其它同事设置Windows防火墙时作为参考的文档资料.如有不 ...
- java----java垃圾回收算法
1.引用计数法(Reference Counting Collector) 1.1算法分析 引用计数是垃圾收集器中的早期策略.在这种方法中,堆中每个对象实例都有一个引用计数.当一个对象被创建时,且将该 ...
- 预热一下吧《实现Redis消息队列》
应用场景 为什么要用redis?二进制存储.java序列化传输.IO连接数高.连接频繁 一.序列化 这里编写了一个java序列化的工具,主要是将对象转化为byte数组,和根据byte数组反序列化成ja ...