
什么叫事务
事务是无法被分割的操作.事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消。也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做。事务应该具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。
本地事务
举例:修改一个数据库中的多个表字段
ACID,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)。
php使用pdo本地事务可以用
1 | $client->beginTransaction(); |
分布式事务
随着业务的发展,一个业务需求并不仅仅是在一个数据库中进行事务操作,很有可能是跨数据库、跨存储介质的事务操作,这种就是分布式事务。分布式的解决方案很多,我们先来看看XA 协议
XA协议
XA协议,全称为eXtended Architecture,是由X/Open组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准
。它主要用于协调跨多个数据库或资源管理器的事务,以确保事务的一致性和可靠性

图中有三个角色。
AP:应用程序,也就是大家写的业务代码
TM:事务管理器,协调事务的“中间层”
RM:资源管理器,实际业务需要操作的资源,比如Mysql/MQ等。
其中TM和RM之间的交互规范为XA协议。
XA协议通过两阶段提交(2PC)或三阶段提交(3PC)机制来实现分布式事务的处理,并确保事务操作的原子性、一致性、隔离性和持久性(ACID特性)。
为什么需要有多阶段提交呢?假定我们是一阶段提交,即是直接进行操作。如果多个资源管理器能直接提交成功这当然没有问题。假定有A/B/C三个RM。AB直接扣成功了,C扣失败了。那么AB是不是要执行回退操作,同时因为是一阶段提交并没有rollback这个操作。所以A和B必须执行反向sql(删除的要插入、更新的要恢复,插入的要删除)整体的复杂度和容错上升了很多。
具体的2pc和3pc就不描述了。总结下2pc和3pc。
2pc

包含的过程
prePare - 锁资源
commit
rollBack
优点
- 2PC的原理直观易懂,实现起来相对简单,这使得它在许多关系型数据库中得到广泛应用。
- 2PC通过协调所有分布式事务参与者来确保事务的一致性,使得事务要么全部提交,要么全部回滚,从而保证了数据的强一致性。(如果发生网络异常会有问题)
- 在2PC中,提交和回滚操作可以利用数据库自身的功能来完成,不需要额外实现,这样可以减少对业务逻辑的侵入。
缺点
- 在2PC的执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态,这会影响系统的吞吐量和性能。
- 协调者在整个两阶段提交协议中起到了非常重要的作用,一旦协调者发生故障,那么整个第二阶段提交流程将无法运转,更为严重的是,协调者在阶段二中出现问题的话,那么其他的参与者将会处于锁定事务资源的状态中,而无法继续完成事务操作。
- 在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这会导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后会执行commit操作,但是其他部分未接到commit请求的机器则无法执行事务提交,于是整个分布式系统便出现了数据不一致的现象。
- 2PC方案需要进行多次网络通信和等待响应,这会增加系统的开销和延迟。特别是在大规模分布式系统中,2PC的性能开销可能会显著影响系统的吞吐量和响应时间。
- 如果在协调者指示参与者进行事务提交询问的过程中,参与者出现故障而导致协调者始终无法获取到所有参与者的响应的消息的话,这时协调者只能依靠其自身的超时机制来判断是否中断事务,这样的策略比较保守,换句话说,二阶段提交协议没有设计相应的容错机制,当任意一个参与者节点宕机,那么协调者超时没收到响应,就会导致整个事务回滚失败。
2pc和xa的关系
X/Open XA协议提供了一套标准接口,使得事务管理器能够与资源管理器进行通信,以实现两阶段提交协议,确保分布式系统中事务的原子性和一致性。因此,可以说X/Open XA协议是实现二阶段提交协议的一种规范,而二阶段提交协议是X/Open XA协议中用于保持事务一致性的核心机制。简单来说两阶段提交是思想,xa规范是基于思想的实现。
3pc

包含的过程
canCommit - 不锁资源
preCommit - 锁资源
doCommit
rollBack
优点
- 3PC通过引入参与者超时机制,在参与者超时后会自动进行本地commit/abort,从而释放资源,避免了参与者长时间无法与协调者通讯时无法释放资源的问题,减少了同步阻塞时间。
- 3PC允许在第三阶段即使协调者出现故障,参与者也可以根据超时时间自行决定提交或回滚事务,降低了单点故障对系统的影响。
- 3PC增加了CanCommit阶段,不像2PC一样从一开始就锁定所有事务资源,而是在通过CanCommit阶段排除掉个别不具备处理事务能力的参与者后,再进入锁定事务资源的第二阶段。
- 3PC通过引入“询问阶段”,解决了2PC协议的阻塞问题和单点故障问题,提高了系统的可用性和性能。
- 在CanCommit阶段,并没有进行真正的事务执行,即使失败也不会影响数据,减少了数据不一致的发生概率。
- 3PC相较于2PC,最大的优点是降低了参与者的阻塞范围,并且能在单点故障后继续达成一致。
缺点
- 3PC 的流程比 2PC 更为复杂,需要更多的状态转换和超时处理逻辑,这增加了实现的难度和出错的可能性。
- 3PC 引入了额外的阶段和网络通信,可能会导致更大的性能开销。
- 虽然 3PC 在一定程度上降低了数据不一致的风险,但在某些极端情况下,例如网络分区等,仍然可能出现数据不一致的情况。(没有根本解决问题)
- 在参与者收到 PreCommit 消息后,如果网络出现分区,协调者和参与者无法进行后续的通信,这种情况下,参与者在等待超时后,依旧会执行事务提交,这样会导致数据的不一致。
- 尽管 3PC 缓解了对协调者的依赖,但没有完全解决。在某些情况下,如果协调者出现问题,事务的提交或回滚可能会受到影响。
TCC

TCC描述
TCC分为try/confirm/cancel三个部分。很明显是借鉴了两阶段提交的思想。
Try:对应二阶段提交协议的 Prepare,业务在收到 Try 指令后,做资源的预留,而非直接执行实际的操作,返回“是”,假如资源预留不成功,返回“否”
Confirm:协调器在收到了所有分布式事务参与方的“是”后,决定全局提交,向分布式事务的各个参与方发送 Confirm,参与方执行 Confirm,将之前预留的资源进行扣除。
Cancel:协调器在收到参与方的任意一个“否”的话,会决定全局回滚,向各参与方发送 Cancel,参与方执行 Cancel,将之前预留的资源进行释放。
Try/Confirm/Cancel 接口满足以下三个特性:
幂等:接口要能够正确的识别重复请求以避免重复处理,一种可行的思路是在业务上引入唯一 id,比如 uuid 库。
防悬挂:防止 Try 在 Cancel 之后到达导致的资源悬挂问题,在一般情况下,先执行 Try,后执行 Cancel,假如先执行 Cancel,后执行 Try,那么后执行的 Try 将没有对应的 Cancel 以取消资源的预留,导致资源悬挂。
空回滚:防止 Cancel 在 Try 之前到达导致的空回滚,这个和放悬挂说的是一件事的两个方面,放悬挂侧重的是要正确处理 Try,空回滚侧重的是要正确处理 Cancel。这两点可能的解决方案是引入类似于子事务屏障的机制,其基本实现原理类似于后面要讲的事务状态表,即在执行的时候记录所有分支事务的状态信息,以感知 Try/Cancel 是否按序执行。
TCC优点
- 解决跨服务的业务操作原子性问题:TCC适用于需要保证跨多个服务操作原子性的业务场景。摆脱了2pc和3pc局限于数据库(或者符合xa规范的sql)的樊篱。自由度更高
- 基于Confirm和Cancel的幂等性,TCC能够保证事务最终完成确认或者取消,保证数据的一致性
- 由于Confirm和Cancel两个阶段的操作都是原子的,因此即使某个节点发生故障,也不会影响整个分布式事务的执行
TCC缺点
- 实现复杂度高:相比于传统的两阶段提交和三阶段提交协议,TCC的实现复杂度更高。需要实现Try、Confirm和Cancel三个阶段的操作,并设计相应的监控和协调机制
- 对微服务的侵入性强:微服务的每个事务都必须实现Try、Confirm、Cancel等3个方法,开发成本高,今后维护改造的成本也高
- 性能开销大:由于需要在Try阶段进行资源锁定和业务检查,因此可能存在一定的性能开销。特别是对于一些高并发的场景,可能会对系统的性能产生影响
- 系统依赖增加:要求所有参与的系统都必须实现TCC协议,增加了系统间的耦合
SAGA

