一篇文章为你解读SOFA-DTX 分布式事务的设计演进路线

2019 年 9 月 05 日

一篇文章为你解读SOFA-DTX 分布式事务的设计演进路线

小蚂蚁说:

本文介绍了蚂蚁金服在分布式事务上,经过多年发展,服务于内外部大量不同业务,沉淀出的一整套包含TCC、FMT、XA模型的分布式事务解决方案。并且在持续对外输出的过程中,进一步打磨产品体验,适应各种严苛的金融级场景和机构需求,比如跨机房跨地域的容灾业务连续性保障能力等。



随着互联网技术快速发展,数据规模增大,采用分布式数据库或者跨多个数据库的分布式微服务应用在中大规模企业普遍存在,而由于网络、机器等不可靠因素,数据一致性的问题很容易出现,与可扩展性、高可用容灾等要求并肩成为金融 IT 架构支撑业务转型升级的最大挑战之一。


在蚂蚁金服核心系统提出微服务化时,曾遇到了非常大的技术难题。首先是在服务拆分以后,面临跨服务的一致性问题;其次,支付宝当时的峰值交易量已经非常高了,在解决一致性问题的同时,还需要兼顾性能。


在当时,业界常用的分布式事务解决方案,通常能够实现跨服务一致性,但在热点数据的处理上,难以满足性能需求。


因此,蚂蚁金服微服务化过程中急需一种即能保证一致性,又能保证高性能的方案。当时经过一系列调研和讨论,最终选择了以 BASE 最终一致性思想为基础,在业务层实现两阶段提交的 TCC 分布式事务解决方案,该方案既能保证跨服务的最终一致,又能通过业务灵活加锁的方式大幅减少资源层加锁时间,高效处理热点问题。


随着蚂蚁金服业务不断丰富,业务逻辑越来越复杂,同时蚂蚁金融云上客户也越来越多,对分布式事务解决方案(简称 DTX,Distributed Transaction-eXtended)也不只是追求极限性能,也对接入便捷性、实时一致性有了要求。


这篇文章将基于分布式事务在支付宝/蚂蚁金服核心金融场景下的演进路线,分享介绍其在各阶段的关键设计考量和发展思路。


一. 分布式事务在蚂蚁金服的应用背景


1.1 支付宝 SOA 架构演进


最初的支付宝系统架构非常简单,是一个典型的 Web 系统,交易、支付、账务系统是没有分开的,全部集中在一个大工程里,底层用 DB 存储,Web 服务使用 Spring 等框架开发,最上层使用 LB 转发用户流量。



随着代码规模和业务逻辑复杂度的增长,将所有的业务模块放在一个 Web 服务内的缺点日益凸显。因此推动了服务模块化拆分,演进成 SOA 架构,很好地解决了应用的伸缩性问题。


在这个架构中,交易系统、支付系统、账务系统分别独立出来了,它们之间的调用靠 RPC,除了服务模块化拆分,还有数据库的垂直拆分,根据业务系统功能,将数据库拆分为多个,每个系统使用独立的数据库。



1.2 数据一致性问题


数据垂直拆分后,减少了单库的性能瓶颈,但也带来了数据一致性的问题。如下图所示:



一笔转账业务需要跨交易、支付、账务三个系统才能完成整个流程,并且要求这三个系统要么一起成功,要么一起失败。如果有的成功,有的失败,就有可能造成数据不一致,那么这个业务行为肯定不会被用户所理解。所以,我们系统面临的问题就是 SOA 架构下的数据一致性。


二. 关键设计考量:金融级一致性要求与海量并发处理能力


考虑到在解决一致性问题的同时,还要兼顾海量并发处理能力,蚂蚁金服内部结合 BASE 理论的思想,选择在业务层实现 2PC(两阶段事务提交)的方式来解决该问题。


BASE 理论是指 BA(Basic Availability,基本业务可用性);S(Soft state,柔性状态);E(Eventual consistency,最终一致性)。该理论认为为了可用性、性能与降级服务的需要,可以适当降低一点一致性的要求,即“基本可用,最终一致”。


一般来讲,在传统的单机数据库或者集中式存储数据库里,对于一致性的要求是实时一致;而对于分布式系统,服务与服务之间已经实现了功能的划分,逻辑的解耦,也就更容易弱化一致性,允许处理过程中,数据的短暂不一致,只需要保证数据最终达到一致。


