理解分布式事务

文章首发于51CTO技术栈公众号
做者 陈彩华
文章转载交流请联系 caison@aliyun.com
复制代码

这篇文章将介绍什么是分布式事务,分布式事务解决什么问题,对分布式事务实现的难点,解决思路,不一样场景下方案的选择,经过图解的方式进行梳理、总结和比较。node

相信耐心看完这篇文章,谈到分布式事务,再也不只是有“2PC”、“3PC”、“MQ的消息事务”、“最终一致性”、“TCC”等这些知识碎片,而是可以将知识连成一片,造成知识体系。mysql

1 什么是事务

介绍分布式事务以前,先介绍什么是事务。git

事务的具体定义

事务提供一种机制将一个活动涉及的全部操做归入到一个不可分割的执行单元,组成事务的全部操做只有在全部操做均能正常执行的状况下方能提交,只要其中任一操做执行失败,都将致使整个事务的回滚。github

简单地说,事务提供一种“ 要么什么都不作,要么作全套(All or Nothing)”机制。 算法

事务

数据库事务的ACID属性

事务是基于数据进行操做,须要保证事务的数据一般存储在数据库中,因此介绍到事务,就不得不介绍数据库事务的ACID特性,指数据库事务正确执行的四个基本特性的缩写。包含:sql

  • 原子性(Atomicity) 整个事务中的全部操做,要么所有完成,要么所有不完成,不可能停滞在中间某个环节。事务在执行过程当中发生错误,会被 回滚(Rollback)到事务开始前的状态,就像这个事务历来没有执行过同样。 例如:银行转帐,从A帐户转100元至B帐户,分为两个步骤:数据库

    • (1)从A帐户取100元
    • (2)存入100元至B帐户。 这两步要么一块儿完成,要么一块儿不完成,若是只完成第一步,第二步失败,钱会莫名其妙少了100元。
  • 一致性(Consistency) 在事务开始以前和事务结束之后,数据库数据的一致性约束没有被破坏。 例如:现有完整性约束A+B=100,若是一个事务改变了A,那么必须得改变B,使得事务结束后依然知足A+B=100,不然事务失败。apache

  • 隔离性(Isolation) 数据库容许多个并发事务同时对数据进行读写和修改的能力,若是一个事务要访问的数据正在被另一个事务修改,只要另一个事务未提交,它所访问的数据就不受未提交事务的影响。隔离性能够防止多个事务并发执行时因为交叉执行而致使数据的不一致。 例如:现有有个交易是从A帐户转100元至B帐户,在这个交易事务还未完成的状况下,若是此时B查询本身的帐户,是看不到新增长的100元的。编程

  • 持久性(Durability) 事务处理结束后,对数据的修改就是永久的,即使系统故障也不会丢失。缓存

数据库事务的ACID特性

简单而言,ACID是从不一样维度描述事务的特性:

  • 原子性 —— 事务操做的总体性
  • 一致性 —— 事务操做下数据的正确性
  • 隔离性 —— 事务并发操做下数据的正确性
  • 持久性 —— 事务对数据修改的可靠性

一个支持事务(Transaction)的数据库,须要具备这4种特性,不然在事务过程中没法保证数据的正确性,处理结果很可能达不到请求方的要求。

何时使用数据库事务

在介绍完事务基本概念以后,何时该使用数据库事务? 简单而言,就是业务上有一组数据操做,须要若是其中有任何一个操做执行失败,整组操做所有不执行并恢复到未执行状态,要么所有成功,要么所有失败。

在使用数据库事务时须要注意,尽量短的保持事务,修改多个不一样表的数据的冗长事务会严重妨碍系统中的全部其余用户,这颇有可能致使一些性能问题。

2 什么是分布式事务

介绍完事务相关基本概念以后,下面介绍分布式事务。

分布式产生背景与概念

随着互联网快速发展,微服务,SOA等服务架构模式正在被大规模的使用,如今分布式系统通常由多个独立的子系统组成,多个子系统经过网络通讯互相协做配合完成各个功能。

有不少用例会跨多个子系统才能完成,比较典型的是电子商务网站的下单支付流程,至少会涉及交易系统和支付系统,并且这个过程当中会涉及到事务的概念,即保证交易系统和支付系统的数据一致性,此处咱们称这种跨系统的事务为分布式事务,具体一点而言,分布式事务是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不一样的分布式系统的不一样节点之上。

举个互联网经常使用的交易业务为例:

上图中包含了库存和订单两个独立的微服务,每一个微服务维护了本身的数据库。在交易系统的业务逻辑中,一个商品在下单以前须要先调用库存服务,进行扣除库存,再调用订单服务,建立订单记录。

能够看到,若是多个数据库之间的数据更新没有保证事务,将会致使出现子系统数据不一致,业务出现问题。

