分布式事务
分布式事务
在分布式条件下,相互关联的多个数据由于跨系统,彼此的事务之间不被感知,无法保证
如何实现分布式下的一致性
典型情况下是两个思路:
- 理想状态:直接像单机数据库事务一样,多个数据库自动通过某种协调机制,实现了跨数据库节点的一致性。
使用场景:要求严格的一致性,比如金融交易类业务。
强一致 : 数据库使用XA协议实现,不是所有数据库都支持XA协议
- 一般情况:可以容忍一段时间的数据不一致,最终通过超时终止,调度补偿,冲正等等方式,实现数据的最终状态一致性。
使用场景:准实时或非实时的处理,比如 T+1的各类操作,或者电商类操作。
弱一致 : 1) 不用事务,业务侧补偿冲正 2) 所谓的柔性事务,使用一套事务框架保证最终一致的事务
XA 分布式事务协议
基于第一个强一致的思路,就有了基于数据库本身支持的协议,XA 分布式事务。XA 整体设计思路可以概括为,如何在现有事务模型上微调扩展,实现分布式事务。
X/Open
X/Open,即现在的 open group,是一个独立的组织,主要负责制定各种行业技术标准。X/Open 组织主要由各大知名公司或者厂商进行支持,这些组织不光遵循 X/Open组织定义的行业技术标准,也参与到标准的制定。
X/Open DTP 模型与 XA 规范
- 应用程序(Application Program ,简称 AP)
用于定义事务边界(即定义事务的开始和结束),并且在事务边界内对资源进行操作。
- 资源管理器(Resource Manager,简称 RM)
如数据库、文件系统等,并提供访问资源的方式。
- 事务管理器(Transaction Manager ,简称 TM)
负责分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚等。
XA 接口
为什么 XA 事务又叫两阶段事务?
xa_prepare完成作为分界线,第一阶段(各分支事务准备完成),第二阶段(全局事务提交或回滚)
MySQL 从 5.0.3 开始支持 InnoDB 引擎的 XA 分布式事务,MySQL Connector/J 从 5.0.0 版本开始支持 XA。
在 DTP 模型中,MySQL 属于资源管理器(RM)。分布式事务中存在多个 RM,由事务管理器 TM 来统一进行协调
MySQL XA 事务状态
完整的 XA 事务处理过程
其中一个参与者:单个 MySQL 的内部操作
思考一个问题:XA 过程中,事务失败怎么办?
- 业务 SQL 执行过程,某个 RM 崩溃怎么处理?
commit之前失败(所有分支业务sql执行过程中),TM得到消息后对所有分支事务进行rollback
- 全部 prepare 后,某个 RM 崩溃怎么处理?
prepare过程中失败(所有分支都需要prepare),TM得到消息后对所有分支事务进行rollback
- commit 时,某个 RM 崩溃怎么办?
commit过程中失败(所有分支都需要commit),TM得到消息后会对没有返回commit确认消息的分支重新进行发送commit,如果重试还是失败,就需要让mysql重启以后会再发送commit回复消息,一定会成功不能失败,一定会commit成功。
MySQL<5.7 版本会出现的问题
• 已经 prepare 的事务,在客户端退出或者服务宕机的时候,2PC 的事务会被回滚
• 在服务器故障重启提交后,相应的 Binlog 被丢失
MySQL 5.6版本在客户端退出的时候,自动把已经 prepare 的事务回滚了,那么 MySQL 为什么要这样做?这主要取决于MySQL 的内部实现, MySQL 5.7以前的版本,对于 prepare 的事务, MySQL 是不会记录 binlog 的(官方说是减少fsync,起到了优化的作用)。只有当分布式事务提交的时候才会把前面的操作写入 binlog 信息,所以对于 binlog 来说,分布式事务与普通的事务没有区别,而 prepare 以前的操作信息都保存在连接的 IO_CACHE 中,如果这个时候客户退出了,以前的 binlog 信息都会被丢失,再次重连后允许提交的话,会造成 Binlog 丢失,从而造成主从数据的不一致,所以官方在客户端退出的时候直接把已经 prepare 的事务都回滚了!
MySQL>5.7 版本的优化(https://dev.mysql.com/worklog/task/?id=6860)
MySQL 对于分布式事务,在 prepare 的时候就完成了写 Binlog 的操作,通过新增一种叫 XA_prepare_log_event的event 类型来实现,这是与以前版本的主要区别(以前版本 prepare 时不写 Binlog)
主流支持 XA 的框架
比较推荐 Atomikos 和 narayana
XA 分布式事务的注意事项
1.同步阻塞问题(一般情况下,不需要调高隔离级别)
全局事务内部包含了多个独立的事务分支,这一组事务分支要不都成功,要不都失败,各个事务分支的ACID特性共同构成了全局事务的ACID特性。也就是将单个事务分支的支持的ACID特性提升一个层次(up a level)到分布式事务的范畴。即使在非分布式事务中(即本地事务),如果对操作读很敏感,我们也需要将事务隔离级别设置为SERIALIZABLE。而对于分布式事务来说,更是如此,可重复读隔离级别不足以保证分布式事务一致性。也就是说,如果我们使用mysql来支持XA分布式事务的话,那么最好将事务隔离级别设置为SERIALIZABLE。地球人都知道,SERIALIZABLE(串行化)是四个事务隔离级别中最高的一个级别,也是执行效率最低的一个级别
2. 单点故障(成熟的 XA 框架需要考虑 TM 的高可用性)
由于协调者的重要性,一旦协调者TM发生故障,参与者RM会一直阻塞下去,尤其在第二阶段,协调者发生故障,那么所有的参与者还处于锁定事务资源的状态中,而无法继续完成事务操作。(如果协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调宕机导致的参与者处于阻塞状态的问题)。
3. 数据不一致(极端情况下,一定有事务失败问题,需要监控和人工处理)
在二阶段提交的阶段二中,当协调者向参与者发功commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接收到了commit请求,而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交,于是整个分布式系统便出现了数据不一致的现象。
mysql8.0 XA 操作
xa recover 可以列出已经prepare的本地事务
BASE 柔性事务
本地事务 -> XA(2PC) -> BASE
如果将实现了 ACID 的事务要素的事务称为刚性事务的话,那么基于 BASE 事务要素的事务则称为柔性事务。 BASE 是基本可用、柔性状态和最终一致性这三个要素的缩写。
- 基本可用(Basically Available)保证分布式事务参与方不一定同时在线。(可能某一个参与节点同时时间不在线,不过没关系,最后会通过各种机制一致)
- 柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉。
- 而最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。
在 ACID 事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。
BASE 柔性事务:TCC
通过手动补偿处理(相当于把 XA 下数据库维护的两阶段机制平移到业务侧)
TCC 模式即将每个服务业务操作分为两个阶段,第一个阶段检查并预留相关资源,第二阶段根据所有服务业务的 Try 状态来操作,如果都成功,则进行 Confirm 操作,如果任意一个 Try 发生错误,则全部 Cancel。
TCC 使用要求就是业务接口都必须实现三段逻辑:
- 准备操作 Try:完成所有业务检查,预留必须的业务资源(prepare)。
- 确认操作 Confirm:真正执行的业务逻辑,不做任何业务检查,只使用 Try 阶段预留的业务资源。
因此,只要 Try 操作成功,Confirm 必须能成功。另外,Confirm 操作需满足幂等性,保证一笔分布式事务能且只能成功一次。
- 释放 Try 阶段预留的业务资源。同样的,Cancel 操作也需要满足幂等性。
副作用:业务层修改较大
优势:锁的粒度变小,并发性能变大
不同阶段 | 张三向李四转 100 元 |
---|---|
Try | 冻结张三账户的 100 元(预留必须的业务资源),放如冻结表中(可以保证:1、成功(转到李四)2、失败(回滚到张三账户)) |
Confirm | 转到李四账户 |
Cancel | 回滚到张三账户 |
TCC 不依赖 RM(数据库) 对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务,不同于 AT 的是就是需要自行定义各个阶段的逻辑,对业务有侵入。
TCC 需要注意的几个问题
- 允许空回滚
try的期间某服务没有成功,所以回滚的时候要允许该服务空回滚,否则rollback后就会莫名其妙多100元
- 防悬挂控制
try调用方超时(网络抖动),同样会发起cancel的请求,由于网络抖动,被调用方先收到cancel执行(没有效果),后收到try,这时候try就会被永久的悬挂,100元会永久冻结到临时表。 先收到cancel的TCC事务,后续到达的try应该丢弃不操作,否则……
- 幂等设计
在没有接收到cancel之前,收到多少次try都是只会冻结100元(网络抖动发多次),同理收到多少次cancel也只能有一次的效果,多次操作结果和一次成功操作的结果一致
BASE 柔性事务:SAGA
Saga 模式没有 try 阶段,直接提交事务。复杂情况下,对回滚操作的设计要求较高。
BASE 柔性事务:AT
通过自动补偿处理:AT 模式就是两阶段提交,自动生成反向 SQL
柔性事务下的事务特性和隔离级别
事务特性
- 原子性(Atomicity):正常情况下保证。
- 一致性(Consistency),在某个时间点,会出现 A 库和 B 库的数据违反一致性要求的情况,但是最终是一致的。
- 隔离性(Isolation),在某个时间点,A 事务能够读到 B 事务部分提交的结果。
- 持久性(Durability),和本地事务一样,只要 commit 则数据被持久。
隔离级别
一般情况下都是读已提交(全局锁)、读未提交(无全局锁)
主流支持 BASE(柔性事务)的框架:Seata-TCC/AT
Seata 是阿里集团和蚂蚁金服联合打造的分布式事务框架。 其 AT 事务的目标是在微服务架构下,提供增量的事务 ACID 语意,让开发者像使用本地事务一样,使用分布式事务,核心理念同 Apache ShardingSphere 一脉相承。
Seata AT 事务模型包含 TM (事务管理器),RM (资源管理器) 和 TC (事务协调器)。 TC 是一个独立部署的服务,TM 和 RM 以 jar 包的方式同业务应用一同部署,它们同 TC 建立长连接,在整个事务生命周期内,保持远程通信。 TM 是全局事务的发起方,负责全局事务的开启,提交和回滚。RM 是全局事务的参与者,负责分支事务的执行结果上报,并且通过 TC 的协调进行分支事务的提交和回滚。
Seata 管理的分布式事务的典型生命周期
- TM 要求 TC 开始一个全新的全局事务。
- TC 生成一个代表该全局事务的 XID。
- XID 贯穿于微服务的整个调用链。
- TM 要求 TC 提交或回滚 XID 对应全局事务。
- TC 驱动 XID 对应的全局事务下的所有分支事务完成提交或回滚
Seata - TCC 原理
Seata - AT 原理
ShardingSphere 对分布式事务的支持
由于应用的场景不同,需要开发者能够合理的在性能与功能之间权衡各种分布式事务。强一致的事务与柔性事务的 API 和功能并不完全相同,在它们之间并不能做到自由的透明切换。在开发决策阶段,就不得不在强一致的事务和柔性事务之间抉择,使得设计和开发成本被大幅增加。
基于 XA 的强一致事务使用相对简单,但是无法很好的应对互联网的高并发或复杂系统的长事务场景;柔性事务则需要开发者对应用进行改造,接入成本非常高,并且需要开发者自行实现资源锁定和反向补偿。
整合现有的成熟事务方案,为本地事务、两阶段事务和柔性事务提供统一的分布式事务接口,并弥补当前方案的不足,提供一站式的分布式事务解决方案是 Apache ShardingSphere 分布式事务模块的主要设计目标。
ShardingSphere 支持 XA 事务的常见几个开源实现
ShardingSphere 支持 Seata 的柔性事务