2.1 分布式金融核心的零差错容忍保证:TCC 模型


如本文开篇所述,蚂蚁金服大部分系统的分布式事务解决方案以 BASE 最终一致性思想为基础,在业务层实现两阶段提交的 TCC(Try-Confirm-Cancel)分布式事务解决方案,以确保在一致性问题的保障和性能方面达到最佳平衡。


TCC 分布式事务模型包括三部分,如下所示


  • 主业务服务:主业务服务为整个业务活动的发起方,服务的编排者,负责发起并完成整个业务活动。

  • 从业务服务:从业务服务是整个业务活动的参与方,负责提供 TCC 业务操作供主业务服务调用

  • 初步操作 Try:完成所有业务检查,预留必须的业务资源。

  • 确认操作 Confirm:真正执行的业务逻辑,不作任何业务检查,只使用 Try 阶段预留的业务资源。因此,只要 Try 操作成功,Confirm 必须能成功。另外,Confirm 操作需满足幂等性,保证一笔分布式事务有且只能成功一次。

  • 取消操作 Cancel:释放 Try 阶段预留的业务资源。同样的,Cancel 操作也需要满足幂等性。

  • 业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护 TCC全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的Confirm 操作,在业务活动取消时调用所有从业务服务的Cancel 操作。



2.1.1 TCC 与 XA 对比 — 并发性优势


TCC 把两阶段拆分成了两个独立的阶段,通过资源业务锁定的方式进行关联。资源业务锁定方式的好处在于,既不会阻塞其他事务在第一阶段对于相同资源的继续使用,也不会影响本事务第二阶段的正确执行。



XA 模型的并发事务



TCC 模型的并发事务


从上面的对比可以发现,TCC 模型相比 XA 模型进一步减少了资源锁的持有时间。XA 模型下,在 Prepare 阶段是不会把事务 1 所持有的锁资源释放掉的,如果事务 2 和事务 1 争抢同一个资源,事务 2 必须等事务 1 结束之后才能使用该资源。


而在 TCC 里,因为事务 1 在 Try 阶段已经提交了,那么事务 2 可以获得互斥资源,不必再等待事务 1 的 Confirm 或 Cancel 阶段执行完。也就是说,事务 2 的 Try 阶段可以和事务 1 的 Confirm 或 Cancel 阶段并行执行,从而获得了一个比较大的并发性能提升。


2.2 保证大规模交易下的并发性能:极致性能优化


2010 年开始,每年天猫双十一大促的峰值交易量成倍增加,如下图所示:



折线上的点代表着每一年的交易峰值(TPS)。前面讲过,每次分布式事务需要协调多个参与方,因此,每年交易峰值乘以一个倍数才是分布式事务框架要协调最终达到一致性状态的峰值分支事务数量。事实上,对分布式事务服务来说,2017 年已经达到每秒百万级水平。


回顾历年大促,2013 年对支付宝的交易处理是对我们挑战很大的一年,2013 年双 11 提出的峰值目标,按照当时大促准备的计算和存储资源,是无法完成的,怎么办?


只能选择继续优化事务框架,让它尽量少消耗资源,并去获得一个更大的性能提升。我们根据 TCC 模型的特点以及日常开发时积累的经验,针对性能问题,做了以下两类优化。


2.2.1 极致性能优化之同库模式


之前的业务活动管理器是一个单独的服务,每次启动业务活动、登记业务操作、提交或回滚业务活动等操作都需要与业务活动管理器交互,并且交互次数与参与者个数成正相关。


因此,为了追求极限性能,将业务活动管理器的远程存储替换成本地存储,如下所示:



减少了 RPC 的调用,同时还会做一些减少存储次数的优化,从而获得性能收益。


通过这种方式,减少 RPC 调用耗时,大幅降低事务执行时间。同时还针对支付、账务等访问频繁的特殊从业务服务,优化处理过程,不再创建单独的分支事务记录,而是将这个信息与主事务记录合并,在创建主事务记录的同时,记录分支事务,最大程度减少与数据库的交互次数,从而获得更高的性能收益。


下面是同库模式优化前后的时序图对比:



优化前时序图



优化后时序图