分布式事务的难点

  • 事务的原子性 事务操做跨不一样节点,当多个节点某一节点操做失败时,须要保证多节点操做的**要么什么都不作,要么作全套(All or Nothing)**的原子性。
  • 事务的一致性 当发生网络传输故障或者节点故障,节点间数据复制通道中断,在进行事务操做时须要保证数据一致性,保证事务的任何操做都不会使得数据违反数据库定义的约束、触发器等规则。
  • 事务的隔离性 事务隔离性的本质就是如何正确多个并发事务的处理的读写冲突和写写冲突,由于在分布式事务控制中,可能会出现提交不一样步的现象,这个时候就有可能出现“部分已经提交”的事务。此时并发应用访问数据若是没有加以控制,有可能出现“脏读”问题。

3 分布式系统的一致性

前面介绍到的分布式事务的难点涉及的问题,最终影响是致使数据出现不一致,下面对分布式系统的一致性问题进行理论分析,后面将基于这些理论进行分布式方案的介绍。

可用性和一致性的冲突 —— CAP理论

CAP

CAP 定理又被称做布鲁尔定理,是加州大学的计算机科学家布鲁尔在 2000 年提出的一个猜测。2002 年,麻省理工学院的赛斯·吉尔伯特和南希·林奇发表了布鲁尔猜测的证实,使之成为分布式计算领域公认的一个定理。

布鲁尔在提出CAP猜测时并无具体定义 Consistency、Availability、Partition Tolerance 这3个词的含义,不一样资料的具体定义也有差异,为了更好地解释,下面选择Robert Greiner的文章《CAP Theorem》做为参考基础。

  • CAP理论的定义 在一个分布式系统(指互相链接并共享数据的节点的集合)中,当涉及读写操做时,只能保证一致性(Consistence)、可用性(Availability)、分区容错性(PartitionTolerance)三者中的两个,另一个必须被牺牲。

Consistency、Availability、Partition Tolerance具体解释以下:

  • C - Consistency 一致性

A read is guaranteed to return the most recent write for a given client. 对某个指定的客户端来讲,读操做保证可以返回最新的写操做结果。

这里并非强调同一时刻拥有相同的数据,对于系统执行事务来讲,在事务执行过程当中,系统其实处于一个不一致的状态,不一样的节点的数据并不彻底一致。

一致性强调客户端读操做可以获取最新的写操做结果,是由于事务在执行过程当中,客户端是没法读取到未提交的数据的,只有等到事务提交后,客户端才能读取到事务写入的数据,而若是事务失败则会进行回滚,客户端也不会读取到事务中间写入的数据。

  • A - Availability 可用性

A non-failing node will return a reasonable response within a reasonable amount of time (no error or timeout). 非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。

这里强调的是合理的响应,不能超时,不能出错。注意并无说“正确”的结果,例如,应该返回 100 但实际上返回了 90,确定是不正确的结果,但能够是一个合理的结果。

  • P - Partition Tolerance 分区容忍性

The system will continue to function when network partitions occur. 当出现网络分区后,系统可以继续“履行职责”。

这里网络分区是指: 一个分布式系统里面,节点组成的网络原本应该是连通的。然而可能由于一些故障(节点间网络链接断开、节点宕机),使得有些节点之间不连通了,整个网络就分红了几块区域,数据就散布在了这些不连通的区域中。

一致性、可用性、分区容忍性的选择

虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,咱们会发现必须选择 P(分区容忍)要素,由于网络自己没法作到 100% 可靠,有可能出故障,因此分区是一个必然的现象。

若是咱们选择了 CA(一致性 + 可用性) 而放弃了 P(分区容忍性),那么当发生分区现象时,为了保证 C(一致性),系统须要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不容许写入),这又和 A(可用性) 冲突了,由于 A(可用性)要求返回 no error 和 no timeout。

所以,分布式系统理论上不可能选择 CA (一致性 + 可用性)架构,只能选择 CP(一致性 + 分区容忍性) 或者 AP (可用性 + 分区容忍性)架构,在一致性和可用性作折中选择

  • CP - Consistency + Partition Tolerance (一致性 + 分区容忍性)
    CP模型

如上图所示,由于Node1节点和Node2节点链接中断致使分区现象,Node1节点的数据已经更新到y,可是Node1 和 Node2 之间的复制通道中断,数据 y 没法同步到 Node2,Node2 节点上的数据仍是旧数据x。

这时客户端C 访问 Node2 时,Node2 须要返回 Error,提示客户端 “系统如今发生了错误”,这种处理方式违 背了可用性(Availability)的要求,所以 CAP 三者只能知足 CP。

  • AP - Availability + Partition Tolerance (可用性 + 分区容忍性)
    AP模型

一样是Node2 节点上的数据仍是旧数据x,这时客户端C 访问 Node2 时,Node2 将当前本身拥有的数据 x 返回给客户端 了,而实际上当前最新的数据已是 y 了,这就不知足一致性(Consistency)的要求了,所以 CAP 三者只能知足 AP。

注意:这里 Node2 节点返回 x,虽然不是一个“正确”的结果,可是一个“合理”的结果,由于 x 是旧的数据,并非一个错乱的值,只是否是最新的数据。

