写点什么

蚂蚁金服分布式事务实践解析

  • 2020-03-19
  • 本文字数:6636 字

    阅读完需:约 22 分钟

蚂蚁金服分布式事务实践解析

大家好,我是今天分享的讲师仁空,目前是蚂蚁金服分布式事务产品的研发。今天跟大家分享的是蚂蚁金服分布式事务实践解析,也就是分布式事务 Seata 在蚂蚁金服内部的实践。今天我们将从以下 4 个主题进行详细介绍:


  • 为什么会有分布式事务产品的需求;

  • 理论界针对这个需求提出的一些理论和解决方案;

  • 蚂蚁金服在工程上是如何解决这个问题的;

  • 针对蚂蚁金服业务场景的性能优化;

一、分布式事务产生背景

首先是分布式事务产生的背景。


支付宝支付产品在 2003 年上线的时候,那时候的软件形态是单体应用,在一个应用内完成所有的业务逻辑操作。随着软件的工业化,场景越来越复杂,软件也越做越大,所有的业务在一个应用内去完成变的不可能,出现了软件模块化、服务化。


在从单体应用升级到分布式架构过程中,很自然得需要进行业务服务拆分,将原来糅在一个系统中的业务进行梳理,拆分出能独立成体系的各个子系统,例如交易系统、支付系统、账务系统等,这个过程就是服务化。业务服务拆分之后,原来一个服务就能完成的业务操作现在需要跨多个服务进行了。



另一个就是数据库拆分,分库分表。原来的单体数据库存不下的这么多信息,按服务维度拆库,比如把用户相关的存一起,形成用户库,订单放一块形成订单库,这个是拆库的过程;另一个是拆表,用户信息按照用户 ID 散列到不同的 DB 中,水平拆分,数据库的容量增大了。这样分库分表之后,写操作就会跨多个数据库了。


二、分布式事务理论基础

我们可以看到,在分布式架构中,跨数据库、跨服务的问题是天然存在的。一个业务操作的完成,需要经过多个服务配合完成,这些服务操作的数据可能在一个机房中,也可能跨机房存在,如果中间某一个服务因为网络或机房硬件的问题发生了抖动,怎么保证这笔业务最终的状态是正确的,比如支付场景,怎么防止我转钱给你的过程中,我的钱扣了,而对方的账户并没有收到钱。这个就是业务最终一致性的问题,是分布式事务需要解决的问题。


2PC 协议


针对这个问题,理论界也提出了解决方案,其中最为人熟知的就是二阶段协议了,简称 2PC(Two Phase Commitment Protocol)两阶段提交协议。


两阶段提交协议,就是把整个过程分成了两个阶段,这其中,它把参与整个过程的实体分成了两类角色,一个叫事务管理器或事务协调者,一个叫资源管理器,事务管理器我们也把它叫做事务发起方,资源管理器称为事务参与者。




两个阶段,第一个阶段是资源准备阶段,比如我要转账,我要先查询下我的余额够不够,够的话我就把余额资源预留起来,然后告诉发起方“我准备好了”,第二个阶段,事务发起方根据各个参与者的反馈,决定事务的二阶段操作是提交还是取消。


TCC 协议


另一个协议是 TCC 协议,各个参与者需要实现 3 个操作:Try、Confirm 和 Cancel,3 个操作对应 2 个阶段,Try 方法是一阶段的资源检测和预留阶段,Confirm 和 Cancel 对应二阶段的提交和回滚。



图中,事务开启的时候,由发起方去触发一阶段的方法,然后根据各个参与者的返回状态,决定二阶段是调 Confirm 还是 Cancel 方法。

三、蚂蚁金服分布式事务介绍

2019 年,蚂蚁金服跟阿里巴巴共同开源了分布式事务 Seata ,目前 Seata 已经有 TCC、AT、Saga 模式,Seata 意为:Simple Extensible Autonomous Transaction Architecture,是一套一站式分布式事务解决方案。今天的分享也是 Seata 在蚂蚁金服内部的实践。


分布式事务在蚂蚁金服的发展


基于上述的理论,接下来我们详细看下蚂蚁金服的分布式事务实现。



经过多年的发展,蚂蚁金服内部针对不同的场景发展了几种不同的模式,最早的是 TCC 模式,也就是上面讲的 Try - confirm - Cancel,我们定义接口规范,业务自己实现这 3 个操作。这个模式提供了更多的灵活性,因为是业务自己实现的,用户可以介入两阶段提交过程,以达到特殊场景下的自定义优化及特殊功能的实现,这个模式能几乎满足任何我们想到的事务场景,比如自定义补偿型事务、自定义资源预留型事务、消息事务等场景。TCC 模式广泛用于蚂蚁金服内部各金融核心系统。


