分布式事务概述

现在手机银行转账已经司空见惯。但是,D瓜哥一直在思考,银卡跨行转账是如何保证事务一致性的?借机就对分布式事务,做了简单地了解。

2PC

两阶段提交(2pc, two-phase commit protocol),2pc是非常经典的强一致性、中心化的原子提交协议。中心化是指协议中有两类节点:一个中心化协调者节点(coordinator)和N个参与者节点(participant、cohort)。

顾名思义,两阶段提交协议的每一次事务提交分为两个阶段:

在第一阶段,协调者询问所有的参与者是否可以提交事务(请参与者投票),所有参与者向协调者投票。

在第二阶段,协调者根据所有参与者的投票结果做出是否事务可以全局提交的决定,并通知所有的参与者执行该决定。在一个两阶段提交流程中,参与者不能改变自己的投票结果。两阶段提交协议的可以全局提交的前提是所有的参与者都同意提交事务,只要有一个参与者投票选择放弃(abort)事务,则事务必须被放弃。

two phase commit process

两阶段提交协议也依赖与日志,只要存储介质不出问题,两阶段协议就能最终达到一致的状态(成功或者回滚)

2pc example
two phase commit diagram

优点

强一致性,只要节点或者网络最终恢复正常,协议就能保证顺利结束;部分关系型数据库(Oracle)、框架直接支持

缺点

  • 网络抖动导致的数据不一致: 第二阶段中协调者向参与者发送commit命令之后,一旦此时发生网络抖动,导致一部分参与者接收到了commit请求并执行,可其他未接到commit请求的参与者无法执行事务提交。进而导致整个分布式系统出现了数据不一致。

  • 超时导致的同步阻塞问题: 2PC中的所有的参与者节点都为事务阻塞型,当某一个参与者节点出现通信超时,其余参与者都会被动阻塞占用资源不能释放。

  • 单点故障的风险: 由于严重的依赖协调者,一旦协调者发生故障,而此时参与者还都处于锁定资源的状态,无法完成事务commit操作。虽然协调者出现故障后,会重新选举一个协调者,可无法解决因前一个协调者宕机导致的参与者处于阻塞状态的问题。

基于两阶段提交的分布式事务在提交事务时需要在多个节点之间进行协调,最大限度地推后了提交事务的时间点,客观上延长了事务的执行时间,这会导致事务在访问共享资源时发生冲突和死锁的概率增高,随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平伸缩的"枷锁", 这是很多Sharding系统不采用分布式事务的主要原因。

3PC

三阶段提交协议(3pc Three-phase_commit_protocol)主要是为了解决两阶段提交协议的阻塞问题,从原来的两个阶段扩展为三个阶段,并且增加了超时机制。

three phase commit protocol

3PC 的三个阶段分别是 CanCommitPreCommitDoCommit

CanCommit

协调者向所有参与者发送CanCommit命令,询问是否可以执行事务提交操作。如果全部响应YES则进入下一个阶段。

PreCommit

协调者向所有参与者发送PreCommit命令,询问是否可以进行事务的预提交操作,参与者接收到PreCommit请求后,如参与者成功的执行了事务操作,则返回Yes响应,进入最终commit阶段。一旦参与者中有向协调者发送了No响应,或因网络造成超时,协调者没有接到参与者的响应,协调者向所有参与者发送abort请求,参与者接受abort命令执行事务的中断。

DoCommit

在前两个阶段中所有参与者的响应反馈均是YES后,协调者向参与者发送DoCommit命令正式提交事务,如协调者没有接收到参与者发送的ACK响应,会向所有参与者发送abort请求命令,执行事务的中断。

3PC只是解决了在异常情况下2PC的阻塞问题,但导致一次提交要传递6条消息,延时很大。

TCC

TCC是Try、Commit、Cancel的缩写,TCC在保证强一致性的同时,最大限度提高系统的可伸缩性与可用性。

TCC(Try-Confirm-Cancel)又被称补偿事务,TCC 与 2PC 的思想很相似,事务处理流程也很相似,但 2PC 是应用于在 DB 层面,TCC 则可以理解为在应用层面的 2PC,是需要我们编写业务逻辑来实现。

