1+ ## 简介
2+
3+ ### 事务
4+
5+ 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。
6+
7+ ### 分布式事务
8+
19分布式事务是指事务的参与者,支持事务的服务器,资源服务器以及事务管理器分别位于分布式系统的不同节点之上。通常一个分布式事务中会涉及对多个数据源或业务系统的操作。分布式事务也可以被定义为一种嵌套型的事务,同时也就具有了ACID事务的特性。
210
3- ## CAP理论
11+ ### 强一致性、弱一致性、最终一致性
12+
13+ ** 强一致性**
14+
15+ 任何一次读都能读到某个数据的最近一次写的数据。系统中的所有进程,看到的操作顺序,都和全局时钟下的顺序一致。简言之,在任意时刻,所有节点中的数据是一样的。
16+
17+ ** 弱一致性**
418
5- Consistency(一致性):数据一致更新,所有数据变动都是同步的(强一致性) 。
19+ 数据更新后,如果能容忍后续的访问只能访问到部分或者全部访问不到,则是弱一致性 。
620
7- Availability(可用性):好的响应性能
21+ ** 最终一致性 **
822
9- Partition tolerance(分区容错性) :可靠性
23+ 不保证在任意时刻任意节点上的同一份数据都是相同的,但是随着时间的迁移,不同节点上的同一份数据总是在向趋同的方向变化。简单说,就是在一段时间后,节点间的数据会最终达到一致状态。
1024
11- 定理:任何分布式系统只可同时满足二点,没法三者兼顾。
25+ 由于分布式事务方案,无法做到完全的ACID的保证,没有一种完美的方案,能够解决掉所有业务问题。因此在实际应用中,会根据业务的不同特性,选择最适合的分布式事务方案。
26+
27+ ## 分布式事务的基础
28+
29+ ### CAP理论
30+
31+ ** Consistency** (一致性):数据一致更新,所有数据变动都是同步的(强一致性)。
32+
33+ ** Availability** (可用性):好的响应性能。
34+
35+ ** Partition tolerance** (分区容错性) :可靠性。
36+
37+ 定理:任何分布式系统** 只可同时满足二点** ,没法三者兼顾。
1238
1339CA系统(放弃P):指将所有数据(或者仅仅是那些与事务相关的数据)都放在一个分布式节点上,就不会存在网络分区。所以强一致性以及可用性得到满足。
1440
1541CP系统(放弃A):如果要求数据在各个服务器上是强一致的,然而网络分区会导致同步时间无限延长,那么如此一来可用性就得不到保障了。坚持事务ACID(原子性、一致性、隔离性和持久性)的传统数据库以及对结果一致性非常敏感的应用通常会做出这样的选择。
1642
1743AP系统(放弃C):这里所说的放弃一致性,并不是完全放弃数据一致性,而是放弃数据的强一致性,而保留数据的最终一致性。如果即要求系统高可用又要求分区容错,那么就要放弃一致性了。因为一旦发生网络分区,节点之间将无法通信,为了满足高可用,每个节点只能用本地数据提供服务,这样就会导致数据不一致。一些遵守BASE原则数据库,(如:Cassandra、CouchDB等)往往会放宽对一致性的要求(满足最终一致性即可),一次来获取基本的可用性。
1844
19- ## BASE理论
20-
21- Basically Available基本可用:指分布式系统在出现不可预知的故障的时候,允许损失部分可用性——但不是系统不可用。
45+ ### BASE理论
2246
23- 响应时间上的损失:假如正常一个在线搜索0.5秒之内返回,但由于故障(机房断电或网络不通),查询结果的响应时间增加到1—2秒。功能上的损失:如果流量激增或者一个请求需要多个服务间配合,而此时有的服务发生了故障,这时需要进行服务降级,进而保护系统稳定性 。
47+ BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写。是对CAP中AP的一个扩展 。
2448
25- Soft state软状态:允许系统在不同节点的数据副本之间进行数据同步的过程存在延迟。Eventually consistent最终一致:最终数据是一致的就可以了,而不是时时高一致。
49+ 1 . 基本可用:分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
50+ 2 . 软状态:允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是CAP中的不一致。
51+ 3 . 最终一致:最终一致是指经过一段时间后,所有节点数据都将会达到一致。
2652
27- BASE思想主要强调基本的可用性,如果你需要High 可用性,也就是纯粹的高性能,那么就要以一致性或容错性为牺牲 。
53+ BASE解决了CAP中理论没有网络延迟,在BASE中用软状态和最终一致,保证了延迟后的一致性。BASE和 ACID 是相反的,它完全不同于ACID的强一致性模型,而是通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态 。
2854
29- ## 实现方案
55+ ## 分布式事务解决方案
3056
31- 分布式事务的实现主要有以下 5 种方案:
57+ 分布式事务的实现主要有以下 6 种方案:
3258
33- - XA 方案
59+ - 2PC 方案
3460- TCC 方案
35- - 可靠消息最终一致性方案
61+ - 本地消息表
62+ - MQ事务
63+ - Saga事务
3664- 最大努力通知方案
3765
38- ## 2PC/XA方案
66+ ### 2PC方案
3967
40- 所谓的 XA 方案,即:两阶段提交,有一个事务管理器的概念,负责协调多个数据库(资源管理器)的事务,事务管理器先问问各个数据库你准备好了吗?如果每个数据库都回复 ok,那么就正式提交事务,在各个数据库上执行操作;如果任何其中一个数据库回答不 ok,那么就回滚事务。
68+ 2PC方案分为两阶段:
4169
42- 这种分布式事务方案,比较适合单块应用里,跨多个库的分布式事务,而且因为严重依赖于数据库层面来搞定复杂的事务,效率很低,绝对不适合高并发的场景。
70+ 第一阶段:事务管理器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交.
4371
44- 一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。如果你要操作别人的服务的库,你必须是通过调用别的服务的接口来实现,绝对不允许交叉访问别人的数据库 。
72+ 第二阶段:事务协调器要求每个数据库提交数据,或者回滚数据 。
4573
46- ## TCC强一致性方案
74+ 优点: 尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于MySQL是从5.5开始支持。
75+
76+ 缺点:
77+
78+ - 单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机,比如在第一阶段已经完成,在第二阶段正准备提交的时候事务管理器宕机,资源管理器就会一直阻塞,导致数据库无法使用。
79+ - 同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。
80+ - 数据不一致:两阶段提交协议虽然为分布式数据强一致性所设计,但仍然存在数据不一致性的可能,比如在第二阶段中,假设协调者发出了事务commit的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。
81+
82+ 总的来说,2PC方案比较简单,成本较低,但是其单点问题,以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。
83+
84+ ### TCC
4785
4886TCC 的全称是:` Try ` 、` Confirm ` 、` Cancel ` 。
4987
@@ -52,26 +90,106 @@ TCC 的全称是:`Try`、`Confirm`、`Cancel`。
5290- ** Cancel 阶段** :如果任何一个服务的业务方法执行出错,那么这里就需要 ** 进行补偿** ,就是执行已经执行成功的业务逻辑的回滚操作。(把那些执行成功的回滚)
5391-
5492
55- 这种方案说实话几乎很少人使用,但是也有使用的场景。因为这个 ** 事务回滚实际上是严重依赖于你自己写代码来回滚和补偿** 了,会造成补偿代码巨大,非常之恶心。
93+ 举个简单的例子如果你用100元买了一瓶水, Try阶段:你需要向你的钱包检查是否够100元并锁住这100元,水也是一样的。
94+
95+ 如果有一个失败,则进行cancel(释放这100元和这一瓶水),如果cancel失败不论什么失败都进行重试cancel,所以需要保持幂等。
96+
97+ 如果都成功,则进行confirm,确认这100元扣,和这一瓶水被卖,如果confirm失败无论什么失败则重试(会依靠活动日志进行重试)。
98+
99+ 这种方案说实话几乎很少人使用,但是也有使用的场景。因为这个** 事务回滚实际上是严重依赖于你自己写代码来回滚和补偿** 了,会造成补偿代码巨大。
100+
101+ ### 本地消息表
102+
103+ 本地消息表的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。
104+
105+ ![ ] ( http://img.dabin-coder.cn/image/本地消息表.png )
106+
107+ 对于本地消息队列来说核心是把大事务转变为小事务。还是举上面用100元去买一瓶水的例子。
108+
109+ 1.当你扣钱的时候,你需要在你扣钱的服务器上新增加一个本地消息表,你需要把你扣钱和写入减去水的库存到本地消息表放入同一个事务(依靠数据库本地事务保证一致性。
110+
111+ 2.这个时候有个定时任务去轮询这个本地事务表,把没有发送的消息,扔给商品库存服务器,叫他减去水的库存,到达商品服务器之后这个时候得先写入这个服务器的事务表,然后进行扣减,扣减成功后,更新事务表中的状态。
112+
113+ 3.商品服务器通过定时任务扫描消息表或者直接通知扣钱服务器,扣钱服务器本地消息表进行状态更新。
114+
115+ 4.针对一些异常情况,定时扫描未成功处理的消息,进行重新发送,在商品服务器接到消息之后,首先判断是否是重复的,如果已经接收,在判断是否执行,如果执行在马上又进行通知事务,如果未执行,需要重新执行需要由业务保证幂等,也就是不会多扣一瓶水。
116+
117+ 本地消息队列是BASE理论,是最终一致模型,适用于对一致性要求不高的。实现这个模型时需要注意重试的幂等。
118+
119+ ### MQ事务
120+
121+ 基于 MQ 的分布式事务方案其实是对本地消息表的封装,将本地消息表基于 MQ 内部,其他方面的协议基本与本地消息表一致。
122+
123+ MQ事务方案整体流程和本地消息表的流程很相似,如下图:
124+
125+ ![ ] ( http://img.dabin-coder.cn/image/MQ事务方案.png )
126+
127+ 从上图可以看出和本地消息表方案唯一不同就是将本地消息表存在了MQ内部,而不是业务数据库中。
128+
129+ 那么MQ内部的处理尤为重要,下面主要基于 RocketMQ 4.3 之后的版本介绍 MQ 的分布式事务方案。
130+
131+ 在本地消息表方案中,保证事务主动方发写业务表数据和写消息表数据的一致性是基于数据库事务,RocketMQ 的事务消息相对于普通 MQ提供了 2PC 的提交接口,方案如下:
132+
133+ ** 正常情况:事务主动方发消息**
134+
135+ ![ ] ( http://img.dabin-coder.cn/image/事务主动方发消息.png )
136+
137+ 这种情况下,事务主动方服务正常,没有发生故障,发消息流程如下:
138+
139+ - 发送方向 MQ 服务端(MQ Server)发送 half 消息。
140+ - MQ Server 将消息持久化成功之后,向发送方 ack 确认消息已经发送成功。
141+ - 发送方开始执行本地事务逻辑。
142+ - 发送方根据本地事务执行结果向 MQ Server 提交二次确认(commit 或是 rollback)。
143+ - MQ Server 收到 commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 rollback 状态则删除半消息,订阅方将不会接受该消息。
144+
145+ ** 异常情况:事务主动方消息恢复**
146+
147+ ![ ] ( http://img.dabin-coder.cn/image/事务主动方消息恢复.png )
148+
149+ 在断网或者应用重启等异常情况下,图中 4 提交的二次确认超时未到达 MQ Server,此时处理逻辑如下:
150+
151+ - MQ Server 对该消息发起消息回查。
152+ - 发送方收到消息回查后,需要检查对应消息的本地事务执行的最终结果。
153+ - 发送方根据检查得到的本地事务的最终状态再次提交二次确认。
154+ - MQ Server基于 commit/rollback 对消息进行投递或者删除。
155+
156+ ** 优点**
157+
158+ 相比本地消息表方案,MQ 事务方案优点是:
159+
160+ - 消息数据独立存储 ,降低业务系统与消息系统之间的耦合。
161+ - 吞吐量大于使用本地消息表方案。
162+
163+ ** 缺点**
164+
165+ - 一次消息发送需要两次网络请求(half 消息 + commit/rollback 消息) 。
166+ - 业务处理服务需要实现消息状态回查接口。
167+
168+ ### Saga事务
169+
170+ Saga是由一系列的本地事务构成。每一个本地事务在更新完数据库之后,会发布一条消息或者一个事件来触发Saga中的下一个本地事务的执行。如果一个本地事务因为某些业务规则无法满足而失败,Saga会执行在这个失败的事务之前成功提交的所有事务的补偿操作。
171+
172+ Saga的实现有很多种方式,其中最流行的两种方式是:
173+
174+ - ** 基于事件的方式** 。这种方式没有协调中心,整个模式的工作方式就像舞蹈一样,各个舞蹈演员按照预先编排的动作和走位各自表演,最终形成一只舞蹈。处于当前Saga下的各个服务,会产生某类事件,或者监听其它服务产生的事件并决定是否需要针对监听到的事件做出响应。
175+ - ** 基于命令的方式** 。这种方式的工作形式就像一只乐队,由一个指挥家(协调中心)来协调大家的工作。协调中心来告诉Saga的参与方应该执行哪一个本地事务。
176+
177+ ### 最大努力通知方案
56178
179+ 最大努力通知也称为定期校对,是对MQ事务方案的进一步优化。它在事务主动方增加了消息校对的接口,如果事务被动方没有接收到消息,此时可以调用事务主动方提供的消息校对的接口主动获取。
57180
181+ 最大努力通知的整体流程如下图:
58182
59- ## 可靠消息最终一致性方案
183+ ![ ] ( http://img.dabin-coder.cn/image/最大努力通知方案.png )
60184
61- 基于 MQ 来实现事务。比如阿里的 RocketMQ 就支持消息事务。大概的意思就是:
185+ 在可靠消息事务中,事务主动方需要将消息发送出去,并且消息接收方成功接收,这种可靠性发送是由事务主动方保证的;
62186
63- 1 . A 系统先发送一个 prepared 消息到 MQ,如果这个 prepared 消息发送失败那么就直接取消操作别执行了;
64- 2 . 如果这个消息发送成功过了,那么接着执行本地事务,如果成功就告诉 MQ 发送确认消息,如果失败就告诉 MQ 回滚消息;
65- 3 . 如果发送了确认消息,那么此时 B 系统会接收到确认消息,然后执行本地的事务;
66- 4 . mq 会自动定时轮询所有 prepared 消息回调你的接口,问你,这个消息是不是本地事务处理失败了,所有没发送确认的消息,是继续重试还是回滚?一般来说这里你就可以查下数据库看之前本地事务是否执行,如果回滚了,那么这里也回滚吧。这个就是避免可能本地事务执行成功了,而确认消息却发送失败了。
67- 5 . 这个方案里,要是系统 B 的事务失败了咋办?重试咯,自动不断重试直到成功,如果实在是不行,要么就是针对重要的资金类业务进行回滚,比如 B 系统本地回滚后,想办法通知系统 A 也回滚;或者是发送报警由人工来手工回滚和补偿。
187+ 但是最大努力通知,事务主动方尽最大努力(重试,轮询....)将事务发送给事务接收方,但是仍然存在消息接收不到,此时需要事务被动方主动调用事务主动方的消息校对接口查询业务消息并消费,这种通知的可靠性是由事务被动方保证的。
68188
69- 这个还是比较合适的,目前国内互联网公司大都是这么玩儿的,要不你举用 RocketMQ 支持的,要不你就自己基于类似 ActiveMQ?RabbitMQ?自己封装一套类似的逻辑出来,总之思路就是这样子的 。
189+ 最大努力通知适用于业务通知类型,例如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口 。
70190
71- ## 最大努力通知方案
191+ ## 参考文章
72192
73- 这个方案的大致意思就是:
193+ https://www.pdai.tech/md/arch/arch-z-transection.html
74194
75- 1 . 系统 A 本地事务执行完之后,发送个消息到 MQ;
76- 2 . 这里会有个专门消费 MQ 的最大努力通知服务,这个服务会消费 MQ 然后写入数据库中记录下来,或者是放入个内存队列也可以,接着调用系统 B 的接口;
77- 3 . 要是系统 B 执行成功就 ok 了;要是系统 B 执行失败了,那么最大努力通知服务就定时尝试重新调用系统 B,反复 N 次,最后还是不行就放弃。
195+ https://juejin.cn/post/6844903647197806605#heading-15
0 commit comments