SAGA描述
SAGA最初出现在1987年Hector Garcaa-Molrna & Kenneth Salem发表的论文SAGAS里。其核心思想是将长事务拆分为多个短事务,由Saga事务协调器协调,如果每个短事务都成功提交完成,那么全局事务就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作
长事务拆分为多个短事务T1,T2,T3…Tn,然后对应的回滚操作为C1,C2,C3…Cn。按照顺序执行T1->Tn.如果其中某个短事务执行失败比如T3。那么就反向执行C3->C2->C1。
Saga的执行顺序有两种:
正向执行顺序:T1, T2, T3, …, Tn(理想情况下的执行顺序)。
反向补偿顺序:T1, T2, …, Tj, Cj, …, C2, C1,其中0 < j < n,表示在执行到第j个子事务时出错,然后从第j个子事务开始逆向执行补偿事务来撤销之前的操作
Saga模式有两种恢复策略:
向后恢复(Backward Recovery):补偿所有已完成的事务,如果任一子事务失败。
向前恢复(Forward Recovery):重试失败的事务,假设每个子事务最终都会成功
SAGA优点
- 灵活性:Saga模式允许每个子事务独立执行,并且可以根据业务需求自定义补偿操作,这为处理复杂的业务流程提供了灵活性。
- 可扩展性:Saga模式适用于微服务架构,可以轻松地扩展到多个服务和组件,而不需要集中式事务管理器
- 容错性:通过补偿事务,Saga模式能够在子事务失败时撤销之前的操作,从而保持数据的一致性
- 最终一致性:Saga模式支持最终一致性,允许系统在出现故障时通过补偿操作达到一致状态,而不是立即保证强一致性
- 去中心化:Saga模式不需要一个中心化的事务协调器,每个服务可以独立管理自己的事务和补偿逻辑
SAGA缺点
- 复杂性:Saga模式需要为每个子事务定义补偿操作,这增加了系统的复杂性,特别是在事务链很长时。
- 实时性问题:Saga模式可能无法保证实时一致性,因为补偿操作可能需要时间来执行,特别是在网络延迟或服务故障的情况下。
- 业务逻辑侵入:Saga模式要求业务逻辑必须能够被拆分成可补偿的事务,这可能对业务逻辑的设计和实现造成侵入。
- 数据不一致的风险:在某些情况下,如果补偿操作不成功或者不完整,可能会导致数据不一致。
- 状态管理:Saga模式需要维护事务的状态,包括执行和补偿状态,这增加了状态管理的复杂性。
- 幂等性问题:Saga中的操作需要保证幂等性,以确保多次执行或补偿不会导致不一致的结果,这在实际应用中可能难以保证。
AT

AT描述
AT依赖数据库的支持,自由度有限
AT优点
- 无侵入性:应用程序无需修改代码就可以使用AT模式,对现有系统的集成较为简单
AT缺点
- 缺点是支持的 SQL 有限,并且锁全局资源有一定的性能问题
Try-Best

Try-Best描述
学术名称为最大努力通知(Best-effort Delivery)。如上图所示,发起通知方需要将消息尽可能的投递到接收通知方。但是因为各种各样的原因这个消息不一定会被处理到。所以发起通知方还需要提供一个状态查询接口给接收通知方。
Try-Best优点
- 实现简单:相对于传统的两阶段提交,最大努力通知方案实现相对简单,易于维护
- 性能高:由于采用异步通知的方式,事务发起方不需要等待通知结果,可以提高系统的整体性能
- 灵活性强:最大努力通知方案可以根据具体业务需求,灵活设置重试策略和人工干预机制
Try-Best缺点
- 一致性保证不足:由于采用异步通知的方式,无法完全保证数据的一致性,可能会存在短暂的不一致情况
- 重试和人工干预成本高:在通知失败的情况下,需要设置重试策略和人工干预机制,增加了系统的复杂度和运维成本
- 接收方主动查询:为了保证通知的可靠性,接收方需要主动查询通知结果,这可能增加系统的负担和复杂性
本地事务状态表

本地事务状态表优点
逻辑简单,失败后插入表后就可以了。具体的补偿交给定时脚本
本地事务状态表缺点
- 异步化,补偿可能有延迟
可靠消息队列-基于本地消息

可靠消息队列-基于本地消息优点
- 业务不用直接对接外部系统
可靠消息队列-基于本地消息缺点
- 异步化,补偿可能有延迟
- 消费侧会比较复杂,比如消费了消息后调用的若干外部系统有失败的怎么弄?状态在哪里存储
可靠消息队列-基于事务消息
可靠消息队列-基于事务消息优点
- 不用扫描脚本了,直接依赖mq
可靠消息队列-基于事务消息缺点
- 对mq有要求
总结

相关实践
腾讯
TDXA
阿里
Seata
字节跳动
有个 bytetx、bytestate,前者是做强一致性的,后者是最终一致性