分布式事务是指事务的参与者、支持事务的服务器、资源服务器及事务管理器分别位于分布式系统的不同节点上。
分布式事务问题也叫分布式数据一致性问题,简单来说就是如何在分布式场景中,保证多个节点数据的一致性。分布式事务产生的核心原因在于存储资源的 分布性,比如多个数据库,或者 MySQL和 Redis 两种不同存储设备的的数据一致性等。
X/Open DTP (X/Open Distributed Transaction Processing Reference Model) 是 X/Open 组织定义的一套分布式事务的标准。这个标准提出了使用 两阶段提交(2PC,Two-Phase-Commit)来保证分布式事务的完整性。
X/Open DTP 中包含一下三种角色:
上图展示的角色和关系与本地事务的原理基本相同,唯一不同的在于,如果此时 RM 代表数据库,那么 TM 需要能够管理多个数据库的事务,大致实现步骤如下:
XID
),XID
会通知各个 RMXID
传递给 RM需要注意的是,TM 和 多个 RM 之间的事务控制,是基于 XA
协议(XA Specification)来完成的。XA
协议是 X/Open
提出的分布式事务处理规范,也是分布式事务处理的工业标准,它定义了 ax_
和 xa_
系列的函数原型及功能描述、约束等。
两阶段提交协议的执行流程如下:
两阶段提交将一个事务的处理过程分为投票和执行两个阶段,它的优点在于充分考虑了分布式系统的不可靠因素,并且采用非常简单的方式(两阶段提交)就把由于系统不可靠而导致事务提交失败的概率降到最小。当然,它也存在以下缺点:
三阶段提交协议是两阶段提交协议的改进版本,它利用 超时机制 解决了 同步阻塞 的问题。
具体描述如下:
三阶段提交协议的时序图如图:
与两阶段提交协议的不同点:
CanCommit
阶段,用于询问所有参与者是否可以执行事务操作并且响应,它的好处是,可以尽早发现无法执行的操作而中止后续的行为。DoCommit
阶段,事务协调者和参与者都引入了 超时机制,一旦超时,事务协调者和参与者会继续提交事务,并且任务处于 成功状态,因为在这种情况下事务默认为成功的可能性较大。实际上,一旦超时,在三阶段提交协议下仍然可能出现数据不一致的情况,当然概率是比较小的。另外,最大的好处就是 基于超时机制来避免资源的永久锁定。
CAP 定理,又称为布鲁尔定理。简单来说它是指在分布式系统中不可能同时能满足 一致性(C:Consistency)、可用性(A:Availablity)、分区容错性(P:Partition Tolerance)这三个基本需求,最多同时满足两个。
BASE 理论由于 CAP 中的一致性和可用性不可兼得而衍生的一种新的思想,BASE 理论的核心思想是通过 牺牲数据的强一致性来获得高可用性。有如下特性
TCC
(Try-Confirm-Cancel)是一种比较成熟的分布式数据一致性解决方案,它实际上把一个完整的业务拆分为如下三个步骤。
Try
阶段预留的资源。Try
阶段预留的资源。
- 先是服务调用链路依次执行 Try 逻辑
- 如果都正常的话,TCC 分布式事务框架推进执行 Confirm 逻辑,完成整个事务
- 如果某个服务的 Try 逻辑有问题,TCC 分布式事务框架感知到之后就会推进执行各个服务的 Cancel 逻辑,撤销之前执行的各种操作
其实 TCC
是一种两阶段提交的思想,第一阶段通过 Try
进行准备工作,第二阶段 Comfirm/Cancel 表示 Try
阶段操作的确认和回滚。在分布式事务场景中,每个服务实现 TCC
后,就作为其中的一个资源,参与到整个分布式事务中。然后主业务服务在第一阶段中分别调用所有 TCC
服务的 Try
方法。最后根据第一阶段的执行情况来决定对第二阶段的 Confirm 或者 Cancel。执行流程如下:
在一些特殊情况下,如果 TCC
服务宕机或者出现异常,导致该服务没有收到 TCC
事务协调器的 Cancel 或者 Confirm 请求,TCC
事务框架会记录一些分布式事务的操作日志,保存分布式事务运行的各个阶段和状态。TCC
事务协调器会根据操作日志来进行重试,以达到数据的最终一致性。
需要注意的是,TCC
服务支持接口调用失败发起重试,所以 TCC
暴露的接口都需要满足 幂等性。
主要利用消息中间件(Kafka、RocketMQ 或 RabbitMQ)的可靠性机制来实现数据一致性的投递。
例如:电商支付场景,大部分电商平台基于营销策略,在支付后,会给用户账户增加一定的积分奖励。所以,当系统接收到第三方返回的支付结果时,需要更新支付服务的支付状态,以及更新账户服务的积分余额,这里就涉及到两个服务的数据一致性问题。这里的数据一致性并不要求实时性,所以可采用基于可靠性消息的最终一致性方案来保证支付服务和账户服务的数据一致性。如下图:支付服务在收到支付结果通知后,先更新支付订单的状态,再发送一条消息到分布式消息队列中,账户服务会监听到指定队列的消息并做响应的处理,完成数据的同步。
在上图的解决方案中,有一个支付服务本地事务与发送消息这个操作的原子性问题,
以上问题也有成熟的解决方案,以 RocketMQ 为例,它提供了事务消息模型,如下描述:
最大努力通知型和基于消息可靠性消息的最终一致性方案的实现是类似的,也比较适用于对 数据一致性要求不高 的场景,最典型的使用场景就是支付宝支付结果的通知,实现流程如下图:
站在商户的角度分析最大努力通知型的处理过程。
从上述分析发现,最大努力通知,就是在商户端如果没有返回一个消息确认时,支付宝会不断地进行重试,直到收到一个消息确认或达到最大重试次数。
Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务,它提供了 AT、TCC、Saga 和 XA 事务模式。
AT 模式是 Seata 最主推的分布式事务解决方案,它是基于 XA 演进而来的一种分布式事务模式,它同样有三大模块,分别是 TM、RM 和 TC,其中 TM 和 RM 作为 Seata 的客户端与业务系统集成,TC 作为 Seata 的服务器独立部署。TM 表示事务管理器(Transaction Manager),它负责向 TC(Transaction Coordinator,事务协调器) 注册一个全局事务,并生成全局唯一的 XID
。在 AT 模式下,每个数据库资源被当作一个 RM(Resource Manager),在业务层面通过 JDBC 标准的接口访问 RM 时,Seata 会对所有的请求进行拦截。每个本地事务进行提交时,RM 都会向 TC 注册一个分支事务。AT 模式 和 XA 模式一样,也是一种两阶段提交事务模型。
具体执行流程如下:
Saga 模式又称为长事务解决方案,主要描述是在没有两阶段提交的情况下如何解决分布式事务问题。其核心思想是:把一个业务流程中的长事务拆分为多个本地短事务,业务流程中的每个参与者都提交 真实的提交 给本地短事务,当其中一个参与者事务执行失败,则通过补偿机制补偿前面已经成功的参与者。
如下图,Saga 由一系列的 sub-transaction Ti 组成,每个 Ti 都有对应的补偿动作 Ci ,补偿动作用于撤销 Ti 造成的数据变更结果。它和 TCC 相比,少了 Try 这个预留动作,每一个 Ti 操作都真实地影响到数据库。
另外,Saga 提供了以下两种补偿恢复方式。
不管是向前恢复还是向后恢复,都可能会出现失败的情况,在最坏的情况下只能人工干预处理。
以电商品台下单场景为例,一般会设计订单的创建、商品库存的扣减、钱包支付、积分赠送等操作,整个时序图如下:
电商平台下单的流程是一个典型的长事务场景,根据 Saga 模式的定义,先将长事务拆分成多个本地短事务,每个服务的本地事务按照执行顺序逐一提交,一旦其中一个服务的事务出现异常,则采用补偿的方式逐一撤回。这一过程会涉及 Saga 的协调模式,它有两种常用的协调模式。
在基于事件的编排模式中,第一个服务执行完一个本地事务之后,发送一个事件。这个事件会被一个或者多个服务监听,监听到事件的服务再执行本地事务并发布新的事件,此后一直延续这种事件触发模式,直到业务流程中最后一个服务的本地事务执行结束。
命令/协同需要定义一个 Saga 协调器,负责告诉每一个参与者该做什么,Saga 协调器以命令/回复的方式与每项服务进行通信。
需要注意的是,订单 Saga 协调器必须提前知道 ”创建订单事务“ 的所有流程(Seata 是通过基于 JSON 的状态机引擎来实现的),并且在整个流程中任何一个环节执行失败,它都需要向每个参与者发送命令撤销之前的事务操作。
[1] 分布式理论之CAP定理(布鲁尔定理) - SegmentFault 思否
[2] 《Spring Cloud Alibaba 微服务原理与实战》