分布式事务
聊什么是分布式事务前,先聊一下我们熟悉的单机事务。所谓单机事务是相对分布式事务来说的,即数据库事务。大家都知道数据库事务有ACID这四个特性:
原子性:A(Atomicity):指单个事务中的操作要不都执行,要不都不执行
一致性:C(Consistency):指事务前后数据的完整性必须保持一致
隔离性:I(Isolation):指的是多个事务并发执行的时候不会互相干扰,即一个事务内部的数据对于其他事务来说是隔离的。
持久性:D(Durability):指事务提交后,就会被永久存储下来
既然数据库事务有这四个特性的,那么分布式事务也不例外,应该具备这四个特性。
在微服务架构下,服务之间通过RPC远程调用,相对单机事务来说,多了“网络通信”这一不确定因素,使得本来服务的调用只有“成功”和“失败”这两种返回结果,变为“成功”、“失败”和“未知”三种返回结果。
系统之间的通信可靠性从单一系统中的可靠变成了微服务架构之间的不可靠,分布式事务其实就是在不可靠的通信下实现事务的特性。一般因为网络导致的异常可能有机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失、其他异常等等。
分布式事务方案
分布式事务顾名思义就是要在分布式系统中实现事务,它其实是由多个本地事务组合而成。
对于分布式事务而言几乎满足不了 ACID,其实对于单机事务而言大部分情况下也没有满足 ACID,不然怎么会有四种隔离级别呢?所以更别说分布在不同数据库或者不同应用上的分布式事务了。
2PC
2PC(Two-phase commit protocol),中文叫二阶段提交。 二阶段提交是一种强一致性设计,2PC引入一个第三方的节点协调者,即Coordinator,其他参与事务的节点为参与者,即Participants。协调者统筹整个事务行为,负责通知参与者进行Commit还是Rollback操作。2PC的过程比较简单,分为两个阶段:
准备阶段
协调者分别给每个参与者发送Prepare消息,每个参与者收到消息后,进行“预提交”操作(不是实际的提交操作),把操作的结果(成功或失败)返回给协调者。
提交阶段
协调者根据准备阶段收到的参与者的返回结果进行判断,如果所有的参与者都返回成功,那么分别给每个参与者发送Commit消息,否则发送Rollback消息。
2PC是一个强一致性协议,同时它在实际应用中还存在几个问题
(1)同步阻塞,2PC的两个阶段中,协调者和参与者的通信都是同步的,这会导致整个事务的长时间阻塞
(2)Coordinator的单点问题
(3)数据不一致,在Commit阶段,可能存在只有部分参与者收到Commit消息(或处理成功)的情况
3PC
3PC即三阶段提交,它比2PC多了一个阶段,即把原来2PC的准备阶段拆分成CanCommit和PreCommit两个阶段,同时引入超时机制来解决2PC的同步阻塞问题。
3PC 包含了三个阶段,分别是准备阶段、预提交阶段和提交阶段,对应的英文就是:CanCommit、PreCommit 和 DoCommit。
TCC
2PC 和 3PC 都是数据库层面的,而 TCC 是业务层面的分布式事务,就像我前面说的分布式事务不仅仅包括数据库的操作,还包括发送短信等,这时候 TCC 就派上用场了!
TCC 指的是Try - Confirm - Cancel。
Try 指的是预留,即资源的预留和锁定,注意是预留。
Confirm 指的是确认操作,这一步其实就是真正的执行了。
Cancel 指的是撤销操作,可以理解为把预留阶段的动作撤销了。
其实从思想上看和 2PC 差不多,都是先试探性的执行,如果都可以那就真正的执行,如果不行就回滚。
比如说一个事务要执行A、B、C三个操作,那么先对三个操作执行预留动作。如果都预留成功了那么就执行确认操作,如果有一个预留失败那就都执行撤销动作。
本地消息表
本地消息表方案应该是业界内使用最为广泛的,因为它使用简单,成本比较低。本地消息表的方案最初是由 eBay 提出(完整方案),核心思路是将分布式事务拆分成本地事务进行处理。
本地消息表顾名思义就是会有一张存放本地消息的表,一般都是放在数据库中,然后在执行业务的时候将业务的执行和将消息放入消息表中的操作放在同一个事务中,这样就能保证消息放入本地表中业务肯定是执行成功的。
然后再去调用下一个操作,如果下一个操作调用成功了好说,消息表的消息状态可以直接改成已成功。
如果调用失败也没事,会有 后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
这时候有可能消息对应的操作不成功,因此也需要重试,重试就得保证对应服务的方法是幂等的,而且一般重试会有最大次数,超过最大次数可以记录下报警让人工处理。
简单来说:本地消息表把每个业务执行结果记录下来,如果失败,就把消息状态置为失败。
后台任务定时去读取本地消息表,筛选出还未成功的消息再调用对应的服务,服务更新成功了再变更消息的状态。
可以看到本地消息表其实实现的是最终一致性,容忍了数据暂时不一致的情况。
SAGA模式
Saga 模式使用一系列本地事务来提供事务管理。本地事务是由 Saga 参与者执行的原子工作。
每个本地事务都会更新数据库,并发布消息或事件来触发 Saga 中的下一个本地事务。如果本地事务失败,则 Saga 将执行一系列补偿事务,以撤消上一个本地事务所做的更改。
Saga的实现有很多种方式,其中最流行的两种方式是:
基于事件的方式。这种方式没有协调中心,整个模式的工作方式就像舞蹈一样,各个舞蹈演员按照预先编排的动作和走位各自表演,最终形成一只舞蹈。处于当前Saga下的各个服务,会产生某类事件,或者监听其它服务产生的事件并决定是否需要针对监听到的事件做出响应。
在基于事件的方式中,整个模式按照预先编排好的流程,第一个服务执行完本地事务之后,会产生一个事件。其它服务会监听这个事件,触发该服务本地事务的执行,并产生新的事件。
基于命令的方式。这种方式的工作形式就像一只乐队,由一个指挥家(协调中心)来协调大家的工作。协调中心来告诉Saga的参与方应该执行哪一个本地事务。