分布式事务小结

一、背景

        事务的ACID特性可以保障在一个事务中更新多条数据时,要么都更新成功要么都失败。在一个Domain服务内部可以使用数据库事务来保证数据一致性(@Transactional)。那如果涉及到跨多个系统、多个数据库的时候,用单一的数据库事务就没办法解决了。

        随着微服务、云原生和数据库拆分增多,如何保证这种跨系统、跨数据库的数据一致性问题呢?答案是:分布式事务。

二、分布式事务

        数据库事务有ACID四个特性,我们知道即使是数据库事务在考虑到性能的因素时,大部分情况下也不需要百分之百地实现ACID,所以有了事务的四种隔离级别。比如交易平台的mysql的隔离级别基于并发性能、热点数据等因素考虑用的就是RC隔离级别,而非mysql默认的RR隔离级别。

        分布式事务按照CAP理论,分为偏向CP的强一致方案,如XA协议(2PC)、3PC。偏向AP的最终一致性方案,如TCC、sage、可靠消息通知型事务和AT方案。其中最终一致性方案按照作用的层面不同又分为基于业务层面的事务和基于JDBC层面的事务。

        基于业务层面的事务包括saga、TCC、可靠消息通知型事务。基于JDBC层面的事务包括Seata  AT方案,实现原理主要是通过拦截sql,然后通过模拟TM事务管理器,RM资源管理器,然后加一个TC全局事务管理器。通过sql的操作前的undo_log、执行结果后的redo_log实现。将全局事务拆分为一个个分支事务。

        本篇文章先介绍下基于业务层面的事务-TCC方案、下篇文章简介下基于JDBC层面的事务(参考阿里的Seata  AT方案)

三、名词解析

TCC由 Try-Confirm-Cancel 三个方法组成的分布式事务解决方案,原理是将两阶段提交提升至应用层的实现。

1、Try 阶段完成的工作是预定操作资源(Prepare),在正式开始执行业务逻辑之前,先把要操作的资源锁定。

2、Confirm 阶段完成的工作是执行主要业务逻辑(Commit),它类似于事务的 Commit 操作。在这个阶段中,可以对 Try 阶段锁定的资源进行各种 CRUD 操作。如果 Confirm 阶段被成功执行,就宣告当前分支事务提交成功。

3、Cancel 阶段的工作是事务回滚(Rollback),它类似于事务的 Rollback 操作。在这个阶段中,你需要通过业务代码,对 Confirm 阶段执行的操作进行人工回滚。


TC、TM、RM

1、TC 全称是 Transaction Coordinator,TC 扮演了一个事务协调者的角色,负责协调全局事务的提交和回滚,并维护全局和分支事务的状态。

2、TM 全称是 Transaction Manager,它是事务管理器,主要作用是发起一个全局事务,对全局事务的提交和回滚做出决议。

3、RM 全称是 Resource Manager,它是资源管理器,向 TC 注册分支事务并上报事务状态,同时负责对当前分支事务进行提交和回滚。每一个分支事务都是全局事务的参与者,这些分支事务的所属应用扮演了 RM 的角色。

四、分布式事务 - TCC事务

TCC事务可以分为本地化TCC事务和远程式TCC事务两种实现方式。

4.1、本地化TCC事务

4.1.1 架构设计

本地化TCC是将 TCC 的功能嵌入客户端 SDK 中,可以随着应用代码去中心化部署,后端共享数据库存储事务日志。客户端通过周期性上报心跳信息到远端的 TC 集群,集群通过心跳信息判定该客户端是否存活,当机器出现失联情况时能自动完成故障转移,使得失联机器的事务得以继续执行。

架构设计如下图。


4.1.2 事务调用流程

这里通过扣减资源链路来说明下本地化 TCC 事务调用流程,主要由两个步骤组成:

① Step1 : Domain服务(事务发起方)通过 RPC 调用下游服务锁定资源

② Step2 : 锁定资源成功后落库生成初始化单据