其中,绿色方块表示本地数据库访问。可以发现,优化后减少了:(2+n) 次 PRC 延迟 + 1 次数据库访问延迟(n 表示参与者个数)。


2.2.2 极致性能优化之异步化


从理论上来说,只要业务允许,事务的第二阶段什么时候执行都可以,因此资源已经被业务锁定,不会有其他事务动用该事务锁定的资源。如下图所示:



这就是 TCC 分布式事务模型的二阶段异步化功能,各从业务服务的第一阶段执行成功以后,主业务服务就可以提交完成,框架会保证正确记录事务状态,然后再由框架异步的执行各从业务服务的第二阶段,从而比较完整的诠释最终一致性。


大促的尖峰时刻是从零点开始的几十秒或一分钟之内,在这个时刻的交易,我们会把二阶段的操作从同步转成异步,在冲高的那一刻,二阶段就停止了,一阶段正常扣款,等着交易零点三十分或者夜里一点开始回落的时候,我们才开始打开二阶段,集中做二阶段的动作。


优化后,Confirm 阶段在空闲时段异步执行:


1.假设 Confirm 阶段与 Try 阶段耗时相同,单个事务耗时减少 50%


2.数据库资源消耗减少 50%


三. 关键设计考量:无侵入自动化的接入体验


众所周知,蚂蚁金服在近几年除了支付业务以外,还发展出了很多复杂的金融业务,比如财富、保险、银行等。同时,在 2014 年蚂蚁金服全面开启了金融云时代,也会对外赋能合作方和客户。在这些新的场景下面,分布式事务产品面临新的挑战,客户的性能需求可能不像蚂蚁金服内部要求那么高,而是更关注接入的便利性和通用性,要求简单易用,对业务代码无侵入。因此,分布式事务产品开始全面升级,满足更多客户对云端产品的需求。


3.1 框架托管 FMT(Framework-managed transactions)模型


TCC 模型作用于业务层,负责协调从业务服务的最终一致性。所有被纳入到分布式事务的从业务服务,需要为框架提供 Try、Confirm、Cancel 三个方法,并且需要满足幂等性。由于方法实现和要满足的约束条件都需要业务方提供,这无疑就大大提高了接入门槛。所以我们在 TCC 模型上继续往前推进发展,提出了 FMT 模型来解决接入便捷性的问题。


FMT 分布式事务模型与 TCC 模型类似,也同样包含主业务服务、从业务服务、业务活动管理器,如下所示:



不同的是,从业务服务不再需要提供 Try、Confirm、Cancel 三个方法,而是直接按照 JDBC 标准,通过托管框架与底层数据库交互,就像使用普通数据源一样。托管框架对业务来说是透明的,主要负责解析 SQL 语义,自动生成两阶段操作。


3.2 FMT 模型实现原理


托管框架的两阶段操作如下图所示:



FMT 框架要求从业务服务只需要正常执行 SQL 操作就可以了,框架会把业务的本地事务操作作为第一阶段。在第一阶段,框架会拦截用户 SQL,解析 SQL 语义,然后把业务 SQL 涉及数据执行前后的状态保存下来,即数据快照,这个就相当于在逻辑上完成了数据库内的 undo 和 redo 操作。在第二阶段,如果这个事务要提交,那么框架直接把快照数据删除就可以了,因为第一阶段的正常操作已经执行完成。如果该事务要回滚,那么会先校验脏写,根据第一阶段保存的执行后的快照,检查在本事务执行过程中,数据有没有被其他操作修改,如果没有,则把数据执行前的快照拿出来,完成回滚操作。


3.3 一阶段示例



举个例子,上图左边这张表有两列,一列是账号,另一列是金额。这时如果要针对该账户执行一条 update 操作,框架会怎么做呢?


在 update 之前,会先把账户的金额保存下来,执行 update 操作,然后把执行之后的金额保存下来。因为在二阶段有可能会是回滚操作,回滚的时候如果想把执行之前的数据覆盖回去的话,必须要保证在覆盖的那个时刻,这些行上面的数据没有被别人变更过,所以最后会加一个逻辑行锁,这个就是金融系统的特性需求。


3.4 与数据访问代理集成


为了更加简化云上用户接入,我们继续和内部产品数据访问代理 DBP 合作集成,如下所示:



分布式事务产品框架可以认为是被集成在数据访问代理里,当进行一个事务时,上层业务方对于底下的分布式事务和本地事务是一视同仁的,通过数据代理看一个事务,并执行 SQL。如果是分布式事务,数据访问代理会通知框架去执行前面提到的一系列保证事务的操作,以保证数据的最终一致。


四. 关键设计考量:数据实时一致性、通用性、性能


4.1 XA 模型


TCC 和 FMT 两个模型都是在最佳实践上追求数据的最终一致性,而不是实时一致性。


我们分析了金融云上的客户发现,如果把业务模型假定成数据最终一致性,那么依然有很多金融客户不得不做出很大的妥协和变更,尤其是原有的业务组织模型和业务逻辑实现。而且这种妥协和调整的工作量是很大的,门槛也是非常高的。


所以我们基于标准 XA 做了一个 XA 模型来满足客户对数据实时一致性的需求。



原生 XA 协议提出至今,大概有 10-20 的时间了,但是在工业界应用的历史和案子都很少。为什么会这样呢?我们认为最重要的一点就是在追求数据实时一致性的同时,性能损失太大了。主要有两个比较方面的性能损失,一个是读和写之间的冲突,另一个是写与写之间的冲突。


4.2 标准 XA 问题分析


了解数据库内核的人都清楚,数据库内部解决写和非加锁读的冲突是通过 MVCC 机制来实现的。假如说最新的数据块在更新的同时,你的读是可以读正在更新的数据块的上一个快照。但是在分布式架构下,单机 MVCC 机制并不能满足数据实时性一致性要求。


依然是转账业务场景,A 账户给 B 账务转账 10 块钱。但是 A 账户和 B 账户分别在两个数据库分片 DB1 和 DB2 上。其操作执行过程如下所以:



如上图所示,DB1 的本地子事务已经提交完毕,但是 DB2 的本地子事务还没提交,这个时候只能读到 DB1 上子事务执行的内容,读不到 DB2 上的子事务。也就是说,虽然在单个 DB 上的本地事务是实时一致的,但是从全局来看,一个全局事务执行过程的中间状态被观察到了,全局实时一致性就被破坏了。


但是原生的 XA 协议没有规定快照读这个概念,也就没有定义怎么实现全局实时一致性。最简单的做法就是使用串行化的隔离级别,即使是快照读也需要转换为加锁读,从而来保证分布式事务的实时一致性。


当然,由于串行化隔离级别的性能较差,很多分布式数据库都自己实现了分布式 MVCC 机制来提供全局的实时一致性读。一个基本思路是用一个集中式或者逻辑上单调递增的东西来控制生成全局快照(Snapshot),每个事务或者每条 SQL 执行时都去获取一次,从而实现不同隔离级别下的全局一致性,如下图所示:



在 DB1 的本地子事务已经提交完毕,DB2 的本地子事务还没提交的中间状态,其他并发事务只能看到该事务执行之前的快照。


我们的分布式事务产品同样实现了分布式 MVCC 机制,从而在保证实时一致性的同时,最大程度的保证读写并发性能。


4.3 并发写优化 — 与 OB 深度定制 commit 延迟优化


除了实现分布式 MVCC 保证并发读写性能外,我们还与自研数据库 OceanBase 深度定制优化并发写,进一步提升产品性能,共同打造实时数据一致性的整体解决方案。


传统标准的二阶段提交过程如下:



其中,绿色方块表示持久化日志,黄色方块表示事务提交。从图中可以看到,单次 Commit 操作需要有 3 次日志延迟、1 次事务延迟以及 2 次 RPC 延迟。


OceanBase 内部实现 XA 协议的时候,会在和协调者交互的时候附带一些信息,并且在 Commit 时落盘,减少 Commit 过程中涉及到的 RPC 和落盘的操作,以达到减少用户 Commit 时间的效果。


优化后时序图如下:



虽然在 Commit 操作之后,还有 Clear 操作,但是在执行 Clear 时,用户的 Commit 请求已经返回了,所以并不影响用户感知的 Commit 请求延迟。因此,从用户感知的角度来说,单次 Commit 操作实际上只需要 1 次日志延迟、1 次事务延迟 以及 2 次 RPC 延迟。