这里要强调一点的是,TCC 模式与底层数据库事务实现无关,是一个抽象的基于 Service 层的概念,也就是说,在 TCC 的范围内,无论是关系型数据库 MySQL,Oracle,还是 KV 存储 MemCache,或者列式存储数据库 HBase,只要将对它们的操作包装成 TCC 的参与者,就可以接入到 TCC 事务范围内。


TCC 模式的好处是灵活性,弊端是牺牲了易用性,接入难度比较大,所有参与者需要进行改造提供 Try - Confirm - Cancel 三个方法。为了解决 TCC 模式的易用性问题,蚂蚁金服分布式事务推出了框架管理事务模式(Framework - Managed Transactions,简称 FMT),也就是 Seata 中的 AT 模式。FMT 模式解决分布式事务的易用性问题,最大的特点是易于使用、快速接入、对业务代码无侵入。


XA 模式是依赖于底层数据库实现的。


Saga 模式是基于冲正模型实现的一个事务模式,现在的银行业金融机构普遍用的是冲正模型。


这期我们重点讲 TCC 和 FMT,关于 Saga 模式,之前 Saga 模式也有专场直播分享过,感兴趣的可以看一下之前的直播回顾:《Seata 长事务解决方案 Saga 模式 | SOFAChannel#10 回顾》。


TCC 模式在蚂蚁金服内的使用


首先看下 TCC 模式,主要包含一下几个模块:


  • 参与者,它要实现全部的三个方法,Try、Confirm 和 Cancel;

  • 发起方,主要是作为协调者的角色,编排各个参与者,比如调用参与者的一阶段方法,决策二阶段是执行提交还是回滚;



举个例子,比如在这个流程图中,存在一个发起方和两个参与者,两个参与者分别实现了 Try、Confirm 和 Cancel 接口,第一阶段被包含在发起方的本地事务模版中(图中黄颜色的两条虚线就是发起方本地事务的范围),也就是说发起方负责调用各个参与者的一阶段方法,发起方的本地事务结束后,开始执行二阶段操作,二阶段结束则整个分布式事务结束。


二阶段是通过 Spring 提供的事务同步器实现的,发起方在发起一个分布式事务的时候,会注册一个事务同步器,当发起方本地事务结束的时候,会进入事务同步器的回调方法中。如果发起方的本地事务失败,则在回调中自动回滚所有参与者。如果发起方的本地事务成功,则二阶段自动提交所有参与者。二阶段结束后,删除所有事务记录。


总结一下:


  • 事务发起方是分布式事务的协调者;

  • 分布式事务必须在本地事务模板中进行,发起方本地事务的最终状态(提交或回滚)决定整个分布式事务的最终状态;

  • 发起方主职责:开启一个分布式事务 + 调用参与者一阶段方法。发起方实现的时候,首先是开启一个本地事务,调用 Start 开启分布式事务,框架会自动注册一个 Spring 事务同步器,然后发起方发起对参与者 Try 方法的调用,当有一个 Try 方法失败,则阻断发起方本地事务,状态置为回滚;否则,所有的参与者 Try 成功,整个分布式事务的状态就是提交。框架会利用事务同步器自动去执行参与者的二阶段方法;

  • 使用数据库持久化记录事务数据,也就是会跟踪发起方和各个参与者的状态,我们称为主事务状态和分支事务状态。这样我们就知道一个大事务整体是处于什么状态,每个参与者又是什么状态,当一笔事务失败时,我们就能捞起那些失败的参与者,进行补偿重试;


上面讲了整个流程以及发起方的实现内容,现在看下业务在实现参与者的时候,需要遵循以下规范:


  • 业务模型分二阶段设计;

  • 幂等控制;

  • 并发控制;

  • 允许空回滚;

  • 防悬挂控制;


我们逐个了解一下:


  • 二阶段设计


二阶段设计和幂等控制比较容易明白。二阶段设计就是一阶段的资源预留和二阶段的提交回滚。


比如以扣钱场景为例,账户 A 有 100 元,要扣除其中的 30 元。一阶段要先检查资源是否足够,账户余额是否大于等于 30 块,资源不足则需要立马返回失败;资源足够则把这部分资源预留起来,预留就是锁资源,锁的粒度可大可小,尽量是按照最小粒度、尽快释放的原则来,比如这里引入一个“冻结部分”的字段,“可用余额”在一阶段后就能立马得到释放,锁的是冻结字段。



