周志明《凤凰架构:构建可靠的大型分布式系统》
https://icyfenix.cn/
包括:
XA、JTA、两阶段提交、三阶段提交、CAP、BASE理论。
实现分布式事务的三种解决方案,可靠消息队列、TCC、SAGA事务。
在本节里,全局事务被限定为一种适用于单个服务使用多个数据源场景的事务解决方案。
为了解决分布式事务的一致性问题,X/Open组织提出了一套名为X/Open XA(XA 是 eXtended Architecture 的缩写)的处理事务架构,其核心内容是定义了全局的事务管理器(Transaction Manager,用于协调全局事务)和局部的资源管理器(Resource Manager,用于驱动本地事务)之间的通信接口。
XA 接口是双向的,能在一个事务管理器和多个资源管理器(Resource Manager)之间形成通信桥梁,通过协调多个数据源的一致动作,实现全局事务的统一提交或者统一回滚。
JTA(Java Transaction API)是基于 XA 模式在 Java 语言中的实现了全局事务处理的标准。
XA 将事务提交拆分成为两阶段过程,两阶段式提交(2 Phase Commit,2PC):
缺点:
为了缓解两段式提交协议的一部分缺陷,具体地说是协调者的单点问题和准备阶段的性能问题,后续又发展出了“三段式提交”(3 Phase Commit,3PC)协议。
三段式提交把原本的两段式提交的准备阶段再细分为两个阶段,分别称为 CanCommit、PreCommit,把提交阶段改称为 DoCommit 阶段。
共享事务(Share Transaction)是指多个服务共用同一个数据源。这里有必要再强调一次“数据源”与“数据库”的区别:数据源是指提供数据的逻辑设备,不必与物理设备一一对应。
如果直接将不同数据源视为不同的数据库,那完全可以用全局事务或者下一讲要学习的分布式事务来实现。
如果针对每个数据源连接的都是同一个物理数据库的特例,一种理论可行的方案是,直接让各个服务共享数据库连接。同一个应用进程中共享数据库连接并不困难,但不同服务节点共享数据库连接很难做到,为了实现共享事务,就必须新增一个中间角色。
这在分布式的场景下是个伪需求,你有充足理由让多个微服务去共享数据库,那就必须找到更加站得住脚的理由,来向团队解释拆分微服务的目的是什么。
分布式事务(Distributed Transaction)特指多个服务同时访问多个数据源的事务处理机制。
分布式系统(distributed system)的最大难点,就是各个节点的状态如何同步。CAP 定理是这方面的基本定理。
一致性(Consistency):代表在任何时刻、任何分布式节点中所看到的数据都是符合预期的。
可用性(Availability):代表系统不间断地提供服务的能力。密切相关两个指标:可靠性(平均无故障时间:MTBF)和可维护性(平均可修复时间:MTTR)。
分区容忍性(Partition Tolerance):代表分布式环境中部分节点因网络原因而彼此失联(即与其他节点形成“网络分区”)时,系统仍能正确地提供服务的能力。
如果放弃分区容忍性(CA without P),意味着我们将假设节点之间通信永远是可靠的。永远可靠的通信在分布式系统中必定不成立的。主流的 RDBMS(关系数据库管理系统)集群通常就是采用放弃分区容错性的工作模式。
如果放弃可用性(CP without A),意味着我们将假设一旦网络发生分区,节点之间的信息同步时间可以无限制地延长,此时,问题相当于退化到前面“全局事务”中讨论的一个系统使用多个数据源的场景之中,我们可以通过 2PC/3PC 等手段,同时获得分区容忍性和一致性。著名的 HBase 也是属于 CP 系统。
如果放弃一致性(AP without C),意味着我们将假设一旦发生分区,节点之间所提供的数据可能不一致。选择放弃一致性的 AP 系统目前是设计分布式系统的主流选择,因为 P 是分布式网络的天然属性,而 A 通常是建设分布式的目的。目前大多数 NoSQL 库和支持分布式的缓存框架都是 AP 系统,如Redis集群。
CAP、ACID 中讨论的一致性称为“强一致性”(Strong Consistency)。把牺牲了 C 的 AP 系统,叫做“最终一致性”(Eventual Consistency)。它是指,如果数据在一段时间内没有被另外的操作所更改,那它最终将会达到与强一致性过程相同的结果,有时候面向最终一致性的算法,也被称为“乐观复制算法”。
BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
BASE 分别是基本可用性(Basically Available)、柔性事务(Soft State)和最终一致性(Eventually Consistent)的缩写。
前面这种靠着持续重试来保证可靠性的操作,在计算机中就非常常见,它有个专门的名字,叫做“最大努力交付”(Best-Effort Delivery),比如 TCP 协议中的可靠性保障,就属于最大努力交付。
支持分布式事务的消息框架,如 RocketMQ,原生就支持分布式事务操作。
可靠消息队列虽然能保证最终的结果是相对可靠的,过程也足够简单(相对于 TCC 来说),但整个过程完全没有任何隔离性可言,有一些业务中隔离性是无关紧要的,但有一些业务中缺乏隔离性就会带来许多麻烦。譬如:超售。
TCC 是另一种常见的分布式事务机制,它是“Try-Confirm-Cancel”三个单词的缩写。
TCC 的实现过程分为了三个阶段:
它是一种业务侵入性较强的事务方案,要求业务处理过程必须拆分为“预留业务资源”和“确认 / 释放消费资源”两个子过程。
TCC 其实有点类似 2PC 的准备阶段和提交阶段,但 TCC 是位于用户代码层面,而不是在基础设施层面,这为它的实现带来了较高的灵活性,可以根据需要设计资源锁定的粒度。TCC 在业务执行时只操作预留资源,几乎不会涉及锁和资源的争用,具有很高的性能潜力。但是 TCC 并非纯粹只有好处,它也带来了更高的开发成本和业务侵入性,意味着有更高的开发成本和更换事务实现方案的替换成本。
通常我们并不会完全靠裸编码来实现 TCC,而是基于某些分布式事务中间件(譬如阿里开源的Seata)去完成,尽量减轻一些编码工作量。
TCC 事务具有较强的隔离性,避免了“超售”的问题,而且其性能一般来说是本篇提及的几种柔性事务模式中最高的,但它仍不能满足所有的场景。TCC 的最主要限制是它的业务侵入性很强,不止是它需要开发编码配合所带来的工作量,而更多的是指它所要求的技术可控性上的约束。譬如,网银支付,通常也就无法完成冻结款项、解冻、扣减这样的操作。
SAGA 事务基于数据补偿代替回滚的解决思路。大致思路是把一个大事务分解为可以交错运行的一系列子事务集合。每个子事务都应该是或者能被视为是原子行为;为每一个子事务设计对应的补偿动作。
两种恢复策略:
正向恢复(Forward Recovery):T1,T2,…,Ti(失败),Ti(重试)…,Ti+1,…,Tn
反向恢复(Backward Recovery):T1,T2,…,Ti(失败),Ci(补偿),…,C2,C1。
与 TCC 相比,SAGA 不需要为资源设计冻结状态和撤销冻结的操作,补偿操作往往要比冻结操作容易实现得多。
SAGA 事务通常也不会直接靠裸编码来实现,一般也是在事务中间件的基础上完成,前面提到的 Seata 就同样支持 SAGA 事务模式。