前言
分布式事务是企业集成中的一个技术难点,也是每一个分布式系统架构中都会涉及到的一个东西,特别是在微服务架构中,几乎是无法避免的。
一、从单机事务到分布式
1.数据库事务
我们都知道数据库事务的四个特性:原子性、一致性、隔离性和持久性,数据库事务由数据库软件自身来完成。假如数据库在提交事务的时候突然断电,数据库可以在日志记录中找到上一次事务操作然后根据当前数据库的情况进行 undo 回滚或者是 redo 前滚(基于某时刻的完整备份然后执行从该时刻到崩溃时间所有增删改查操作,使数据库恢复到崩溃前的状态)。在分布式环境中可能遇到的问题就更多了,例如机器宕机、网络异常、消息乱序、数据错误、存储数据丢失等等,数据库自身并没有比较好的解决方案(下文会提到一种数据库支持的解决方法)。
2.分布式理论
当单个数据库的性能产生瓶颈的时候,我们可能会对数据库进行分区,这里分区指的是物理分区,分区后不同的库可能就在不同的服务器上了,这个时候单个数据库的 ACID 已经不能适应这种情况了,在集群情况下想保证集群的 ACID 是很困难的,即使能够达到,效率和性能也会大幅下降,而且难以拓展。这就需要一个新的理论CAP 原则。
CAP 是加州大学伯克利分校 Eric Brewer 教授提出的,他指出 WEB 服务无法同时满足以下 3 个属性:
- 一致性(Consistency):客户端发起的一系列操作都会同时生效
- 可用性(Availability):每个操作必须以可预期的响应结束
- 分区容错性(Partition Tolerance):即使单个组件无法使用,操作任然可以完成
具体的说在分布式系统中,任何数据库至多只能同时支持上面的两个属性,但是任何横向拓展策略都要依赖数据分区。因此,设计人员必须在一致性与可用性中作出选择。在此基础上后面又提出了另外一个理论,BASE理论,用于对 CAP 原则进一步扩充。BASE 理论指的是:
- 基本可用(Basically Available)
- 软状态(Soft State)
- 最终一致性(Eventually Consistent)
BASE 理论是对 CAP 中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们没法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
二、解决方案
1.两阶段提交(2PC)
主流关系型数据库大都支持,又叫XA Transactions(XAT)。其中 XA 是一个两阶段提交协议,该协议分为以下两个阶段:
- 第一阶段:事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作,并反映是否可以提交
- 第二阶段:事务协调器要求每个数据库提交数据
其中,在第一阶段如果有任何一个数据库否决此次提交,那么所有数据库都会被要求回滚它们在此次事务中的那部分信息。通俗的说在进行一次事务操作时,事务管理器会先询问每个用到的数据库能不能操作成功(数据库进行一次预操作看能否成功),如果任意一个说操作失败,所有的数据库都回滚预操作。
优点:尽量保证了数据的强一致
缺点:实现复杂,可用性降低,同时对性能影响很大
2.补偿事务(TCC)
核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
- Try 阶段主要对业务系统做检测及资源预留
- Confirm 阶段主要对业务系统做确认提交,Try 阶段执行成功然后开始执行 Confirm 阶段时,默认 Confirm 阶段是不会出错的。也就是只要 Try 成功,Confirm 一定成功。
- Cancel 阶段主要在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
优点:跟 2PC 比起来,实现以及流程相对简单了点,但是数据的一致性也差一点
缺点:在第二阶段、第三阶段都由可能失败。对业务代码入侵比较严重,需要在实现的时候多写很多补偿代码。
3.本地消息表(异步补偿)
本地消息应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路来源于 ebay。
基本思路如下:
每个分布式节点都需要额外建一个消息表。
消息生产方,记录消息发送状态。消息表和业务数据放在一个事物里提交,然后消息经过 MQ 发送到消息的消费方,如果消息发送失败,进行重新发送。
消息消费方,需要处理这个消息,并完成自己的业务逻辑,同上也要将消息记录到消息表中,此时如果本地事务处理成功,将消息通过 MQ 发送给下一个消费方,知道所有事物执行完毕,如果事物处理失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作,生产方再向上传递,直到回到初始状态。
生产方和消费方定时扫描本地消息表,把还没处理玩的消息或失败消息再次发送一遍,这需要一个靠谱的自动对账补账逻辑。
这种方案遵循 BASE 理论,采用最终一致性,实现较为简单,性能也不错。
优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点:消息表耦合到了业务系统中。