分布式事务
银行跨行转账业务是一个典型分布式事务场景,假设A需要跨行转账给B,那么就涉及两个银行的数据,无法通过一个数据库的本地事务保证转账的ACID,只能够通过分布式事务来解决。
分布式事务就是指事务的发起者、资源及资源管理器和事务协调者分别位于分布式系统的不同节点之上。
分布式事务的解决方案
两阶段提交/XA
XA是由X/Open组织提出的分布式事务的规范,XA规范主要定义了(全局)事务管理器(TM)和(局部)资源管理器(RM)之间的接口。本地的数据库如mysql在XA中扮演的是RM角色
XA一共分为两阶段:
第一阶段(prepare):即所有的参与者RM准备执行事务并锁住需要的资源。参与者ready时,向TM报告已准备就绪。
第二阶段 (commit/rollback):当事务管理者(TM)确认所有参与者(RM)都ready后,向所有参与者发送commit命令。
目前主流的数据库基本都支持XA事务,包括mysql、oracle、sqlserver、postgre
XA 事务由一个或多个资源管理器(RM)、一个事务管理器(TM)和一个应用程序(ApplicationProgram)组成。
如果有任何一个参与者prepare失败,那么TM会通知所有完成prepare的参与者进行回滚。
XA事务的特点是:
-
简单易理解,开发较容易
-
对资源进行了长时间的锁定,并发度低
SAGA
Saga是这一篇数据库论文saga提到的一个方案。其核心思想是将长事务拆分为多个本地短事务,由Saga事务协调器协调,如果正常结束那就正常完成,如果某个步骤失败,则根据相反顺序一次调用补偿操作。
SAGA事务的特点:
-
并发度高,不用像XA事务那样长期锁定资源
-
需要定义正常操作以及补偿操作,开发量比XA大
-
一致性较弱,对于转账,可能发生A用户已扣款,最后转账又失败的情况
TCC
TCC分为3个阶段
-
Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
-
Confirm 阶段:确认执行真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源,Confirm 操作要求具备幂等设计,Confirm 失败后需要进行重试。
-
Cancel 阶段:取消执行,释放 Try 阶段预留的业务资源。Cancel 阶段的异常和 Confirm 阶段异常处理方案基本上一致,要求满足幂等设计。
TCC特点如下:
-
并发度较高,无长期资源锁定。
-
开发量较大,需要提供Try/Confirm/Cancel接口。
-
一致性较好,不会发生SAGA已扣款最后又转账失败的情况
-
TCC适用于订单类业务,对中间状态有约束的业务
本地消息表
写本地消息和业务操作放在一个事务里,保证了业务和发消息的原子性,要么他们全都成功,要么全都失败。
容错机制:
-
扣减余额事务 失败时,事务直接回滚,无后续步骤
-
轮序生产消息失败, 增加余额事务失败都会进行重试
本地消息表的特点:
-
长事务仅需要分拆成多个任务,使用简单
-
生产者需要额外的创建消息表
-
每个本地消息表都需要进行轮询
-
消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作
事务消息
事务消息发送及提交:
-
发送消息(half消息)
-
服务端存储消息,并响应消息的写入结果
-
根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
-
根据本地事务状态执行Commit或者Rollback(Commit操作发布消息,消息对消费者可见)
补偿流程:
对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
Producer收到回查消息,返回消息对应的本地事务的状态,为Commit或者Rollback
事务消息方案与本地消息表机制非常类似,区别主要在于原先相关的本地表操作替换成了一个反查接口
事务消息特点如下:
-
长事务仅需要分拆成多个任务,并提供一个反查接口,使用简单
-
消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作
AT事务模式
这是阿里开源项目seata中的一种事务模式,在蚂蚁金服也被称为FMT。优点是该事务模式使用方式,类似XA模式,业务无需编写各类补偿操作,回滚由框架自动完成,缺点也类似AT,存在较长时间的锁,不满足高并发的场景。
分布式事务中的网络异常
在分布式事务的各个环节都有可能出现网络以及业务故障等问题,这些问题需要分布式事务的业务方做到防空回滚,幂等,防悬挂三个特性,下面以TCC事务说明这些异常情况:
空回滚:
在没有调用 TCC 资源 Try 方法的情况下,调用了二阶段的 Cancel 方法,Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。
出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行Try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的Cancel方法,从而形成空回滚。
幂等:
由于任何一个请求都可能出现网络异常,出现重复请求,所以所有的分布式事务分支,都需要保证幂等性
悬挂:
悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。
出现原因是在 RPC 调用分支事务try时,先注册分支事务,再执行RPC调用,如果此时 RPC 调用的网络发生拥堵,RPC 超时以后,TM就会通知RM回滚该分布式事务,可能回滚完成后,RPC 请求才到达参与者真正执行。