由于网络的不确定性RPC可能会出现超时情况,需要引入分布式事务保证原子性。对于扣减资源流程抽象出 TCC 三个方法的逻辑如下:


在单据占用链路本地化 TCC 的执行流程为:

1、用户确认操作后会在调用 Try 方法之前向数据库中插入一条事务日志开启 TCC 事务,状态为初始化(init)

2、调用下游服务实现的 Try 方法锁定资源,落库初始化状态的订单

3、如果步骤 2 未抛异常,修改事务日志状态为待提交(to_submitted),进而调用 Confirm 方法;如果抛异常修改事务日志状态为待回滚(to_rolledBack),调用 Cancel 方法

4、如果 Confirm 抛异常,修改事务日志重试时间,等待下次重试;Cancel 同理

5、反复执行重试,一旦重试成功则删除日志,或者触发最大重试次数限制或者事务超时限制熔断告警,人工介入处理异常事务。

需要注意两个问题:

① 由于 RPC 服务不可避免会出现超时异常,因此下游服务提供的三个接口需要保证幂等性

② 由于修改事务日志状态的操作可能出现数据库异常,在极端情况下会导致状态修改失败,造成重复补偿,因此Confirm和Cancel需要进行状态幂等。

4.1.3 故障转移

        由于本地化 TCC 模式是类两阶段提交,对于极端情况如机器出现宕机,事务有可能长时间处于中间状态得不到执行,导致服务间出现数据不一致,产生业务影响(极端情况下需要修复大量数据),因此引入故障转移策略是必要的,本地化 TCC 一般采用的是事务托管机制。为了讲清楚托管机制,在时间轴上我划定三个时间点,分别为 T1、T2 和 T3:


1、T1 是客户端心跳失联的时刻

2、T1 到 T2 这段是集群判定机器失活的最大失联时间,比如最大失联时间为1min

3、T2 这个时间点,集群会将失联机器的已存在事务托管给另一台存活机器执行

4、T3 失联机器恢复心跳,会拉取自 T3 之后的新事务日志继续执行

        TC集群一般会通过zk/nocas/etcds等注册中心选举出 Leader 机器执行心跳检查任务,针对失活的客户端会一般优先选择同地域的机器进行托管。托管请求会通过 RPC 请求发送至客户端,客户端接收后刷新自己维护的托管列表,同时客户端侧会有定时任务,周期性拉取托管列表。

4.2、远程式TCC事务

4.2.1、架构设计

        本地化 TCC 接入简便、改造成本低、去中心化等优势,但是它的架构设计也存在局限性:

①本地化 TCC 的功能是 SDK 内嵌的,导致重试流量无法分散到其他节点

②本地化 TCC 目前不支持事务传播机制,仅能支持事务的单层调用

        为了解决上述问题,采用中心化集群协调事务、支持跨多应用的分布式事务解决方案——远程式 TCC。相较于本地化 TCC,远程式 TCC 将一个分布式事务分为一个全局事务加若干个分支事务(拆分思想:将大事务拆分为一个个小的事务),采用中心化部署的协调者集群完成全局事务的提交、回滚和分支事务的补偿操作。


        这里还是扣减资源流程说明下,需要调用两个 TCC 参与方——下游依赖服务1 和依赖服务2(RM),整个远程式 TCC 事务的运行流程为:

1、Domain服务(事务发起方)先向 TC 集群注册并开启一个全局事务,再通过 RPC 调用两个参与方的 Try 方法

2、两个参与方首先向 TC 集群注册分支事务,然后分别调用自己的 Try 方法进行资源锁定操作

3、事务发起方感知到业务方法调用没有异常,则向 TC 集群请求提交全局事务;如果抛出异常则要求回滚全局事务

4、TC 集群查询出所有分支事务,按照注册时间顺序回调客户端完成相应的补偿分支,提交则回调 Confirm,回滚则回调 Cancel

        对发起方来说,业务操作仅需要调用两个下游服务的 Try 方法完成资源,剩下的提交或者回滚操作是由框架自动完成的,如果分支补偿出现异常,则进入异步重试。

