前几天看了公司封装的MQ中间件的一些设计文档,其中提到了实现的事务消息,具体实现原理和RocketMQ很相似,看完之后我有一些困惑,RocketMQ中的事务消息实现是真正的事务么?
事务
事务( transaction)是访问并可能操作各种数据项的一个操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
事务具有四大特性: ACID 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
事务中最重要的就是原子性,也是事务最具代表性的特点,在数据库中它是通过UndoLog来完成的,某一事务其中一部分没有执行完成,就会直接将数据库回滚至undoLog中记录的版本,当作无事发生。
大概了解事务的含义,我们可以看一下kafka和RocketMq中的事务消息
Kafka
Kafka 事务消息是用在一次事务中需要发送多个消息的情况,保证多个消息之间的事务约束,即多条消息要么都发送成功,要么都发送失败。 Kafka 的事务有事务协调者角色,事务协调者其实就是 Broker 的一部分。
在开始事务的时候,生产者会向事务协调者发起请求表示事务开启,事务协调者会将这个消息记录到特殊的日志-事务日志中,然后生产者再发送真正想要发送的消息,这里 Kafka 和 RocketMQ 处理不一样,Kafka 会像对待正常消息一样处理这些事务消息,由消费端来过滤这个消息。
然后发送完毕之后生产者会向事务协调者发送提交或者回滚请求,由事务协调者来进行两阶段提交,如果是提交那么会先执行预提交,即把事务的状态置为预提交然后写入事务日志,然后再向所有事务有关的分区写入一条类似事务结束的消息,这样消费端消费到这个消息的时候就知道事务好了,可以把消息放出来了。
最后协调者会向事务日志中再记一条事务结束信息,至此 Kafka 事务就完成了  可以看到,kafka的事务或许不是我们想象中的事务,可以看下RocketMq中的事务消息。 它解决的是本地事务的执行和发消息这两个动作满足事务的约束,应该更符合我们对事务消息的需求
rocketMq
可以看下rocketMq中的设计方案 
上图说明了事务消息的大致方案,其中分为两个流程:正常事务消息的发送及提交、事务消息的补偿流程。
1.事务消息发送及提交:
(1) 发送消息(half消息)。
(2) 服务端响应消息写入结果。
(3) 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)。
(4) 根据本地事务状态执行Commit或者Rollback(Commit操作生成消息索引,消息对消费者可见)
2.补偿流程:
(1) 对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
(2) Producer收到回查消息,检查回查消息对应的本地事务的状态
(3) 根据本地事务状态,重新Commit或者Rollback
其中,补偿阶段用于解决消息Commit或者Rollback发生超时或者失败的情况。
可以看到,其实RocketMq中的事务可以保证,当消费者消费到事务消息时,本地事务是一定执行结束的,其中的回滚机制,其实就是删除消息,使其无法被消费。
也就是说本地事务是没有回滚机制的,当消息发送失败时,mq只能不断尝试
|