分布式事务
为什么需要分布式事务
业务系统的复杂度提升,数据量的增加,使用多个中间件,以及多个微服务导致出现了分布式事务。
什么叫分布式事务
概念:在分布式条件下,保证多个节点操作的整体事务一致性。
特别是在微服务场景下,业务 A 和业务 B 关联,事务 A 成功,事务 B 失败,由于跨系统,就会导致不被感知。此时从整体来看,数据是不一致的。
如何实现?
典型情况下是两个思路:
理想状态(强一致):直接像单机数据库事务一样,多个数据库自动通过某种协调机制,实现了跨数据库节点的一致性。
一般情况(弱一致):可以容忍一段时间的数据不一致,最终通过超时终止,调度补偿等方式,实现数据的最终状态一致性。
实现方式:
强一致:XA
弱一致:
不用事务,通过业务侧补偿冲正。缺点:维护复杂。
柔性事务,使用一套事务框架保证最终一致的事务。
XA 分布式事务
简介
基于第一个强一致的思路,就有了基于数据库本身支持的协议,XA 分布式事务。XA 整体设计思路可以概括为,如何在现有事务模型上微调扩展,实现分布式事务。
概念
应用程序(Application Program ,简称 AP):用于定义事务边界(即定义事务的开始和结束),并且在事务边界内对资源进行操作。
资源管理器(Resource Manager,简称 RM):如数据库、文件系统等,并提供访问资源的方式。
事务管理器(Transaction Manager ,简称 TM):负责分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚等。
XA 接口
xa_start :负责开启或者恢复一个事务分支
xa_end: 声明 SQL 已执行完毕(负责取消当前线程与事务分支的关联)
xa_prepare:询问 RM 是否准备好提交事务分支
—————— 第一阶段提交 ————————— (如果是单机,可以直接跳过 prepare 和第二阶段,输入
one phase commit 事务id
直接进行提交即可。 )xa_commit:通知 RM 提交事务分支
xa_rollback: 通知 RM 回滚事务分支
xa_recover : 显示当前的 xa 事务(需要恢复的 XA 事务)
—————— 第二阶段提交 —————————
MySQL 中的 XA
MySQL 从 5.0.3 开始支持 InnoDB 引擎的 XA 分布式事务,MySQL Connector/J 从 5.0.0 版本开始支持 XA。
通过 show engines
可以看到各个引擎对 XA 的支持情况。
在 DTP 模型中,MySQL 属于资源管理器(RM)。分布式事务中存在多个 RM,由事务管理器 TM 来统一进行协调。
MySQL XA 事务状态流转
完整的 XA 事务处理过程
单个 MySQL 的内部操作
5.7 对 MySQL XA 的优化/bug 修复
MySQL <5.7 版本会出现的问题
已经 prepare 的事务,在客户端退出或者服务宕机的时候,2PC 的事务会被回滚。
在服务器故障重启提交后,相应的 Binlog 被丢失
MySQL 5.6 版本在客户端退出的时候,自动把已经 prepare 的事务回滚了,那么 MySQL 为什么要这样做?
这主要取决于 MysQL 的内部实现,MySQL 5.7 以前的版本,对于 prepare 的事务,MySQL 是不会记录 binlog 的(官方说是减少 fsync,起到了优化的作用)。只有当分布式事务提交的时候才会把前面的操作写入 binloq 信息,所以对于 binloq 来说,分布式事务与普通的事务没有区别,而 prepare 以前的操作信息都保存在连接的 IO CACHE 中,如果这个时候客户端退出了,以前的 binloq 信息都会被丢失,再次重连后允许提交的话,会造成 Binloq 丢失,从而造成主从数据的不一致,所以官方在客户端退出的时候直接把已经 prepare 的事务都回滚了!
MySQL >5.7 版本的优化
https://dev.mysql.com/worklog/task/?id-6860
MysQL 对于分布式事务,在 prepare 的时候就完成了写 Binlog 的操作,通过新增一种叫 XA-preparelog-event 的 event 类型来实现,这是与以前版本的主要区别(以前版本 prepare 时不写 Binlog)
XA 的失败处理
XA 失败要考虑的问题:
XA 过程中,事务失败怎么办?
业务 SQL 执行过程,某个 RM 崩溃怎么处理?
全部 prepare 后,某个 RM 崩溃怎么处理?
commit 时,某个 RM 崩溃怎么办?
XA 框架
XA 协议存在的问题
同步阻塞问题 (一般情况下,不需要调高隔离级别,XA 默认不会改变隔离级别)全局事务内部包含了多个独立的事务分支,这一组事务分支要不都成功,要不都失败。各个事务分支的 ACID 特性共同构成了全局事务的 ACID 特性。也就是将单个事务分支的支持的 ACID 特性提升一个层次(up a level)到分布式事务的范畴。即使在非分布事务中(即本地事务),如果对操作读很敏感,我们也需要将事务隔离级别设置为 SERIALIZABLE,而对于分布式事务来说,更是如此,可重复读隔离级别不足以保证分布式事务一致性。也就是说,如果我们使用 MySQL 来支持 XA 分布式事务的话,那么最好将事务隔离级别设置为 SERIALIZABLE,地球人都知道 SERIALIZABLE(串行化)是四个事务隔离级别中最高的一个级别,也是执行效率最低的一个级别
单点故障成熟的 XA 框架需要考虑 TM 的高可用性由于协调者的重要性,一旦协调者 TM 发生故障,参与者 RM 会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
数据不一致
极端情况下,一定有事务失败问题,需要监控和人工处理
在二阶段提交的阶段二中,当协调者向参与者发送 commit 请求之后,发生了局部网络异常或者在发送 commit 请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了 commit 请求。而在这部分参与者接到 commit 请求之后就会执行 commit 操作。但是其他部分未接到 commit 请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。
BASE 柔性事务
简介
在 ACID 事务中对隔离性的要求很高,在事务执行过程中,必须将所有的资源锁定。因此将实现了 ACID 的事务要素的事务称为刚性事务。柔性事务的理念则是通过业务逻辑将互斥锁操作从资源层面上移至业务层面。通过放宽对强一致性要求,来换取系统吞吐量的提升。柔性事务可以被认为是基于 BASE 事务要素的事务。
本质:把锁的粒度减小,同时用乐观锁赌一把,绝大多数情况下都能成功提交。而无法成功的提交就需要人工干预回滚。
BASE 是基本可用、柔性状态和最终一致性这三个要素的缩写。
基本可用(Basically Available)保证分布式事务参与方不一定同时在线。
柔性状态(Soft state)则允许系统状态更新有一定的延时,这个延时对客户来说不一定能够察觉。
最终一致性(Eventually consistent)通常是通过消息传递的方式保证系统的最终一致性。
BASE 柔性事务常见模式:
TCC,通过手动补偿处理
AT,通过自动补偿处理
对比
事务特性
原子性(Atomicity):正常情况下保证。
一致性(Consistency),在某个时间点,会出现 A 库和 B 库的数据违反一致性要求的情况,但是最终是一致的。
隔离性(Isolation),在某个时间点,A 事务能够读到 B 事务部分提交的结果。
持久性(Durability),和本地事务一样,只要 commit 则数据被持久。
隔离级别
某种程度来说,在整个柔性事务过程中,多个数据库执行事务时,读到的数据都是脏数据,而只有所有数据库都执行完事务之后,才是干净的数据。
换个角度来看,其实单个数据库的事务执行可以理解为一个单机事务中的 SQL,所有数据库的事务执行后,才算整个事务被提交。
因此,如有全局锁,则隔离级别一般为读已提交;如无全局锁,隔离级别其实是读未提交。
因此要注意:业务设计时要注意高内聚、低耦合,来考虑隔离级别的情况。
TCC
简介
TCC 模式即将每个服务业务操作分为两个阶段,第一个阶段检查并预留相关资源,第二阶段根据所有服务业务的 Try 状态来操作,如果都成功,则进行 Confirm 操作,如果任意一个 Try 发生错误,则全部 Cancel。TCC 使用要求就是业务接口都必须实现三段逻辑:
准备操作 Try:完成所有业务检查,预留必须的业务资源。
确认操作 Confirm:真正执行的业务逻辑,不做任何业务检查,只使用 Try 阶段预留的业务资源。因此,只要 Try 操作成功,Confirm 必须能成功。另外,Confirm 操作需满足幂等性,保证一笔分布式事务能且只能成功一次。
取消操作 Cancel:释放 Try 阶段预留的业务资源。同样的,Cancel 操作也需要满足幂等性。
优缺点
优点:TCC 不依赖 RM 对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
缺点:不同于 AT 的是就是需要自行定义各个阶段的逻辑,对业务有侵入。
需注意的问题
允许空回滚:空回滚的意思是,如果发现 try 没成功,则跳过 Cancel 操作,避免执行 cancel 操作导致的数据状态不正确。
防悬挂控制:悬挂是指:由于步骤被拆分,加上网络抖动等情况,有可能出现 cancel 执行比 try 要前,这就会导致 try 对应的 cancel 不存在了(空回滚会让 cancel 不执行),因此,需要有机制保证 try 在 cancel 之前执行;或者是保存 cancel 释放的资源,当接收到 try 操作时,判断是否存在 cancel 先来的情况,如果有的话,则放弃 try 操作。
幂等设计:commit 有可能被重试,因此需要保证 commit 等操作是幂等的,比如操作前先进行去重。
AT
简介
AT 模式就是两阶段提交,自动生成反向 SQL,并保存到单独的数据库中。当需要回滚时,通过数据库内的数据和现场,就可以拼接出 revert SQL 进行回滚。
评论