在亿级流量架构之分布式事务解决方案对比中, 已经简单阐明了从本机事务到分布式事务的演变过程, 文章的最后简单说明了TCC事务, 这儿将会深入了解TCC事务是原理, 以及理论支持, 最后会用Demo举例实现。
XA协议
在上面提到的文章中, 分布式事务直接讲二阶段提交, 思维逻辑有些断层, 但是那毕竟是比较解决方案, 在这儿从理论上推导分布式事务的根基, 也就是为什么要二阶段提交。
在单体应用中, 往往由自己来保证事务的一致性, 但是分布式中, 涉及到跨网络调用就难以保证, 从理论上讲两台机器理论上无法达到一致的状态, 所以专门从服务角色上将事务操作抽象出一个服务用来协调事务, 叫做协调者, 或者说事务管理者。由全局事务管理器管理和协调的事务,可以跨越多个资源(如数据库或JMS队列)和进程。全局事务管理器一般使用 XA 二阶段提交协议与数据库进行交互。
而XA协议, 就是事务管理者与各个服务模块(也叫服务者、资源管理者)之间的通讯遵守的协议就是XA协议, 简单来说就是规范了接口, 这个协议由X/Open组织提出, 是分布式事务的规范。 XA规范主要定义了全局事务管理器(TM)和局部资源管理器(RM)之间的接口。除此之外, XA接口是双向的系统接口,在事务管理器 (TM)以及一个或多个资源管理器(RM)之 间形成通信桥梁,如上图。
二阶段提交协议
二阶段协议,一句话说就是, 先进行一个复杂度低的询问操作, 看看各个服务模块(也叫参与者、资源管理者、RM)是否可以进行事务操作, 一方面检验网络是否通畅, 另一方面看看对应的资源是否被占用 , 如果可以得到的回应是所有的服务可以进行事务操作, 那么这时候再通知所有服务提交事务。详细的说, 二阶段提交(2PC:Two-Phase Commit), 该协议将一个分布式的事务过程拆分成两个阶段: 投票 和 事务提交 。为了让整个数据库集群能够正常的运行,该协议指定了一个 协调者(事务管理器) 单点,用于协调整个数据库集群各节点的运行。为了简化描述,我们将数据库集群中的各个节点称为 参与者(也叫服务者, 资源管理者) 。
第一阶段:投票
该阶段的主要目的在于打探数据库集群中的各个参与者是否能够正常的执行事务,具体步骤如下:
- 协调者向所有的参与者发送事务执行请求,并等待参与者反馈事务执行结果;
- 事务参与者收到请求之后,执行事务但不提交,并记录事务日志;
- 参与者将自己事务执行情况反馈给协调者,同时阻塞等待协调者的后续指令。
第二阶段:事务提交
在经过第一阶段协调者的询盘之后,各个参与者会回复自己事务的执行情况,这时候存在 3 种可能性:
- 所有的参与者都回复能够正常执行事务。
- 一个或多个参与者回复事务执行失败。
- 协调者等待超时。
对于第 1 种情况,协调者将向所有的参与者发出提交事务的通知,具体步骤如下:
- 协调者向各个参与者发送 commit 通知,请求提交事务;
- 参与者收到事务提交通知之后执行 commit 操作,然后释放占有的资源;
- 参与者向协调者返回事务 commit 结果信息。
除此之外, 还有2种情况, 囿于篇幅, 详情参考: 亿级流量架构之分布式事务思路及方法后面的二阶段提交协议
今天要聊的TCC就是二阶段提交的具体事务实现。
LCN
详情参考:官网(中文版)
有了前面的XA协议以及二阶段提交的知识, 就不难理解LCN框架了, 这个框架可以理解成就是上面所说的协调者, 不生产事务, 只负责协调事务。5.0以后框架兼容了LCN、TCC、TXC三种事务模式。
LCN中各个字母依次代表:锁定事务单元(lock)、确认事务模块状态(confirm)、通知事务(notify)。
在一个分布式系统下存在多个模块协调来完成一次业务。那么就存在一次业务事务下可能横跨多种数据源节点的可能。TX-LCN目的是解决这样的问题。
例如存在服务模块A 、B、 C。A模块是mysql作为数据源的服务,B模块是基于redis作为数据源的服务,C模块是基于mongo作为数据源的服务。若需要解决他们的事务一致性就需要针对不同的节点采用不同的方案,并且统一协调完成分布式事务的处理。
在LCN中, 协调者称之为TxManager , 参与者称之为 TxClient, TxManager作为分布式事务的控制方, 事务发起方或者参与方都由TxClient端来控制决定。
时序图(来源官网):
LCN核心步骤
- 创建事务组
是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。 - 加入事务组
添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。 - 通知事务组
是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。
TCC
详情参考: Github(中文版)
TCC事务机制相对于二阶段提交,其特征在于它不依赖资源管理器(RM)对XA协议的支持,而是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务, 将事务分成 Try 和 Confirm/ Cancel两个阶段。
三种操作作用: Try: 尝试执行业务、 Confirm:确认执行业务、 Cancel: 取消执行业务。
整体流程如图
Try 从执行阶段来看,与传统事务机制(二阶段提交)中业务逻辑相同。但从业务角度来看,却不一样。TCC机制中的Try仅是一个初步操作,它和后续的确认一起才能真正构成一个完整的业务逻辑。TCC机制将传统事务机制(2PC)中的业务逻辑一分为二:
拆分后保留的部分为初步操作(Try);
而分离出的部分即为验证操作(Confirm/cancel),被延迟到事务提交阶段执行。
三阶段主要特点:
TCC补偿机制
在很多情况下,我们是无法做到强一致的 ACID 的。特别是我们需要跨多个系统的时候,而且这些系统还不是由一个公司所提供的。比如,在我们的日常生活中,我们经常会遇到这样的情况,就是要找很多方协调很多事,而且要保证我们每一件事都成功,否则整件事就做不到。
比如,要出门旅游, 我们需要干这么几件事。
第一,向公司请假,拿到相应的假期;
第二,订飞机票或是火车票;
第三,订酒店;
第四,租车。
这四件事中,前三件必需完全成功,我们才能出行,而第四件事只是一个锦上添花的事,但第四件事一旦确定,那么也会成为整个事务的一部分。这些事都是要向不同的组织或系统请求。我们可以并行地做这些事,而如果某个事有变化,其它的事都会跟着出现一些变化。
设想下面的几种情况。
- 我没有订到返程机票,那么我就去不了了。我需要把订到的去程机票,酒店、租到的车都给取消了,并且把请的假也取消了。
- 如果我假也请好了,机票,酒店也订好了,只是车没租到,那么并不影响我出行这个事,整个事还是可以继续的。
- 如果我的飞机因为天气原因取消或是晚点了,那么我被迫要去调整和修改我的酒店预订和租车的预订。
从人类的实际生活当中,我们可以看出,上述的这些情况都是天天在发生的事情。所以,我们的分布式系统也是一样的,也是需要处理这样的事情——就是当条件不满足,或是有变化的时候,需要从业务上做相应的整体事务的补偿。
对于业务补偿来说,首先需要将服务做成幂等性的,如果一个事务失败了或是超时了,我们需要不断地重试,努力地达到最终我们想要的状态。然后,如果我们不能达到这个我们想要的状态,我们需要把整个状态恢复到之前的状态。另外,如果有变化的请求,我们需要启动整个事务的业务更新机制。
业务补偿机制特点
由上可知,一个好的业务补偿机制需要做到下面这几点。
- 要能清楚地描述出要达到什么样的状态(比如:请假、机票、酒店这三个都必须成功,租车是可选的),以及如果其中的条件不满足,那么,我们要回退到哪一个状态。这就是所谓的整个业务的起始状态定义。
- 当整条业务跑起来的时候,我们可以串行或并行地做这些事。对于旅游订票是可以并行的,但是对于网购流程(下单、支付、送货)是不能并行的。总之,我们的系统需要努力地通过一系列的操作达到一个我们想要的状态。如果达不到,就需要通过补偿机制回滚到之前的状态。这就是所谓的状态拟合。
- 对于已经完成的事务进行整体修改,可以考虑成一个修改事务。
其实,在纯技术的世界里也有这样的事。比如,线上运维系统需要发布一个新的服务或是对一个已有的服务进行水平扩展,我们需要先找到相应的机器,然后初始化环境,再部署上应用,再做相应的健康检查,最后接入流量。这一系列的动作都要完全成功,所以,我们的部署系统就需要管理好整个过程和相关的运行状态。
业务补偿的设计重点
业务补偿主要做两件事。
- 努力地把一个业务流程执行完成。
- 如果执行不下去,需要启动补偿机制,回滚业务流程。
所以,下面是几个重点。
- 因为要把一个业务流程执行完成,需要这个流程中所涉及的服务方支持幂等性。并且在上游有重试机制。
- 我们需要小心维护和监控整个过程的状态,所以,千万不要把这些状态放到不同的组件中,最好是一个业务流程的控制方来做这个事,也就是一个工作流引擎。所以,这个工作流引擎是需要高可用和稳定的。这就好像旅行代理机构一样,我们把需求告诉它,它会帮我们搞定所有的事。如果有问题,也会帮我们回滚和补偿的。
- 补偿的业务逻辑和流程不一定非得是严格反向操作。有时候可以并行,有时候,可能会更简单。总之,设计业务正向流程的时候,也需要设计业务的反向补偿流程。
- 我们要清楚地知道,业务补偿的业务逻辑是强业务相关的,很难做成通用的。
- 下层的业务方最好提供短期的资源预留机制。就像电商中的把货品的库存预先占住等待用户在 15 分钟内支付。如果没有收到用户的支付,则释放库存。然后回滚到之前的下单操作,等待用户重新下单。
站在巨人的肩膀上
- 陈皓 左耳听风专栏