值得补充的是,CAP理论告诉咱们分布式系统只能选择AP或者CP,但实际上并非说整个系统只能选择AP或者CP,在 CAP 理论落地实践时,咱们须要将系统内的数据按照不一样的应用场景和要求进行分类,每类数据选择不一样的策略(CP 仍是 AP),而不是直接限定整个系统全部数据都是同一策略。

另外,只能选择CP或者AP是指系统发生分区现象时没法同时保证C(一致性)和A(可用性),但不是意味着什么都不作,当分区故障解决后,系统仍是要保持保证CA。

CAP理论的延伸——BASE理论

BASE

BASE 是指基本可用(Basically Available)、软状态( Soft State)、最终一致性( Eventual Consistency),核心思想是即便没法作到强一致性(CAP 的一致性就是强一致性),但应用能够采用适合的方式达到最终一致性。

  • BA - Basically Available 基本可用 分布式系统在出现故障时,容许损失部分可用性,即保证核心可用。

这里的关键词是“部分”和“核心”,实际实践上,哪些是核心须要根据具体业务来权衡。例如登陆功能相对注册功能更加核心,注册不了最多影响流失一部分用户,若是用户已经注册但没法登陆,那就意味用户没法使用系统,形成的影响范围更大。

  • S - Soft State 软状态 容许系统存在中间状态,而该中间状态不会影响系统总体可用性。这里的中间状态就是 CAP 理论中的数据不一致。

  • E - Eventual Consistency 最终一致性 系统中的全部数据副本通过必定时间后,最终可以达到一致的状态。

这里的关键词是“必定时间” 和 “最终”,“必定时间”和数据的特性是强关联的,不一样业务不一样数据可以容忍的不一致时间是不一样的。例如支付类业务是要求秒级别内达到一致,由于用户时时关注;用户发的最新微博,能够容忍30分钟内达到一致的状态,由于用户短期看不到明星发的微博是无感知的。而“最终”的含义就是无论多长时间,最终仍是要达到一致性的状态。

BASE 理论本质上是对 CAP 的延伸和补充,更具体地说,是对 CAP 中 AP 方案的一个补充:

  • CAP 理论是忽略延时的,而实际应用中延时是没法避免的。 这一点就意味着完美的 CP 场景是不存在的,即便是几毫秒的数据复制延迟,在这几毫秒时间间隔内,系统是不符合 CP 要求的。所以 CAP 中的 CP 方案,实际上也是实现了最终一致性,只是“必定时间”是指几毫秒而已。

  • AP 方案中牺牲一致性只是指发生分区故障期间,而不是永远放弃一致性。 这一点其实就是 BASE 理论延伸的地方,分区期间牺牲一致性,但分区故障恢复后,系统应该达到最终一致性。

数据一致性模型

前面介绍的BASE模型提过“强一致性”和“最终一致性”,下面对这些一致性模型展开介绍。

分布式系统经过复制数据来提升系统的可靠性和容错性,而且将数据的不一样的副本存放在不一样的机器上,因为维护数据副本的一致性代价很高,所以许多系统采用弱一致性来提升性能,下面介绍常见的一致性模型:

  • 强一致性 要求不管更新操做是在哪一个数据副本上执行,以后全部的读操做都要能得到最新的数据。对于单副本数据来讲,读写操做是在同一数据上执行的,容易保证强一致性。对多副本数据来讲,则须要使用分布式事务协议。

  • 弱一致性 在这种一致性下,用户读到某一操做对系统特定数据的更新须要一段时间,咱们将这段时间称为"不一致性窗口"。

  • 最终一致性 是弱一致性的一种特例,在这种一致性下系统保证用户最终可以读取到某操做对系统特定数据的更新(读取操做以前没有该数据的其余更新操做)。"不一致性窗口"的大小依赖于交互延迟、系统的负载,以及数据的副本数等。

系统选择哪一种一致性模型取决于应用对一致性的需求,所选取的一致性模型还会影响到系统如何处理用户的请求以及对副本维护技术的选择等。后面将基于上面介绍的一致性模型分别介绍分布式事务的解决方案。

柔性事务

柔性事务的概念

在电商等互联网场景下,传统的事务在数据库性能和处理能力上都暴露出了瓶颈。在分布式领域基于CAP理论以及BASE理论,有人就提出了柔性事务的概念。

基于BASE理论的设计思想,柔性事务下,在不影响系统总体可用性的状况下(Basically Available 基本可用),容许系统存在数据不一致的中间状态(Soft State 软状态),在通过数据同步的延时以后,最终数据可以达到一致。并非彻底放弃了ACID,而是经过放宽一致性要求,借助本地事务来实现最终分布式事务一致性的同时也保证系统的吞吐

实现柔性事务的一些特性

下面介绍的是实现柔性事务的一些常见特性,这些特性在具体的方案中不必定都要知足,由于不一样的方案要求不同。