TCC 的核心思想是:"针对每个操作都要注册一个与其对应的确认(Try)和补偿(Cancel)"。

tcc

一个完整的业务包含一组子业务,Try操作完成所有的子业务检查,预留必要的业务资源,实现与其他事务的隔离;Confirm使用Try阶段预留的业务资源真正执行业务,而且Confirm操作满足幂等性,以遍支持重试;Cancel操作释放Try阶段预留的业务资源,同样也满足幂等性。“一次完整的交易由一系列微交易的Try 操作组成,如果所有的Try 操作都成功,最终由微交易框架来统一Confirm,否则统一Cancel,从而实现了类似经典两阶段提交协议(2PC)的强一致性。”

tcc process

再来一个例子:

tcc example

与2PC协议比较 ,TCC拥有以下特点:

  • 位于业务服务层而非资源层 ,由业务层保证原子性

  • 没有单独的准备(Prepare)阶段,降低了提交协议的成本

  • Try操作 兼备资源操作与准备能力

  • Try操作可以灵活选择业务资源的锁定粒度,而不是锁住整个资源,提高了并发度

缺点

  • 应用侵入性强:TCC由于基于在业务层面,至使每个操作都需要有 try、confirm、cancel三个接口。

  • 开发难度大:代码开发量很大,要保证数据一致性 confirm 和 cancel 接口还必须实现幂等性。

Seata 中,根据两阶段行为模式的不同,我们将分支事务划分为 Automatic (Branch) Transaction ModeManual (Branch) Transaction Mode.

AT 模式基于 支持本地 ACID 事务关系型数据库

  • 一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。

  • 二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。

  • 二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。

相应的,TCC 模式,不依赖于底层数据资源的事务支持:

  • 一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。

  • 二阶段 commit 行为:调用 自定义 的 commit 逻辑。

  • 二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。

所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。

AT 模式在一定程度上减少了代码量。

Best Effort

best effort即尽最大努力交付,主要用于在这样一种场景:不同的服务平台之间的事务性保证。

best effort

对比

strengths and weaknesses

2PC的强一致性依赖于数据库,而TCC的强一致性依赖于应用层的Commit与cancel。异步消息,1PC,best effort都只保证最终一致性。

2PC需要对整个资源加锁,因此不适用于高并发的分布式场景;而tcc只对需要的资源进行加锁,加锁的粒度小,且try commit Cancel都是本地短事务,因此能在保证强一致性的同时最大化提高系统可用性。而异步消息,1PC,best effort都是先提交一部分事务,无需加锁。

2PC是有数据库来保证回滚,而TCC是应用层实现回滚:为每一个try操作提供一个对应的cancel操作。而异步消息,1PC适用于理论上一定会成功的场景,难以回滚。best effort这种模式,需要服务的调用者实现完整的一个事务操作用于回滚。

串并行与LPO

一个重要的优化: “最末参与者优化”(Last Participant Optimization,术语来自支付宝),即允许两阶段提交协议中有一个参与者不实现“准备”操作,在其余参与者都prepare ok的情况下,直接提交自己的分式事务。

lpo success
lpo failure

本质上,LPO是将最后一个参与者的准备操作与提交/放弃操作合并成一个提交操作,这样提高了分布式事务的执行效率。也可以看到,要使用LPO,在prepare阶段一定是串行的。

案例分析

example

在上图中,使用了三种分布式事务解决办法:

  1. 基于可靠消息的最终一致性方案(异步确保型),这个使用比较广,适用于分支事务大概率成功的情况;

    上图中使用于:对应支付系统会计异步记账业务,银行通知结果信息存储与驱动订单处理

  2. TCC事务补偿性方案,使用在同时需要保证一致性与高性能的场景

    对应上图中支付系统的订单账户操作:订单处理,资金账户处理,积分账户处理

  3. best effort,最大努力通知型方案,适用于跨平台之间的事务原子性保证

    对应上图中支付系统的商户业务通知场景