分布式事务是啥
高并发场景下的分布式事务,是行业内至今没有很好解决的难题
其核心点在于:事务参与者出现在不同的数据库实例,需要网络通讯进行交互,引发了分布式场景下数据一致性问题
以购物下单的场景来分析
当订单表和库存表都在一个 DB 实例时,那么一个本地事务就能保证 ACID
而订单和库存分处于不同的 DB 实例时,那么下单的过程中,生成订单是一个事务参与者,减库存是一个事务参与者
在它俩的上面还有一个模块,负责先创建订单再减库存(或先锁库存再创建订单,谁先谁后,视业务场景而定)
但由于是不同的 DB 实例,原子性被破坏了,也就无法保证 ACID,这就是分布式事务产生的背景
其实就是把库拆开了,原来的一个操作一次事务可以在一个 DB 里完成,现在在两个 DB 里了
而我们还需要保证它的事务(也就是保证数据的一致,要么全成功,要么全失败),这就是分布式事务
分布式事务分类
分布式事务在不同的一致性要求上,通常有不同的解决方案(这玩意儿终究没有银弹)
一般分两种:强一致(刚性事务)和最终一致(柔性事务)
比如ABC三个操作,刚性事务就要求三个必须同时全成功或全失败(不允许落单)
而柔性事务则不同,比如AB写入成功,C失败,这时就不会要求它全成功,或者说没必要在同一时刻全成功
失败的那个可以通过线下或离线的机制让它成功,这样业务上体验会好一点,毕竟希望该交易是成功的,这就是柔性事务
不过柔性事务也不一定都是奔着成功走的,后面补充处理时也可能让AB都回滚,反正最终数据是对齐的,不会出现不一致
刚性事务 | 柔性事务 | |
---|---|---|
分类 | XA、2PC、3PC | TCC、Saga、事务消息、最大努力通知事务 |
一致性 | 强一致 | 最终一致 |
隔离性 | 原生支持 | 实现资源锁定接口 |
适合场景 | 短事务,并发较低 | 长事务,高并发 |
并发性能 | 严重衰退 | 略微衰退 |
业务改造 | 无 | 有 |
刚性事务
我们说的刚性事务,基本上就可以认为是XA,它基于2PC协议,各数据库厂商都实现了XA规范(实际上干的就是两阶段的事)
它是根据 XA 接口做真正的写入操作,但不提交,最后有一个事务管理器去协调通知它们提交,在此期间数据就被锁住了
其它事务或人就不能用了,性能损耗极大,并且刚性事务的时间大部分都耗在数据库上了,也就不太适合互联网
不过,改造起来比较简单:把以前调用普通数据源的地方,改成调用 XA 的数据源就行了
另外,XA的事务管理器会将事务执行状态记录在 local-log,即它是有状态的,若机器崩了那状态也就没了,故其不支持高可用
柔性事务
TCC:该模型等于是完全交给业务了,需要开发同学自己实现Try、Confirm、Cancel,业务入侵比较大
Try:资源的检测和预留,比如转账,先保证 A 账户余额足够并冻结
Confirm:执行业务操作,若 Try 成功 Confirm 一定要能成功(即执行 A 账号预留资源的扣款)
Cancel: 对 Try 资源预留的释放(即执行第一阶段资源的释放,解冻 A 账号预留的余额)
Saga:它是基于异步补偿的模型,业务实现时需要写正向和逆向两个接口的操作,业务入侵比较大
失败时,事务调度器会调正向补偿(最后让它全部成功),或调逆向操作回滚事务
事务消息:保证本地DB和发MQ这俩操作的原子性(另一个DB会去消费该MQ),但它不能保证另一个DB消费成功还是失败
所以它只做了一半(只保证了自己这块),它不是一个完全的分布式事务
最大努力通知事务:它可以基于Saga来做,就是说不逆向回滚,直接正向补偿,多试几次,尽最大努力让这个事务成功
一般可以选择 TCC 或 Saga(他俩的一致性基本是一样的),如果对一致性要求没有那么高,就可以用事务消息
事务模式对比
性能损耗:XA > TCC = Saga = 事务消息
一致性保障:XA > TCC = Saga > 事务消息
业务友好性:XA > 事务消息 > Saga > TCC
补充一下2PC
这里顺便介绍一下二阶段提交 2PC(Two phase Commit)
它是指在分布式系统中为保证所有节点进行事务提交时保持一致性的一种算法
它的思路
分布式系统中,每个节点都可以知晓自己操作的成功或失败,却无法知道其他节点操作的成功或失败
当一个事务跨多个节点时,为保持事务的原子性与一致性
需要引入一个协调者(Coordinator)来统一掌控所有参与者(Participant)的操作结果
并指示它们是否要把操作结果进行真正的提交(commit)或者回滚(rollback)
2PC 顾名思义分为两个阶段,其实施思路可概括为:
- 投票阶段(voting phase):参与者将操作结果通知协调者(可以理解为单机事务的trx.exec)
- 提交阶段(commit phase):收到参与者的通知后,协调者再向参与者发出通知
根据反馈情况决定各参与者是否要提交还是回滚(可以理解为单机事务的trx.commit或trx.rollback)
它的缺陷
2PC 在执行过程中,所有节点都处于阻塞状态,所有节点所持有的资源(例如数据库数据,本地文件等)都处于封锁状态
典型场景为:
- 某一个参与者发出通知之前,所有参与者以及协调者都处于阻塞状态
- 在协调者发出通知之前,所有参与者都处于阻塞状态
另外,如有协调者或某个参与者出现了崩溃,为避免整个算法处于一个完全阻塞状态
往往需要借助超时机制来将算法继续向前推进,故此时算法的效率比较低
总的来说,2PC 是一种保守并且低效的算法,而且效率很低
因此,针对分布式事务,通常有两种常见的实践:补偿事务和后置提交优化
补偿事务,是一种在业务端实施业务逆向操作的事务
缺点也很明显:
- 不同的业务要写不同的补偿事务,不具备通用性
- 要考虑补偿事务的失败
- 如果业务流程很复杂,if/else 会嵌套非常多层
而后置提交优化,它是改变事务执行与提交的时序,变成事务先执行,最后一起提交
举例:trx1.exec(); trx2.exec(); trx3.exec(); trx1.commit(); trx2.commit(); trx3.commit();
那么在第一个事务成功提交之后,最后一个事务成功提交之前
如果出现问题(例如服务器重启,数据库异常等),都可能导致数据不一致
它的不足是对事务吞吐量会有影响:因为所有库的连接,要等到所有事务执行完才释放
这就意味着,数据库连接占用的时间增长了,系统整体的吞吐量降低了
Seata简述
Seata:Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架
Seata 的愿景就是像使用本地事务一样使用分布式事务,目标是提供一站式的分布式事务解决方案
它的演进历史是这样的:
- TXC(Taobao Transaction Constructor):阿里巴巴中间件团队 2014 年启动该项目,以解决分布式事务的问题
- GTS(Global Transaction Service):2016 年 TXC 作为阿里中间件的产品,更名为 GTS 发布
- Fescar(Fast & EaSy Commit And Rollback):2019年01月09号基于阿里商用版的 GTS 开源了 Fescar
- Seata:随着蚂蚁金服的加入并贡献了 TCC 模式,更名为 Seata,地址为 https://github.com/seata/seata
它具有以下特性
- 支持多个微服务框架:Dubbo、SpringCloud、Sofa-RPC、Motan、gRPC
- 高可用:支持基于数据库存储的集群模式,水平扩展能力强
- 高可扩展性:支持配置中心、注册中心、SPI扩展
其缺点是不保证隔离性(事实上,业界的分布式事务解决方案中,一致性是优先保障的,隔离性几乎很少能完全做到)
并且,Seata要求数据库必须是支持本地 ACID 事务的关系型数据库,且必须定义主键
Seata事务模式
它支持以下四种事务模式
- AT模式:原始支持,早期还有MT模式(0.4.2废弃)
- TCC模式:0.4版本支持,2019.03.19
- Saga模式:0.9版本支持,2019.10.16
- XA模式:1.2版本支持,2020.04.21
其中 AT(Automatic Transaction)模式是 Seata 独有的,好处在于:业务不需关心事务,只关注自己的逻辑SQL就行了
使用 AT 模式就类似于在使用 XA 模式,而它的性能衰减更友好一些,没有 XA 这么大
从业务侵入的角度来看:AT 和 XA 无侵入,TCC 和 Saga 有侵入
Seata核心框架
它主要由Transaction Coordinator(TC)、Transaction Manager(TM)、Resource Manager(RM)三个模块组成
TC:事务协调者。维护全局和分支事务的状态,负责协调并驱动全局事务的提交或回滚
TC 不存在单点问题,它的数据是存到数据库的,它是无状态的,可以做到高可用
并且 TC 的数据量并不大,它自己的事务就用本地事务就行了,它不用再搞一个分布式事务,也不用跨数据中心
TM:事务管理器。控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议
RM:资源管理器。控制分支事务的边界和行为,负责分支注册、状态汇报,并接收 TC 指令,驱动分支(本地)事务的提交或回滚
RM 做的事就是在业务 SQL 基础上做一层拦截,然后想干啥干啥
全局事务就是对若干分支事务的整体协调,一个典型的事务过程包括:
- TM 向 TC 申请开启一个全局事务,TC 创建全局事务后返回全局唯一的 XID,XID 会在全局事务的上下文中传播
- RM 向 TC 注册分支事务,该分支事务归属于拥有相同 XID 的全局事务
- TM 向 TC 发起针对 XID 的全局提交或回滚
- TC 调度 XID 下的全部分支事务完成提交或回滚
基于架构上定义的这三个核心组件,分布式事务被抽象成如下事务框架:
这个框架图非常重要,理解了他,再去理解后面文章中介绍的四种事务模式就易如反掌了
Seata 的四种事务模式都是在该框架里跑的,不同事务模式体现在 RM 内部实现的方式不同
换句话说:该框架下 RM 驱动的分支事务的不同行为模式,即事务(分支)模式
补充
Seata 的 TCC 和 Saga 没有什么性能衰减,主要衰减还是在业务上
只是 AT 上衰减比较严重,后来 AT 把上报分支事务状态的步骤给取消了,这又提升了不少性能(但也相应的带来一些流程的改造)