可见性(对外可查询) 在分布式事务执行过程当中,若是某一个步骤执行出错,就须要明确的知道其余几个操做的处理状况,这就须要其余的服务都可以提供查询接口,保证能够经过查询来判断操做的处理状况。

为了保证操做的可查询,须要对于每个服务的每一次调用都有一个全局惟一的标识,能够是业务单据号(如订单号)、也能够是系统分配的操做流水号(如支付记录流水号)。除此以外,操做的时间信息也要有完整的记录。

操做幂等性 幂等性,实际上是一个数学概念。幂等函数,或幂等方法,是指可使用相同参数重复执行,并能得到相同结果的函数。幂等操做的特色是其任意屡次执行所产生的影响均与一次执行的影响相同。也就是说,同一个方法,使用一样的参数,调用屡次产生的业务结果与调用一次产生的业务结果相同。

之因此须要操做幂等性,是由于为了保证数据的最终一致性,不少事务协议都会有不少重试的操做,若是一个方法不保证幂等,那么将没法被重试。幂等操做的实现方式有多种,如在系统中缓存全部的请求与处理结果、检测到重复操做后,直接返回上一次的处理结果等。

4 常见分布式事务解决方案

介绍完分布式系统的一致性相关理论,下面基于不一样的一致性模型介绍分布式事务的常看法决方案,后面会再介绍各个方案的使用场景。

分布式事务的实现有许多种,其中较经典是由Tuxedo提出的XA分布式事务协议,XA协议包含二阶段提交(2PC)和三阶段提交(3PC)两种实现。

4.1 2PC(二阶段提交)方案 —— 强一致性

方案简介

二阶段提交协议(Two-phase Commit,即2PC)是经常使用的分布式事务解决方案,即将事务的提交过程分为两个阶段来进行处理:准备阶段和提交阶段。事务的发起者称协调者,事务的执行者称参与者。

在分布式系统里,每一个节点均可以知晓本身操做的成功或者失败,却没法知道其余节点操做的成功或失败。当一个事务跨多个节点时,为了保持事务的原子性与一致性,而引入一个协调者来统一掌控全部参与者的操做结果,并指示它们是否要把操做结果进行真正的提交或者回滚(rollback)。

二阶段提交的算法思路能够归纳为:参与者将操做成败通知协调者,再由协调者根据全部参与者的反馈情报决定各参与者是否要提交操做仍是停止操做

核心思想就是对每个事务都采用先尝试后提交的处理方式,处理后全部的读操做都要能得到最新的数据,所以也能够将二阶段提交看做是一个强一致性算法。

处理流程

简单一点理解,能够把协调者节点比喻为带头大哥,参与者理解比喻为跟班小弟,带头大哥统一协调跟班小弟的任务执行。

阶段1:准备阶段

  • 一、协调者向全部参与者发送事务内容,询问是否能够提交事务,并等待全部参与者答复。
  • 二、各参与者执行事务操做,将undo和redo信息记入事务日志中(但不提交事务)。
  • 三、如参与者执行成功,给协调者反馈yes,便可以提交;如执行失败,给协调者反馈no,即不可提交。

阶段2:提交阶段 若是协调者收到了参与者的失败消息或者超时,直接给每一个参与者发送回滚(rollback)消息;不然,发送提交(commit)消息;参与者根据协调者的指令执行提交或者回滚操做,释放全部事务处理过程当中使用的锁资源。(注意:必须在最后阶段释放锁资源) 接下来分两种状况分别讨论提交阶段的过程。

状况1,当全部参与者均反馈yes,提交事务

事务正常提交

  • 一、协调者向全部参与者发出正式提交事务的请求(即commit请求)。
  • 二、参与者执行commit请求,并释放整个事务期间占用的资源。
  • 三、各参与者向协调者反馈ack(应答)完成的消息。
  • 四、协调者收到全部参与者反馈的ack消息后,即完成事务提交。

状况2,当任何阶段1一个参与者反馈no,中断事务

事务中断

  • 一、协调者向全部参与者发出回滚请求(即rollback请求)。
  • 二、参与者使用阶段1中的undo信息执行回滚操做,并释放整个事务期间占用的资源。
  • 三、各参与者向协调者反馈ack完成的消息。
  • 四、协调者收到全部参与者反馈的ack消息后,即完成事务中断。

方案总结

2PC方案实现起来简单,实际项目中使用比较少,主要由于如下问题:

  • 性能问题 全部参与者在事务提交阶段处于同步阻塞状态,占用系统资源,容易致使性能瓶颈。
  • 可靠性问题 若是协调者存在单点故障问题,若是协调者出现故障,参与者将一直处于锁定状态。
  • 数据一致性问题 在阶段2中,若是发生局部网络问题,一部分事务参与者收到了提交消息,另外一部分事务参与者没收到提交消息,那么就致使了节点之间数据的不一致。

4.2 3PC(三阶段提交)方案

方案简介

