-
消息详解 -
一、概述 在 《柔性事务之TCC详解》 和《柔性事务之Saga详解》两文中我们详细剖析了柔性事务的第一个分支补偿型事务。在《刚性事务总结和柔性事务概述》中我们介绍过的柔性事务包含补偿型事务和通知型事务。
通知型事务主要包含事务消息和最大努力通知型分布式事务两个组成。通知型事务的核心思想是通过MQ来通知其他事务参与者自己事务的执行状态。MQ组件的引入有效的将事务参与者解耦开,各个参与者都可以异步执行,所以通知型事务又称为异步事务。
事务消息的难度在于服务本地事务和投递消息的一致性保障。目前业界解决这个一致性的方案有两个分支:
基于MQ自身的事务消息方案 基于DB的本地消息表方案
-
基于MQ自身的事务消息方案 -
基于MQ的事务消息方案主要依靠MQ的半消息机制来实现投递消息和参与者自身本地事务的一致性保障。半消息机制实现原理其实借鉴的2PC的思路,是二阶段提交的广义拓展,流程图如下:
1、事务发起方首先发送prepare消息到MQ;
2、在发送prepare消息成功后执行本地事务;
3、根据本地事务执行结果返回commit或者是rollback;
4、如果消息是rollback, MQ将删除该prepare消息不进行下发,如果是commit消息,MQ将会消息发送给consumer端;
5、如果执行本地事务过程中,执行端挂掉,或者超时,MQ服务器端将不停的询问producer来获取事务状态;
6、Consumer端的消费成功机制有MQ保证
MQ事务消息方案因为使用了半消息机制,对业务页具有比较大侵入性,有以下注意点:
1、 业务方调用半消息,并提供对应的回查方法;
2、 MQ提要提供半消息机制,并定期扫描长期半消息,对消息生产者进行回查确认事务。
3、 消费方需要进行幂等消费。
-
基于BD的本地消息表方案 -
有时候我们目前的MQ组件并不支持事务消息,或者我们想尽量少的侵入业务方。这时我们需要另外一种方案“基于DB本地消息表“,流程图如下:
1、 业务方:直接利用本地事务,将业务数据和事务消息直接写入数据库。
2、 投递线程:使用专门的投递工作线程进行事务消息投递到MQ,根据投递ACK去删除事务消息表记录
本地事务消息表的优势在于方案的通用性,无需提供回查方法,进一步减少的业务的侵入。在某些场景下,还可以进一步利用注解等形式进行解耦,有可能实现无业务代码侵入式的实现。我们上面说了本地事务消息表的基本理论,那么如果要设计一个高可用的企业级本地事务消息表方案,就要考虑更多的事情,在性能上做更大的优化,降低更多的重复投递率。以下是一个企业级事务消息的设计流程图:
1、 事务消息服务:提供通用投递接口,用于保证事务消息的本地写入,并将事务消息写入事务内存队列。
2、 使用投递线程池,继续事务内存队列投递派发分配。投递工作线程只投递本实例拥有的事务消息,投递失败线程列入时间轮队列;重试机制使用失败挡位区分,默认提供6档:5s、10s、15s、20s、25s、30s。
3、 时间轮线程进行60秒转动,将到期的失败事务消息重入事务内存队列.
4、 因为我们的事务消息服务是无状态化的多实例存在,所以需要一个持锁线程进行主节点竞争强锁,处理一些额外的工作。
5、 因为我们的事务内存队列是内存级,不可避免面临重启等情况下的数据丢失。这时需要事务消息服务主节点进行定期扫表,将长期未投递的事务消息取出放入事务消息服务。
6、 事务消息服务主节点还有一个清理线程,专门用于将已处理成功的历史事务消息进行归档清理,降低DB的数据量。
-
总结 -
咱们上面介绍了MQ事务消息方案和DB本地消息表方案,这两个方案有什么区别呢?
1、 MQ事务消息方案
a) 需要MQ支持半消息机制或者类似特性,在重复投递上具有比较好的去重处理
b) 需要业务方进行改造,提供对应的本地操作成功回查功能。具有比较大的业务侵入性。
2、 DB本地事务消息表方案
a) 使用了数据库来存储事务消息,降低了对MQ的需求,但是增加了存储成本。
b) 事务消息使用了异步投递,增大了消息重复投递的可能性。
我们说了两种事务消息的特性和优劣性,我们在总结下事务消息的共性。
1、 事务消息都依赖MQ进行事务通知,所以都是异步的。
2、 事务消息在投递方都是存在重复投递的可能,需要有配套的机制去降低重复投递率,实现更友好的消息投递去重。
3、 事务消息的消费方,因为投递重复的无法避免,因此需要进行消费去重设计或者服务幂等设计。 本文来源于:奈学开发者社区