二阶段,如果是提交则真正扣除冻结的 30 元;如果是回滚的话,则把冻结部分加回可用余额里。


我们看个具体的客户案例,网商银行在使用 TCC 时,划分了三层,最上一层是具体的业务平台,承接着外部不断变化的业务需求;中间是资产交换服务,是事务发起方层,由它来发起和编排各种不同的事务链路;最底下一层是事务参与者层,提供最基础的服务,比如存款核心提供的存入、支出、冻结、解冻服务,借记账务的各种原子服务等。



看下我们日常生活中常见的几个金融业务场景,支出、存入、冻结、解冻、提现、手续费和销户。提现场景,比如信用卡提现至银行卡,类似 A 到 B 的转账;手续费,跟转账类似。


下面重点介绍一下其他 4 个场景:支出(扣款)、存入(记入)、冻结和解冻四个 Case。


首先,看下账户表的设计,前面说过,在设计的时候,需要尽可能减少锁的时间和锁的粒度,这里账户表有这 4 个字段:当前余额、未达金额、业务冻结金额和预冻结金额。用户看到的余额 = 当前金额 - 预冻结 - 业务冻结金额。


支出(扣款)场景



先来看下支出(扣款)场景下,账户表里各字段的数额变化。初始状态下,显示的账户余额,和当前余额是一致的。TCC 的一阶段检查并预留资源,这里对应的资源是“预冻结金额”字段,预冻结金额设置为 100 元,当前余额不变。因为 100 块被预冻结了,显示给用户的可用余额现在是 900 元。如果二阶段是提交的话,就释放预冻结金额,扣除当前余额,账户的当前余额就是 900 元。如果二阶段不是提交,是回滚,这里就是把一阶段的资源释放,也就是把预冻结金额释放回去,显式的账户余额重新变成 1000 元。


存入场景



上面是支出(扣款)场景,再来看下存入的场景。初始状态还是当前余额和显式的可用余额都是 1000 元。因为是存入,一阶段的话就是“未达金额”加 100 元,显示的可用余额还是不变。二阶段如果是提交,就把未达金额清除,把这部分的钱加到当前余额,当前余额就是 1100 元了。如果二阶段是回滚,直接清除一阶段的未达金额即可。


冻结场景



冻结场景则是在一阶段是资源预留,就是预冻结,预冻结金额字段设置为 100 元,显示给用户的可用余额也要少 100 块。二阶段如果是提交,就是真正冻结,把预冻结金额释放,添加业务冻结金额。二阶段回滚的话,就是把一阶段的预冻结释放。


解冻场景



最后看下解冻场景,一阶段检查账户状态是不是可用,二阶段如果提交,就释放冻结金额,显示的可用余额就多了 100 元。二阶段如果是回滚状态,就什么都不用做。以上分享了接入 TCC 如何进行二阶段设计以及如何进行资源预留,用实际的金融场景分析了下 TCC 一二阶段需要做的事情。因为二阶段设计是 TCC 接入的关键,所以进行了重点阐述。接下来我们继续看 TCC 设计的其他规范。


  • 幂等控制


幂等控制,就是 Try-Confirm-Cancel 三个方法均需要保持幂等性。无论是网络数据包重传,还是异常事务的补偿执行,都会导致 TCC 服务的 Try、Confirm 或者 Cancel 操作被重复执行;用户在实现 TCC 服务时,需要考虑幂等控制,即 Try、Confirm、Cancel 执行一次和执行多次的业务结果是一样的。


  • 并发控制


并发控制即当两个并发执行的分布式事务操作同一个账号时,冻结的部分是相互隔离的,也就是 T1 冻结金额只能被事务 1 使用,T2 冻结金额只能被事务 2 使用。冻结资源与事务 ID 之间建立关联关系。



  • 允许空回滚


首先对空回滚的定义就是 Try 未执行,Cancel 先执行了。正常是一阶段的请求先执行,然后才是二阶段的请求。出现空回滚的原因,是网络丢包导致的,调用 Try 方法时 RPC timeout 了,分布式事务回滚,触发 Cancel 调用;参与者未收到 Try 请求而收到了 Cancel 请求,出现空回滚。


我们在设计参与者时,要支持这种空回滚。


  • 防悬挂


悬挂的定义是 Cancel 比 Try 先执行。不同于空回滚,空回滚是 Try 方法的请求没有收到。悬挂是 Try 请求到达了,只不过由于网络拥堵,Try 的请求晚于二阶段的 Cancel 方法。