三阶段提交协议,是二阶段提交协议的改进版本,与二阶段提交不一样的是,引入超时机制。同时在协调者和参与者中都引入超时机制。

三阶段提交将二阶段的准备阶段拆分为2个阶段,插入了一个preCommit阶段,使得原先在二阶段提交中,参与者在准备以后,因为协调者发生崩溃或错误,而致使参与者处于没法知晓是否提交或者停止的“不肯定状态”所产生的可能至关长的延时的问题得以解决。

处理流程

阶段1:canCommit 协调者向参与者发送commit请求,参与者若是能够提交就返回yes响应(参与者不执行事务操做),不然返回no响应:

  • 一、协调者向全部参与者发出包含事务内容的canCommit请求,询问是否能够提交事务,并等待全部参与者答复。
  • 二、参与者收到canCommit请求后,若是认为能够执行事务操做,则反馈yes并进入预备状态,不然反馈no。

阶段2:preCommit 协调者根据阶段1 canCommit参与者的反应状况来决定是否能够基于事务的preCommit操做。根据响应状况,有如下两种可能。

状况1:阶段1全部参与者均反馈yes,参与者预执行事务:

阶段2预执行事务

  • 一、协调者向全部参与者发出preCommit请求,进入准备阶段。
  • 二、参与者收到preCommit请求后,执行事务操做,将undo和redo信息记入事务日志中(但不提交事务)。
  • 三、各参与者向协调者反馈ack响应或no响应,并等待最终指令。

状况2:阶段1任何一个参与者反馈no,或者等待超时后协调者尚没法收到全部参与者的反馈,即中断事务:

阶段2中断事务

  • 一、协调者向全部参与者发出abort请求。
  • 二、不管收到协调者发出的abort请求,或者在等待协调者请求过程当中出现超时,参与者均会中断事务。

阶段3:do Commit 该阶段进行真正的事务提交,也能够分为如下两种状况:

状况1:阶段2全部参与者均反馈ack响应,执行真正的事务提交:

阶段3正式执行事务

  • 一、若是协调者处于工做状态,则向全部参与者发出do Commit请求。
  • 二、参与者收到do Commit请求后,会正式执行事务提交,并释放整个事务期间占用的资源。
  • 三、各参与者向协调者反馈ack完成的消息。
  • 四、协调者收到全部参与者反馈的ack消息后,即完成事务提交。

阶段2任何一个参与者反馈no,或者等待超时后协调者尚没法收到全部参与者的反馈,即中断事务:

中断事务

  • 一、若是协调者处于工做状态,向全部参与者发出abort请求。
  • 二、参与者使用阶段1中的undo信息执行回滚操做,并释放整个事务期间占用的资源。
  • 三、各参与者向协调者反馈ack完成的消息。
  • 四、协调者收到全部参与者反馈的ack消息后,即完成事务中断。

注意:进入阶段3后,不管协调者出现问题,或者协调者与参与者网络出现问题,都会致使参与者没法接收到协调者发出的do Commit请求或abort请求。此时,参与者都会在等待超时以后,继续执行事务提交。

方案总结

  • 优势 相比二阶段提交,三阶段贴近下降了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题,阶段3中协调者出现问题时,参与者会继续提交事务。

  • 缺点 数据不一致问题依然存在,当在参与者收到preCommit请求后等待do commite指令时,此时若是协调者请求中断事务,而协调者没法与参与者正常通讯,会致使参与者继续提交事务,形成数据不一致。

4.3 TCC (Try-Confirm-Cancel)事务 —— 最终一致性

方案简介

TCC(Try-Confirm-Cancel)的概念,最先是由Pat Helland于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。

TCC是服务化的二阶段编程模型,其Try、Confirm、Cancel 3个方法均由业务编码实现;

  • Try操做做为一阶段,负责资源的检查和预留。
  • Confirm操做做为二阶段提交操做,执行真正的业务。
  • Cancel是预留资源的取消。

TCC事务的Try、Confirm、Cancel能够理解为SQL事务中的Lock、Commit、Rollback。

处理流程

为了方便理解,下面以电商下单为例进行方案解析,这里把整个过程简单分为扣减库存,订单建立2个步骤,库存服务和订单服务分别在不一样的服务器节点上。

一、Try 阶段 从执行阶段来看,与传统事务机制中业务逻辑相同。但从业务角度来看,却不同。TCC机制中的Try仅是一个初步操做,它和后续的确认一块儿才能真正构成一个完整的业务逻辑,这个阶段主要完成:

  • 完成全部业务检查( 一致性 )
  • 预留必须业务资源( 准隔离性 )
  • Try 尝试执行业务 TCC事务机制以初步操做(Try)为中心的,确认操做(Confirm)和取消操做(Cancel)都是围绕初步操做(Try)而展开。所以,Try阶段中的操做,其保障性是最好的,即便失败,仍然有取消操做(Cancel)能够将其执行结果撤销。

Try阶段

假设商品库存为100,购买数量为2,这里检查和更新库存的同时,冻结用户购买数量的库存,同时建立订单,订单状态为待确认。

二、Confirm / Cancel 阶段 根据Try阶段服务是否所有正常执行,继续执行确认操做(Confirm)或取消操做(Cancel)。 Confirm和Cancel操做知足幂等性,若是Confirm或Cancel操做执行失败,将会不断重试直到执行完成。

Confirm:当Try阶段服务所有正常执行, 执行确认业务逻辑操做

Confirm

这里使用的资源必定是Try阶段预留的业务资源。在TCC事务机制中认为,若是在Try阶段能正常的预留资源,那Confirm必定能完整正确的提交。Confirm阶段也能够当作是对Try阶段的一个补充,Try+Confirm一块儿组成了一个完整的业务逻辑。

Cancel:当Try阶段存在服务执行失败, 进入Cancel阶段

Cancel
Cancel取消执行,释放Try阶段预留的业务资源,上面的例子中,Cancel操做会把冻结的库存释放,并更新订单状态为取消。

方案总结

TCC事务机制相对于传统事务机制(X/Open XA),TCC事务机制相比于上面介绍的XA事务机制,有如下优势:

  • 性能提高 具体业务来实现控制资源锁的粒度变小,不会锁定整个资源。
  • 数据最终一致性 基于Confirm和Cancel的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
  • 可靠性 解决了XA协议的协调者单点故障问题,由主业务方发起并控制整个业务活动,业务活动管理器也变成多点,引入集群。

缺点: TCC的Try、Confirm和Cancel操做功能要按具体业务来实现,业务耦合度较高,提升了开发成本。

4.4 本地消息表 —— 最终一致性

方案简介

本地消息表的方案最初是由ebay提出,核心思路是将分布式事务拆分红本地事务进行处理。

方案经过在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。

这样设计能够避免”业务处理成功 + 事务消息发送失败",或"业务处理失败 + 事务消息发送成功"的棘手状况出现,保证2个系统事务的数据一致性。

处理流程

下面把分布式事务最早开始处理的事务方成为事务主动方,在事务主动方以后处理的业务内的其余事务成为事务被动方。

为了方便理解,下面继续以电商下单为例进行方案解析,这里把整个过程简单分为扣减库存,订单建立2个步骤,库存服务和订单服务分别在不一样的服务器节点上,其中库存服务是事务主动方,订单服务是事务被动方。

事务的主动方须要额外新建事务消息表,用于记录分布式事务的消息的发生、处理状态。

整个业务处理流程以下:

本地消息表方案

  • 步骤1 事务主动方处理本地事务。 事务主动发在本地事务中处理业务更新操做和写消息表操做。 上面例子中库存服务阶段再本地事务中完成扣减库存和写消息表(图中一、2)。
  • 步骤2 事务主动方经过消息中间件,通知事务被动方处理事务通知事务待消息。 消息中间件能够基于Kafka、RocketMQ消息队列,事务主动方法主动写消息到消息队列,事务消费方消费并处理消息队列中的消息。 上面例子中,库存服务把事务待处理消息写到消息中间件,订单服务消费消息中间件的消息,完成新增订单(图中3 - 5)。
  • 步骤3 事务被动方经过消息中间件,通知事务主动方事务已处理的消息。 上面例子中,订单服务把事务已处理消息写到消息中间件,库存服务消费中间件的消息,并将事务消息的状态更新为已完成(图中6 - 8)

为了数据的一致性,当处理错误须要重试,事务发送方和事务接收方相关业务处理须要支持幂等。具体保存一致性的容错处理以下:

  • 一、当步骤1处理出错,事务回滚,至关于什么都没发生。
  • 二、当步骤二、步骤3处理出错,因为未处理的事务消息仍是保存在事务发送方,事务发送方能够定时轮询为超时消息数据,再次发送的消息中间件进行处理。事务被动方消费事务消息重试处理。
  • 三、若是是业务上的失败,事务被动方能够发消息给事务主动方进行回滚。
  • 四、若是多个事务被动方已经消费消息,事务主动方须要回滚事务时须要通知事务被动方回滚。

方案总结

方案的优势以下:

  • 从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于消息中间件,弱化了对MQ中间件特性的依赖。
  • 方案轻量,容易实现。

缺点以下:

  • 与具体的业务场景绑定,耦合性强,不可公用。
  • 消息数据与业务数据同库,占用业务系统资源。
  • 业务系统在使用关系型数据库的状况下,消息服务性能会受到关系型数据库并发性能的局限。

4.5 MQ事务 —— 最终一致性

方案简介

基于MQ的分布式事务方案实际上是对本地消息表的封装,将本地消息表基于MQ 内部,其余方面的协议基本与本地消息表一致。

处理流程

下面主要基于RocketMQ4.3以后的版本介绍MQ的分布式事务方案。

在本地消息表方案中,保证事务主动方发写业务表数据和写消息表数据的一致性是基于数据库事务,RocketMQ的事务消息相对于普通MQ,相对于提供了2PC的提交接口,方案以下:

正常状况——事务主动方发消息 这种状况下,事务主动方服务正常,没有发生故障,发消息流程以下:

正常状况——事务主动方发消息

  • 图中一、发送方向 MQ服务端(MQ Server)发送half消息。
  • 图中二、MQ Server 将消息持久化成功以后,向发送方 ACK 确认消息已经发送成功。
  • 图中三、发送方开始执行本地事务逻辑。
  • 图中四、发送方根据本地事务执行结果向 MQ Server 提交二次确认(commit 或是 rollback)。
  • 图中五、MQ Server 收到 commit 状态则将半消息标记为可投递,订阅方最终将收到该消息;MQ Server 收到 rollback 状态则删除半消息,订阅方将不会接受该消息。

异常状况——事务主动方消息恢复 在断网或者应用重启等异常状况下,图中4提交的二次确认超时未到达 MQ Server,此时处理逻辑以下:

异常状况——事务主动方消息恢复

  • 图中五、MQ Server 对该消息发起消息回查。
  • 图中六、发送方收到消息回查后,须要检查对应消息的本地事务执行的最终结果。
  • 图中七、发送方根据检查获得的本地事务的最终状态再次提交二次确认
  • 图中八、MQ Server基于commit / rollback 对消息进行投递或者删除

方案总结

相比本地消息表方案,MQ事务方案优势是:

  • 消息数据独立存储 ,下降业务系统与消息系统之间的耦合。
  • 吞吐量因为使用本地消息表方案。

缺点是:

  • 一次消息发送须要两次网络请求(half消息 + commit/rollback消息)
  • 业务处理服务须要实现消息状态回查接口

4.6 Saga事务 —— 最终一致性

方案简介

Saga事务源于1987年普林斯顿大学的Hecto和Kenneth发表的如何处理long lived transaction(长活事务)论文,Saga事务核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,若是正常结束那就正常完成,若是某个步骤失败,则根据相反顺序一次调用补偿操做。

处理流程

Saga事务基本协议以下

  • 每一个Saga事务由一系列幂等的有序子事务(sub-transaction) Ti 组成。
  • 每一个Ti 都有对应的幂等补偿动做Ci,补偿动做用于撤销Ti形成的结果。

能够看到,和TCC相比,Saga没有“预留”动做,它的Ti就是直接提交到库。

下面如下单流程为例,整个操做包括:建立订单、扣减库存、支付、增长积分 Saga的执行顺序有两种:

Saga事务执行顺序

  • 事务正常执行完成 T1, T2, T3, ..., Tn,例如:扣减库存(T1),建立订单(T2),支付(T3),依次有序完成整个事务。
  • 事务回滚 T1, T2, ..., Tj, Cj,..., C2, C1,其中0 < j < n,例如:扣减库存(T1),建立订单(T2),支付(T3,支付失败),支付回滚(C3),订单回滚(C2),恢复库存(C1)。

Saga定义了两种恢复策略:

  • 向前恢复(forward recovery)

Saga事务向前恢复

对应于上面第一种执行顺序,适用于必需要成功的场景,发生失败进行重试,执行顺序是相似于这样的:T1, T2, ..., Tj(失败), Tj(重试),..., Tn,其中j是发生错误的子事务(sub-transaction)。该状况下不须要Ci。

  • 向后恢复(backward recovery)

Saga事务向后恢复

对应于上面提到的第二种执行顺序,其中j是发生错误的子事务(sub-transaction),这种作法的效果是撤销掉以前全部成功的子事务,使得整个Saga的执行结果撤销。

Saga事务常见的有两种不一样的实现方式:

  • 一、命令协调(Order Orchestrator):中央协调器负责集中处理事件的决策和业务逻辑排序。

中央协调器(Orchestrator,简称OSO)以命令/回复的方式与每项服务进行通讯,全权负责告诉每一个参与者该作什么以及何时该作什么。

命令协调模式

以电商订单的例子为例:

一、事务发起方的主业务逻辑请求OSO服务开启订单事务 二、OSO向库存服务请求扣减库存,库存服务回复处理结果。 三、OSO向订单服务请求建立订单,订单服务回复建立结果。 四、OSO向支付服务请求支付,支付服务回复处理结果。 五、主业务逻辑接收并处理OSO事务处理结果回复。