4.2.2、乱序防御

        通过上面的介绍可知,由于远程式 TCC 下应用是分布式部署的,通过 RPC 进行网络通信,不可避免会存在由于网络环境造成的请求乱序问题,比如二阶段跑在了一阶段前面的情况,常遇到的问题可分为两类:空回滚、Try 方法悬挂。

4.2.2.1、空回滚

什么是空回滚:

1、发起方的调用由于超时异常,导致全局事务回滚

2、但此时分支事务已经注册,但 Try 方法尚未执行

3、客户端此时先收到了 TC 集群的回调 Cancel 请求

4、对参与方 A 来说 Try 尚未执行,先执行了 Cancel,造成回滚一个没有被预留过的资源

解决方案是需要框架能识别出空回滚方法,并直接忽略它,不回调真正的 Cancel


4.2.2.2、Try 方法悬挂

什么是 Try 方法悬挂:

1、是空回滚的衍生现象

2、当客户端正常忽略了空回滚之后,Try 方法继续执行锁定资源,导致该资源长时间被锁定得不到释放

解决方案要求框架能识别出悬挂的 Try 方法并忽略执行。


        对于上面两个问题,如果要求在上下游业务代码中自定义处理这些乱序问题将变得非常麻烦。TCC框架一般都会集成相应的解决方案,一般是通过AOP切面记录操作日志的方法,来统一解决乱序问题。请求操作日志由全局事务 XID、分支事务 Branch_ID、状态组成,通过这些日志我们可以判断某个事务请求的执行顺序,从而规避请求乱序问题。

下面的通过流程图介绍事务日志表是如何工作的:

1、正常的流程为 Try 方法达到,事务控制表记录为空,插入一条记录状态为初始化,之后 Cancel 请求达到发现事务控制表里有记录,状态为初始化,因此继续执行 Cancel

2、如果 Cancel 请求到来发现事务控制表里没有自身 XID 的记录,说明自己是一个空回滚,需要新增一条记录,用于防止后续的 Try 悬挂

3、如果 Try 方法到达,发现事务控制表记录不为空,意识到自己是一个悬挂的 Try 方法,因此忽略执行


4.2.3、故障转移

        远程式 TCC 的故障转移需要分客户端和集群两方面来看。对于客户端来说一旦发生宕机等异常,TC 集群会转而向其他客户端机器进行重试,理论上只要应用下存在正常的机器,事务最终都会重试成功。


        由于 TC 集群侧每一台机器均负责一部分事务的重试操作,因此一旦发生机器宕机,需要将未完成的重试事务托管给

其他机器继续执行,类似于本地 TCC 下的事务托管机制。

        TC集群侧一般采用心跳表维护机器心跳,Leader 节点负责检查机器的存活与否,对于失联的机器会选择一台存活机器告知其托管宕机机器未完成的重试事务。TC集群机器宕机时刻,正在发往该机器的全局事务提交请求可能会丢失,此时需要客户端会向其他节点重试,该类重试操作需要TC集群的事务日志操作保证幂等,确保全局事务状态提交完成。


五、小结

TCC的优劣:

优:

1、一阶段写隔离,二阶段没有锁性能好

2、不是真正的业务回滚,cancle只是释放了被try占用的资源

劣:

1、需要一定的业务代码改造,把串行的业务逻辑拆分成 Try-Confirm-Cancel 三个不同的阶段执行

2、需要引入分布式事务框架,比较重,需要考虑分布式事务集群短暂不可用时的降级方案


        对于TCC事务,需要根据微服务上下游依赖情况,把串行的业务逻辑拆分成 Try-Confirm-Cancel 三个不同的阶段执行,需要考虑到下游资源的锁定操作、然后根据根据事务的执行情况确定提交或回滚操作。需要特别注意的是幂等性,接口幂等性是保证数据一致性的重要前提。因此对于单据模型包含status、stage字段的业务场景天然更适合些。

        本文抛砖引玉,如有表述不清楚的地方欢迎指出讨论。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容