整个流程是这样的:


  • 调用 TCC 服务 Try 方法,网络拥堵(未丢包),RPC 超时;

  • 分布式事务回滚;

  • TCC 服务 Cancel 被调用,执行了空回滚;整个分布式事务结束;

  • 被拥堵的 Try 请求到达 TCC 服务,并被执行;出现了二阶段 Cancel 请求比一阶段 Try 请求先执行的情况,TCC 参与者悬挂;


解决悬挂的问题,可以跟踪事务的执行,如果已经回滚过了,一阶段不应该正常执行,这时候要拒绝 Try 的执行。


FMT 模式在蚂蚁金服内的使用


接下来我们来看一下 FMT(Framework-Managerment-Transaction)框架管理事务模式。


之前介绍几个事务模式的时候,说过 TCC 模式虽然灵活,功能强大,能做很多定制和优化,但是使用难度上比较大,业务系统要进行二阶段改造,编码工作非常多。


针对那些对性能要求并不高,业务体量并不大的中小业务,我们推出了 FMT 模式——框架管理事务,从名字上看,就是大部分工作由框架自动完成,业务只需要关注实现自己的业务 SQL 即可。


FMT 还是基于二阶段的模型,业务只需要关注一阶段实现自己的业务 SQL,二阶段的自动提交回滚由框架来完成。



框架托管的二阶段,需要基于对一阶段的分析。在一阶段中,会执行下面几个步骤:对 SQL 进行解析,提取表的元数据,保存 SQL 执行前的值,执行 SQL,保存执行后的快照,保存行锁。


下面看下每个阶段具体做的事:


查询操作不涉及事务,我们这里以一个更新操作为例,首先要对操作的 SQL 进行语法语义分析,提取出关于这条记录的全部信息,包括是属于哪张表、查询条件是什么、有哪些字段、这些记录的主键等,这些信息可以通过 JDBC MetaData API 就能拿到。



然后我们开始保存执行前的快照数据,把目标记录的所有字段的当前值存到 undo log 里,存完后真正执行 SQL,SQL 执行后原来的一些字段值就已经产生变化了,我们把新的快照数据存到 redo log 里。最后把表名称和记录主键值存到行锁表,代表当前这个事务正在操作的是哪些记录。


有了这些信息后,框架就完全能自己去执行二阶段操作了。比如,当事务需要进行二阶段提交,因为在一阶段里业务 SQL 已经执行了,二阶段只需要把产生的中间数据删掉即可。当二阶段回滚时,因为我们保存了 SQL 执行的快照数据,所以还原回执行前的快照数据即可,同时把中间数据删掉。



这里我们知道了 undo 和 redo log 的作用,接下来讲讲行锁。行锁是用来进行并发控制的。当一个事务在操作一条记录前,会先去行锁表里查下有没有这条记录的锁信息,如果有,说明当前已经有一个事务抢占了,需要等待那个事务把锁释放。图中,事务 1 在一阶段对记录上锁,这个时候事务 2 进来,只能等待,等事务 1 二阶段提交,把锁释放,事务 2 这时候才能加锁成功。


四、极致性能优化

最后,我们看看在蚂蚁金服内部,针对双十一、双十二这种大促,为了达到更好的性能状态,做的一些优化。


二阶段异步化


一个是二阶段异步化,因为一阶段的结果已经能决定整个事务的状态了,而且资源也都预留好了,剩下的二阶段可以等请求峰值过后再去执行。这样,分布式事务耗时由执行 Try + Confirm 或者 Try + Cancel 缩减成 Try,提高了吞吐量。虽然结果有延迟的,但最终结果无任何影响。


异步的二阶段方法,在请求洪峰过后,会由事务恢复服务捞起执行。



同库模式


另一个优化,在事务记录上。分布式事务在推进过程中,会记录事务日志,如果这个事务日志是放到 Server 这边的,发起方更新事务状态时,需要跨 RPC 调用到 Server 方那边,影响分布式事务的性能。如果将事务日志存在业务数据库,则每次记录状态的就是业务本地执行的,减少 RPC 调用次数,从而提升了性能。

五、总结

以上就是本期分享的全部内容,我们先从事务产生的背景入手,在现在分布式架构的体系结构下,跨服务协同调用是常态,而网络、数据库、机器等都具有不可靠性,如果保证这中间操作要么全部成功,要么全部失败,是大家面临的共同问题,特别是金融场景下,对解决这个问题更有迫切性,蚂蚁金服作为一家金融科技公司,在这方面也进行了探索,积累了很多经验。