通过以上优化,两阶段提交与普通提交的落盘次数和 RPC 次数是相同的,也就是说耗时和普通提交相差无几,写和写之间的冲突所带来的额外性能消耗将被降低很大一部分。



五. 总结


总结关于分布式事务服务的关键设计考量,首先为了保障支付业务的核心需求,保障分布式环境下交易一致性的问题,我们基于 BASE 思想,在业务层实现了 TCC 模型,并且为了业务发展的需求,优化了其工程实践,实现海量并发处理能力,让它的性能可以达到比业界其它产品高很多的状态。


其次,因为上层业务系统的复杂、业务种类的丰富等等业务需求,对分布式事务解决方案提出了全新的要求,所以我们在 TCC 模型基础上又加入了框架托管(FMT)模型,其简单易用,对业务无侵入的特点,可以较好的解决金融云场景下接入便捷性问题。


最后,我们对数据实时一致性、通用性、性能再思考,与自研数据库 OceanBase 深度定制,推出 XA 模型,共同打造实时数据一致性的整体分布式事务解决方案。


本文转载自公众号蚂蚁金服科技(ID:Ant-Techfin)。


原文链接:


https://mp.weixin.qq.com/s/OX_RMkMbjDT2SnGIystm2w


2019 年 9 月 05 日 18:30822
用户头像

发布了 150 篇内容, 共 20.7 次阅读, 收获喜欢 12 次。

关注

评论

发布
暂无评论
发现更多内容

云原生 go-zero 微服务框架

Kevin Wan

go golang microservice go-zero 微服务框架

商业通识 : 商业为什么能进步?

Walker

学习 得到 个人成长 商业

读后感之《任正非:以客户为中心》

王新涵

架构师训练营第十三周总结

张明森

阿里巴巴发布国内首个公益区块链标准 用技术让公益公开透明

CECBC区块链专委会

区块链 公益

Spring 5 中文解析测试篇-Spring测试

青年IT男

Spring5 JUnit

如何将VSCode变成绿色版本

lmymirror

vscode 教程

2020-09-03-第十三周学习总结

路易斯李李李

ARTS Week15

时之虫

ARTS 打卡计划

一点思考|工作十几年了,竟从未用过do-while!

王磊

Java

握草,你竟然在代码里下毒!

小傅哥

Java 程序员 小傅哥 bug 有毒代码

给DevOps加点料——融入安全性的DevSecOps

DevOps 运维 测试 开发 安全性

Golang Package sync 透析

卓丁

golang sync

有奖征文重磅来袭,来!一起玩把大的!

小红豆

LeetCode题解:239. 滑动窗口最大值,单调队列,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

拥抱K8S系列-05-基于docker部署面临的问题

张无忌

Docker 运维

翻转链表,机器学习视觉训练,对数据的人工标注,使信息丢失,John 易筋 ARTS 打卡 Week 16

John(易筋)

学习 ARTS 打卡计划 翻转链表 Google论文评判人工预处理 大数据架构Spark

用函数式写法精简Java代码的一个例子

Sean

Java 函数式编程

Go: gops如何与Go运行时交互?

陈思敏捷

go golang gops

给大家介绍下,这是我的流程图软件 —— draw.io

程序员小航

工具 流程图 draw.io drawio-desktop 画图软件

不支持原子性的 Redis 事务也叫事务吗?

海星

Java redis 事务

甲方日常 8

句子

工作 随笔杂谈 日常

从湖南“软硬兼施”,管窥三湘水畔的智能浪潮

脑极体

程序的机器级表示-数组的分配和访问

引花眠

计算机基础

ARTS打卡 第15周

引花眠

微服务 ARTS 打卡计划

透彻理解AQS源码分析系列之AQS基础二

InfoQ_d2212957090d

深入理解JVM垃圾回收机制 - GC Roots枚举

NORTH

GC Roots枚举 安全点 安全区域 OopMap

学习 Java,有什么书籍推荐?学习的方法和过程是怎样的?

沉默王二

Java 学习 程序员 书单

usdt支付系统开发|承兑商支付跑分系统搭建

WX13823153201

usdt支付系统开发

oeasy教您玩转linux010206 蒸汽机车 sl

o

usdt跨境入金支付系统搭建|区块链跑分系统开发

WX13823153201

一篇文章为你解读SOFA-DTX 分布式事务的设计演进路线-InfoQ