2PC
2PC即两阶段提交协议,是将整个事务流程分为两个阶段,准备阶段(Prepare phase)、提交阶段(commit phase)。
整体流程
阶段一:准备阶段(投票阶段)
-
协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。
-
参与者节点执行事务操作,并将Undo和Redo信息记入事务日志中。
-
各参与者响应协调者发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个"同意"消息;如果参与者节点的事务操作实际执行失败,则它返回一个"中止"消息。
阶段二:提交阶段(完成)
成功
当协调者节点从所有参与者节点获得的响应消息都为"同意"时:
- 协调者节点向所有参与者节点发出"正式提交"的请求。
- 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
- 参与者节点向协调者节点发送"完成"消息。
- 协调者节点收到所有参与者节点反馈的"完成"消息后,完成事务。
失败
如果任一参与者节点在第一阶段返回的响应消息为"终止",或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:
- 协调者节点向所有参与者节点发出"回滚操作"的请求。
- 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
- 参与者节点向协调者节点发送"回滚完成"消息。
- 协调者节点收到所有参与者节点反馈的"回滚完成"消息后,取消事务。
缺点
同步阻塞
在执行过程中,所有参与该事务操作的逻辑都处理于阻塞状态。
太过保守
协调者节点指示参与者节点进行提交等操作时,如有参与者节点出现了崩溃等情况而导致协调者始终无法获取所有参与者的响应信息,这时协调者将只能依赖协调者自身的超时机制来生效。但往往超时机制生效时,协调者都会指示参与者进行回滚操作。这样的策略显得比较保守。
实现
XA协议
TCC模式
数据库以及分布式的两阶段提交都提供了ACID 的保证。 由于隔离性互斥的要求,在事务执行过程中,所有的资源都是被锁定的,这种情况只适合执行时间确定的短事务。
后续大家开始通过业务逻辑将互斥锁操作从资源层面上移到业务层面,这并不是完全放弃了 ACID,而是通过放宽一致性要求,借助本地事务来实现最终分布式事务一致性的同时也保证系统的吞吐。
概述
TCC 名字的由来是其中包含了 try, confirm, cancel 三个操作。
- Try:资源的检测和预留;
- Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
- Cancel:预留资源释放;
服务要求
TCC设计 - 业务模型分2阶段设计
用户接入TCC,最重要的是考虑如何将自己的业务模型拆成两阶段来实现。
以“扣钱”场景为例,在接入TCC前,对 A 账户的扣钱,只需一条更新账户余额的SQL便能完成;但是在接入TCC之后,用户就需要考虑如何将原来一步就能完成的扣钱操作,拆成两阶段,实现成三个方法,并且保证一阶段Try成功,二阶段Confirm一定能成功。
如上图所示,Try 方法作为一阶段准备方法,需要做资源的检查和预留。在扣钱场景下,Try 要做的事情是就是检查账户余额是否充足,预留转账资金,预留的方式就是冻结 A 账户的 转账资金。Try 方法执行之后,账号 A 余额虽然还是 100,但是其中 30 元已经被冻结了,不能被其他事务使用。
二阶段 Confirm 方法执行真正的扣钱操作。Confirm 会使用 Try 阶段冻结的资金,执行账号扣款。Confirm 方法执行之后,账号 A 在一阶段中冻结的 30 元已经被扣除,账号 A 余额变成 70 元 。
如果二阶段是回滚的话,就需要在 Cancel 方法内释放一阶段 Try 冻结的 30 元,使账号 A 的回到初始状态,100 元全部可用。
用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是TCC 模式无AT模式的全局行锁,TCC性能会比 AT模式高很多。
TCC设计 - 允许空回滚
Cancel接口设计时需要允许空回滚。在 Try 接口因为丢包时没有收到,事务管理器会触发回滚,这时会触发Cancel接口,这时Cancel执行时发现没有对应的事务 xid 或主键时,需要返回回滚成功。让事务服务管理器认为已回滚,否则会不断重试,而Cancel又没有对应的业务数据可以进行回滚。
TCC设计 - 防悬挂控制
悬挂的意思是:Cancel比Try 接口先执行,出现的原因是 Try 由于网络拥堵而超时,事务管理器生成回滚,触发 Cancel 接口,而最终又收到了 Try 接口调用,但是 Cancel 比 Try 先到。按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,则此时的 Try 接口不应该执行,否则会产生数据不一致,所以我们在 Cancel 空回滚返回成功之前先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try接口先检查这条事务xid或业务主键如果已经标记为回滚成功过,则不执行 Try 的业务操作。
TCC设计 - 幂等控制
幂等性的意思是:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。因为网络抖动或拥堵可能会超时,事务管理器会对资源进行重试操作,所以很可能一个业务操作会被重复调用,为了不因为重复调用而多次占用资源,需要对服务设计时进行幂等控制,通常我们可以用事务 xid 或业务主键判重来控制。
Saga模式
概述
Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。
分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
Saga 模式下分布式事务通常是由事件驱动的,各个参与者之间是异步执行的,Saga 模式是一种长事务解决方案。
服务要求
与TCC实践经验相同的是,Saga 模式中,每个事务参与者的冲正、逆向操作,需要支持:
- 空补偿:逆向操作早于正向操作时;
- 防悬挂控制:空补偿后要拒绝正向操作
- 幂等
适应场景
-
Saga模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。
-
事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供TCC要求的接口,可以使用Saga 模式。
优缺点
优势
- 一阶段提交本地数据库事务,无锁,高性能;
- 参与者可以采用事务驱动异步执行,高吞吐;
- 补偿服务即正向服务的“反向”,易于理解,易于实现;
缺点
Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。
Seata
术语
Seata中有3个基本组件:
- Transaction Coordinator(TC): Maintain status of global and branch transactions, drive the global commit or rollback.
- Transaction Manager(TM): Define the scope of global transaction: begin a global transaction, commit or rollback a global transaction.
- Resource Manager(RM): Manage resources that branch transactions working on, talk to TC for registering branch transactions and reporting status of branch transactions, and drive the branch transaction commit or rollback.
整体流程
- TM asks TC to begin a new global transaction. TC generates an XID representing the global transaction.
- XID is propagated through microservices' invoke chain.
- RM registers local transaction as a branch of the corresponding global transaction of XID to TC.
- TM asks TC for committing or rollbacking the corresponding global transaction of XID.
- TC drives all branch transactions under the corresponding global transaction of XID to finish branch committing or rollbacking.
AT模式
AT模式是一种无侵入的分布式事务解决方案。在AT模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。
整体机制
两阶段提交协议的演变:
-
阶段一
业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
-
阶段二:
-
- 提交异步化,非常快速地完成。
- 回滚通过一阶段的回滚日志进行反向补偿。
阶段一:详细流程
Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
阶段二:提交
“业务SQL”在阶段一已经提交至数据库, 所以Seata 框架只需将阶段一保存的快照数据和行锁删掉,完成数据清理即可。
阶段二:回滚
Seata需要回滚阶段一已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
适应场景
分布式事务的业务逻辑中仅仅纯数据库操作,不包含其他中间件事务逻辑。
优缺点
优点
改动及代码侵入最小。由Seata来负责Commit和Rollback的自动化触发或回滚操作。
缺点
-
如果事务中包含缓存存储或发送消息等不适合。
-
为了保证镜像sql的可靠性,需要用户对sql尽量做简化, 建议做法:将多条SQL语句分解为多个事务中的原子步骤(对应SeataAt模式的分支Branch概念),如果单条SQL语句跨表,也分解成为多个事务中的原子步骤(尽量降低Seata存储镜前SQL结果时的风险)。
-
多次对DB的操作,以及全局行锁的存在对并发处理性能有影响。
微服务事务一致性建议
与两阶段提交相比,TCC 位于业务服务层, 没有单独的准备阶段,Try 操作可以灵活选择业务资源锁的粒度。TCC 是通过最终一致性来解决系统性能问题的这个设计,对我们设计抉择有很大的启发。 有些时候 系统的技术问题是可以通过业务建模的方式来解决的。
有关领域建模,这里给大家推荐两本书,一个是《领域驱动设计》,还有一个是《实现领域驱动设计》。
微服务架构是一个在限定界限上下文内的松耦合的服务架构。微服务事务一致性的建议是什么呢?就是内刚外柔。在限定上下文内容范围内可以借助数据库提供事务一致性来做强一致。在限定上下文之间依靠最终一致性方案来解决服务间协同问题。
参考资料
[]: mp.weixin.qq.com/s?__biz=MzI… "Saga分布式事务解决方案与实践"
[]: www.jianshu.com/p/f2caa8737… "分布式事务 Seata(二) 理解什么是AT、TCC、Saga"
[]: www.cnblogs.com/anhaogoon/p… "Seata解决方案整体介绍"