26

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

 4 years ago
source link: https://mp.weixin.qq.com/s/Xh2jfZgD7Qallh_DAUbeVg
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

<SOFA:Channel/>,有趣实用的分布式架构频道。 

本文根据 SOFAChannel#12 直播分享整理,主题:蚂蚁金服分布式事务实践解析。回顾视频以及 PPT 查看地址见文末。

欢迎加入直播互动钉钉群 : 30315793,不错过每场直播。

UJV3Mve.jpg!web

大家好,我是今天分享的讲师仁空,目前是蚂蚁金服分布式事务产品的研发。今天跟大家分享的是蚂蚁金服分布式事务实践解析,也就是分布式事务 Seata 在蚂蚁金服内部的实践。

今天我们将从以下 4 个主题进行详细介绍:

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

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

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

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

一、分布式事务产生背景

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

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

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

ii2qYbI.png!web

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

veYr6bZ.png!web

二、分布式事务理论基础

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

2PC 协议

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

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

zuYJJjM.png!web

IZVfey2.png!web

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

TCC 协议

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

aaMVRri.png!web

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

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

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

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

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

6zeUFbB.png!web

经过多年的发展,蚂蚁金服内部针对不同的场景发展了几种不同的模式,最早的是 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;

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

aQzuErn.png!web

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

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

总结一下:

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

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

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

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

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

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

  • 幂等控制;

  • 并发控制;

  • 允许空回滚;

  • 防悬挂控制;

我们逐个了解一下:

  • 二阶段设计

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

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

raue6jJ.jpg!web

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

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

ERJJruN.png!web

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

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

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

支出(扣款)场景

77BRfeF.jpg!web

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

存入场景

NjiAJn2.jpg!web

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

冻结场景

zmmQfmZ.jpg!web

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

解冻场景

v2QfQzY.jpg!web

最后看下解冻场景,一阶段检查账户状态是不是可用,二阶段如果提交,就释放冻结金额,显示的可用余额就多了 100 元。二阶段如果是回滚状态,就什么都不用做。

以上分享了接入 TCC 如何进行二阶段设计以及如何进行资源预留,用实际的金融场景分析了下 TCC 一二阶段需要做的事情。因为二阶段设计是 TCC 接入的关键,所以进行了重点阐述。接下来我们继续看 TCC 设计的其他规范。

  • 幂等控制

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

  • 并发控制

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

vYrQfai.jpg!web

  • 允许空回滚

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

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

  • 防悬挂

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

2Mzymmy.png!web

整个流程是这样的:

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

  • 分布式事务回滚;

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

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

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

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

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

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

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

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

rYVFJbV.png!web

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

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

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

eAjQVzF.png!web

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

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

7nYveyY.png!web

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

EZ3ERnr.png!web

四、极致性能优化

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

二阶段异步化

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

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

zyu6jqA.png!web

同库模式

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

五、总结

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

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

以上就是本期分享的全部内容,如果大家对蚂蚁金服在分布式事务中的实践以及 Seata 有问题跟兴趣,也可以在群内(群号:30315793)与我们交流。

本期视频回顾以及 PPT 查看地址

https://tech.antfin.com/community/live/1119/data/980

本文归档在 sofastack.tech。

VfuEBjV.jpg!web

:tada: 励支持 SOFAStack 的你~

* 点下右下角“ 在看

* 到公众号对话框发送“ 卫衣 ”,试试手气~

* 本期互动奖品“ SOFAStack Community 卫衣


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK