现代大型互联网系统已经告别了单机时代,微服务、负载均衡、分布式已经成为主流。在分布式系统中,对于数据库的关键操作,也需要使用事务来保证业务结果,如银行转账、交易扣款等。单机的事务可以通过数据库事务或 Spring 事务来实现,分布式系统则更复杂,涉及多机房、多数据源。
分布式事务也需要满足事务的基本特性,包括原子性、一致性、隔离性、持久性。事务中的一系列操作,要么全部成功,要么全部失败。在分布式事务中,最大的挑战是一致性,要求系统中所有的数据存储保持一致。一致性满足 CAP 原则,即 Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)不能同时满足。Consistency 指同一时间系统中数据保持一致,Availability 指部分节点异常后能否正常提供服务,Partition tolerance 指数据不一致形成分区后能否正常提供服务。
目前业界对分布式事务已形成 BASE 理论共识,即 Basically Available(基本可用),Soft State(软状态)和 Eventual Consistency(最终一致性)。BASE 理论延伸了 CAP 原则,在系统出现部分节点故障时允许损失部分可用性,允许出现中间状态,并使用一些手段保证最终数据的一致性。基于 BASE 理论设计的系统,能够接受短暂时间的数据不一致,流程设计上要确保这个不一致不能影响业务。
两阶段提交方案
两阶段提交就是把数据处理过程分为两个阶段。使用一个独立的事务管理器,在第一阶段(prepare)向子系统发起事务请求,询问各个子系统是否就绪,根据子系统的响应决定第二阶段(commit/rollback)是否提交事务或者回滚。如果第一阶段所有子系统都就绪,则第二阶段提交事务;如有子系统未就绪,则第二阶段所有子系统进行回滚。
两阶段提交方案最大的问题是存在阻塞和不一致风险。若事务管理器故障,将导致整个系统不可用。如果第二阶段事务管理器或子系统异常,最终结果可能无法确定。
三阶段提交方案
三阶段提交方案,是把两阶段提交方案的第一步再次细分为两步。第一阶段(CanCommit),事务管理器询问子系统是否可以执行事务,得到全部肯定答复后,在第二阶段(PreCommit)向子系统发起事务请求,然后在第三阶段(DoCommit)提交或者回滚。
TCC 方案
TCC 方案,全称是 try-confirm-cancel,各阶段含义是:
-
try:尝试执行,完成业务检查,预留业务资源;
-
confirm:执行事务,不做任何检查,使用 try 预留的资源,满足幂等性;
-
cancel:取消事务,不做任何检查,释放 try 预留的资源,满足幂等性;
本地消息表方案
假设系统 A、系统 B 是分布式事务的参与者。
-
系统 A 写入业务数据后向数据库 A 写入一条消息;
-
系统 A 定时轮询本地消息,并写入消息队列,如果消息发送失败会重试;
-
系统 B 消费消息队列中的消息,并执行事务,写入业务数据;如果事务处理失败,会继续重试,如果需要回滚,可以修改数据库 A 消息通知系统 A 进行回滚;
使用本地消息表方案,要求系统 A 和系统 B 支持幂等,并且系统 A 能够进行回滚。
可靠消息方案
可靠消息方案通过消息队列来保证分布式事务。流程如下:
-
系统 A 发送 prepare 消息,若失败,事务直接失败;
-
消息发送成功后,系统 A 执行本地操作;
-
本地操作成功后,系统 A 发送 confirm 消息,否则发送回滚消息;
-
系统 B 定时消费消息队列中的消息;
-
系统 B 收到 confirm 消息后,执行本地操作;
-
系统 B 本地操作成功后,发送 ACK 消息,否则业务失败后系统 B 向 A 系统发送回滚请求;
-
消息队列定时轮询 prepare 消息,并调用系统 A 确认,若成功重发 confirm 否则回滚消息;
尽最大努力通知方案
尽最大努力通知,就是系统 A 执行成功后,发送消息,通知服务消费消息并调用系统 B,如果系统 B 执行成功则流程结束,否则会尽量调用 B 进行重试,失败到一定次数后,放弃本次事务。
总结
分布式事务很难做到实时一致性,总的来说主要是保证最终一致性。在技术设计中,需要充分考虑中间状态,确保中间状态不影响业务,确保系统可用性。