在介绍蚂蚁金服的分布式事务中间件之前,先介绍了一些分布式事务的理论背景,包括两阶段协议和 TCC 协议。基于理论背景,重点介绍了蚂蚁金服在分布式事务上 TCC、FMT 模式的应用,分享了实现原理和设计规范以及 TCC 二阶段设计等。最后介绍了针对双十一双十二这种大促活动,如何进行二阶段异步化和同库模式的优化,来支撑零点峰值时的洪峰请求。


本文转载自公众号金融级分布式架构(ID:Antfin_SOFA)。


原文链接


https://mp.weixin.qq.com/s?__biz=MzUzMzU5Mjc1Nw==&mid=2247485925&idx=1&sn=77fe4ee2caae2b09d3c97ea3fddaebe6&chksm=faa0e63fcdd76f297754c999ef87cc4ddd1aebfeaf71d41c9112322d25040bcefec31814e847&scene=27#wechat_redirect


2020-03-19 10:163823

评论 3 条评论

发布
用户头像
异步优化中,一阶段的2,假设其中一个参与者返回失败,后续的参与者调用其实就可以不用调了
2020-03-25 20:19
回复
不过这些参与者应该都是异步调用,main thread中等待各参与者回应(加上超时)
2020-03-25 20:23
回复
用户头像
不错
2020-03-23 15:55
回复
没有更多了
发现更多内容

Python Asyncio 初探:基本概念和模式

宇宙之一粟

Python asyncio 7月月更

spring篇之属性注入

邱学喆

spring 属性注入 @Autowired注入原理 @Resource注入原理 @Qualifier

Ark UI中的问题汇总【系列1】

坚果

Open HarmonyOS OpenHarmony Open Harmony 7月月更

【C语言深度剖析】详解strlen与sizeof的区别及用法

Albert Edison

7月月更

Android Wear开发步骤

芝麻粒儿

android 手机 7月月更

《MySQL入门很轻松》第4章:数据表的创建修改删除

乌龟哥哥

7月月更

新星计划Day3【JavaSE】 集合 Part1

京与旧铺

7月月更

Hive说我变了,Spark说不你没变

怀瑾握瑜的嘉与嘉

spark 7月月更

ORACLE进阶(十一)MERGE INTO学习总结

No Silver Bullet

oracle MERGE INTO 7月月更

数据库每日一题---第21天:员工花费的总时间

知心宝贝

数据库 云计算 后端 开发 7月月更

C 语言入门(二)

逝缘~

c 7月月更

OKALEIDO:我们为何如此看好多媒体NFT板块?

鳄鱼视界

OKALEIDO:我们为何如此看好多媒体NFT板块?

股市老人

架构实战营模块六作业

Geek_Q

Python|揭开「pip不是内部或外部命令,也不是可运行的程序或批处理文件」的神秘面纱

AXYZdong

Python 7月月更

14岁懂社会 - 《你没有那么笨》读书笔记

懒时小窝

读书笔记 14岁懂社会

前端异常监控平台对比

南城FE

前端 7月月更 异常监控

【愚公系列】2022年7月 Go教学课程 008-数据类型之整型

愚公搬代码

7月月更

【Python技能树共建】python selectolax 模块 & Python爬虫模拟登录

梦想橡皮擦

Python 爬虫 7月月更

Python反爬,JS反爬串讲,从MAOX眼X开始,本文优先解决反爬参数 signKey

梦想橡皮擦

Python 爬虫 7月月更

电商系统微服务架构

泋清

#架构实战营

Spring Cloud源码分析之Eureka篇第七章:续约

程序员欣宸

Java Spring Cloud Eureka 7月月更

关于 HTTP post 请求 form data 里的特殊符号,比如加号 plus symbol

汪子熙

HTTP web开发 7月月更 encoding form

人最痛苦的时候就是没有目标的时候

KEY.L

7月月更

C++|登录后通知各个显示页面,观察者模式

中国好公民st

c++ 7月月更

拆分电商系统为微服务

爱晒太阳的大白

ORACLE进阶(十二)union(all)学习总结

No Silver Bullet

oracle 7月月更 union union all

内部排序——归并排序

乔乔

7月月更

Jenkins centOS搭建和task创建

沃德

ci 程序员 7月月更

QT 实现生成压缩包

小肉球

qt 7月月更

蚂蚁金服分布式事务实践解析_架构_仁空_InfoQ精选文章