中央协调器必须事先知道执行整个订单事务所需的流程(例如经过读取配置)。若是有任何失败,它还负责经过向每一个参与者发送命令来撤销以前的操做来协调分布式的回滚。基于中央协调器协调一切时,回滚要容易得多,由于协调器默认是执行正向流程,回滚时只要执行反向流程便可。

  • 二、事件编排 (Event Choreography0:没有中央协调器(没有单点风险)时,每一个服务产生并观察其余服务的事件,并决定是否应采起行动

在事件编排方法中,第一个服务执行一个事务,而后发布一个事件。该事件被一个或多个服务进行监听,这些服务再执行本地事务并发布(或不发布)新的事件。

当最后一个服务执行本地事务而且不发布任何事件时,意味着分布式事务结束,或者它发布的事件没有被任何Saga参与者听到都意味着事务结束。

以电商订单的例子为例:

事件编排模式

一、事务发起方的主业务逻辑发布开始订单事件 二、库存服务监听开始订单事件,扣减库存,并发布库存已扣减事件 二、订单服务监听库存已扣减事件,建立订单,并发布订单已建立事件 四、支付服务监听订单已建立事件,进行支付,并发布订单已支付事件 五、主业务逻辑监听订单已支付事件并处理。

事件/编排是实现Saga模式的天然方式,它很简单,容易理解,不须要太多的代码来构建。若是事务涉及2至4个步骤,则多是很是合适的。

方案总结

命令协调设计的优势和缺点: 优势以下:

  • 一、服务之间关系简单,避免服务之间的循环依赖关系,由于Saga协调器会调用Saga参与者,但参与者不会调用协调器
  • 二、程序开发简单,只须要执行命令/回复(其实回复消息也是一种事件消息),下降参与者的复杂性。
  • 三、易维护扩展,在添加新步骤时,事务复杂性保持线性,回滚更容易管理,更容易实施和测试

缺点以下:

  • 一、中央协调器容易处理逻辑容易过于复杂,致使难以维护。
  • 二、存在协调器单点故障风险。

事件/编排设计的优势和缺点 优势以下:

  • 一、避免中央协调器单点故障风险。
  • 二、当涉及的步骤较少服务开发简单,容易实现。

缺点以下:

  • 一、服务之间存在循环依赖的风险。
  • 二、当涉及的步骤较多,服务间关系混乱,难以追踪调测。

值得补充的是,因为Saga模型中没有Prepare阶段,所以事务间不能保证隔离性,当多个Saga事务操做同一资源时,就会产生更新丢失、脏数据读取等问题,这时须要在业务层控制并发,例如:在应用层面加锁,或者应用层面预先冻结资源。

5 总结

各方案使用场景

介绍完分布式事务相关理论和常看法决方案后,最终的目的在实际项目中运用,所以,总结一下各个方案的常见的使用场景。

方案比较

  • 2PC/3PC 依赖于数据库,可以很好的提供强一致性和强事务性,但相对来讲延迟比较高,比较适合传统的单体应用,在同一个方法中存在跨库操做的状况,不适合高并发和高性能要求的场景。

  • TCC 适用于执行时间肯定且较短,实时性要求高,对数据一致性要求高,好比互联网金融企业最核心的三个服务:交易、支付、帐务。

  • 本地消息表/MQ事务 都适用于事务中参与方支持操做幂等,对一致性要求不高,业务上能容忍数据不一致到一我的工检查周期,事务涉及的参与方、参与环节较少,业务上有对帐/校验系统兜底。

  • Saga事务 因为Saga事务不能保证隔离性,须要在业务层控制并发,适合于业务场景事务并发操做同一资源较少的状况。 Saga相比缺乏预提交动做,致使补偿动做的实现比较麻烦,例如业务是发送短信,补偿动做则得再发送一次短信说明撤销,用户体验比较差。Saga事务较适用于补偿动做容易处理的场景。

分布式事务方案设计

本文介绍的偏向于原理,业界已经有很多开源的或者收费的解决方案,篇幅所限,就再也不展开介绍。

实际运用理论时进行架构设计时,许多人容易犯“手里有了锤子,看什么都以为像钉子”的错误,设计方案时考虑的问题场景过多,各类重试,各类补偿机制引入系统,致使设计出来的系统过于复杂,落地遥遥无期。

世界上解决一个计算机问题最简单的方法:“刚好”不须要解决它!—— 阿里中间件技术专家沈询

有些问题,看起来很重要,但实际上咱们能够经过合理的设计或者将问题分解来规避。设计分布式事务系统也不是须要考虑全部异常状况,没必要过分设计各类回滚,补偿机制。若是硬要把时间花在解决问题自己,实际上不只效率低下,并且也是一种浪费。

若是系统要实现回滚流程的话,有可能系统复杂度将大大提高,且很容易出现Bug,估计出现Bug的几率会比须要事务回滚的几率大不少。在设计系统时,咱们须要衡量是否值得花这么大的代价来解决这样一个出现几率很是小的问题,能够考虑当出现这个几率很小的问题,可否采用人工解决的方式,这也是你们在解决疑难问题时须要多多思考的地方。

更多精彩,欢迎关注做者公众号【分布式系统架构】

参考

technology-talk —— 事务

MySQL 中事务的实现

分布式一致性算法2PC和3PC

分布式开放消息系统(RocketMQ)的原理与实践

RocketMQ事务消息入门介绍

Saga分布式事务解决方案与实践 —— 姜宁

分布式事务Saga模式

从一笔金币充值去思考分布式事务

相关文章
相关标签/搜索