分布式系统学习10:分布式事务
这是小卷对分布式系统架构学习的第13篇文章,今天学习面试中高频问题:分布式事务,为什么要用分布式事务,分布式事务的实现方案有哪些,方案对比优缺点;
1.知识体系
1.为什么要用分布式事务
单体架构时,以本地事务为例,业务场景是下单场景,用户下单、创建订单、扣减库存这些操作都可以在一个数据库事务中完成。
而随着业务的增长,系统转变为分布式系统,原有的单体架构也拆分为多个微服务。下单场景需要在多个服务间操作,需要保证所有操作都能成功,保证整个下单流程的数据一致性,就需要用到分布式事务了
2.理论
- 分布式理论的CP -> 刚性事务
遵循ACID,对数据要求强一致性
- 分布式理论的AP+BASE -> 柔性事务
遵循BASE,允许一定时间内不同节点的数据不一致,但要求最终一致。
这里重新复习一遍BASE理论是什么:
- 基本可用 Basically Available
- 软状态 Soft State
- 最终一致性 Eventually Consistent
基本可用:是指系统出现未知故障时,还是能用的;
软状态:允许系统存在中间态,即所有副本数据允许存在延迟;
最终一致性:存在软状态,在一定时间后,所有副本数据保持一致,从而达到数据最终一致性;
3.刚性事务(CP模式)
刚性事务指的是强一致性,基础是XA协议,XA协议是一个基于数据库的分布式事务协议,其分为两部分:事务管理器(Transaction Manager)**和**本地资源管理器(Resource Manager)。事务管理器作为一个全局的调度者,负责对各个本地资源管理器统一号令提交或者回滚;
而2PC (两阶段提交)和3PC(三阶段提交)都是由XA协议衍生出来的
3.1两阶段提交(2PC)
引入一个作为协调者(coordinator)的组件来统一掌控所有参与者(participant)的操作结果,并最终指示这些节点是否要把操作结果进行真正的提交
2PC指的是 Prepare & Commit
第一阶段:准备阶段:
- 协调者向所有参与者发送REQUEST-TO-PREPARE
- 当参与者收到REQUEST-TO-PREPARE消息后,它向协调者发送消息PREPARE或者NO,表示事务是否准备好;如果发送是NO,那么事务回滚;
第二阶段:提交阶段
- 协调者收集所有参与者的返回信息,如果所有参与者都回复PREPARED,那么协调者向所有参与者发送COMMIT消息,否则,协调者发送ABORT消息
- 参与者收到协调者发来的Commit消息或Abort消息,它将执行提交或回滚,并向协调者发送DONE消息确认
两阶段提交的缺点:
- 网络抖动导致数据不一致:第二阶段协调者向参与者发送commit命令后,如果发生网络抖动,有一部分参与者未收到commit请求,则无法执行事务提交,影响整个系统数据一致性;
- 超时导致的同步阻塞问题:2PC中所有参与者节点都是事务阻塞型,当一个节点通信超时,其余参与者都会被阻塞;
- 单点故障的风险:整个过程严重依赖协调者,如果协调者故障,参与者处于锁定资源的状态,无法完成事务commit的操作。即使重新选择一个协调者,也无法解决因前一个协调者宕机导致的阻塞问题;
2PC只适用于两个数据库(数据库实现了XA协议)之间使用,限制较大,两个系统间无法使用
3.2 三阶段提交(3PC)
在2PC的基础上,第一阶段和第二阶段中插入一个准备阶段,同时在协调者和参与者中都引入超时机制,当参与者为收到协调者发送的commit请求后,也会对本地事务commit,不会一直阻塞等待
过程如下:
- CanCommit:协调者向所有参与者发生Cancommit命令,算法可以执行事务提交操作,如果都响应YES,则下一阶段;
- PreCommit:协调者向所有参与者发送Precommit命令,是否可以进行事务预提交操作。参与者如果已执行了事务操作,则回复YES,进入下一阶段。如果回复NO,或者协调者没有收到参与者的回复,协调者就向所有参与者发送Abort请求,执行事务的中断;
- DoCommit:所有参与者已经回复YES,协调者发DoCommit命令正式提交事务,如果协调者没有收到参与者的ACK响应,则发Abort请求给所有参与者,中断事务。
小结:
2PC存在使用限制的问题,3PC存在数据不一致的问题,两者在实际中很少使用;
4.柔性事务(AP +BASE 最终一致性)
柔性事务要求最终一致性,允许有中间态,柔性事务可以分为:TCC、Saga、本地消息表、MQ事务方案、最大努力通知
4.1 TCC 补偿事务
TCC(Try Confirm Cancel)补偿事务,与2PC不同的是,2PC是在DB层面,TCC是在应用层面
三个操作步骤:
- Try阶段:完成业务检查,预留必须得业务资源;
- Confirm阶段:执行业务逻辑,只使用Try阶段预留的业务资源。Confirm需满足幂等性,保证一个分布式事务只成功一次;
- Cancel阶段:取消操作,释放Try阶段预留的业务资源,需要幂等性;
4.2 Saga事务
Saga可以看做一个异步的、利用队列实现的补偿事务。
由一系列本地事务构成,每个本地事务更新了数据库后,会发布一条消息来触发Saga中的下一个本地事务的执行,如果某个本地事务失败了,Saga会执行这个失败事务之前 已提交的所有事务的补偿操作
Saga的实现最流行的两种方式是:
- 基于事件的方式。这种方式没有协调中心,整个模式的工作方式就像舞蹈一样,各个舞蹈演员按照预先编排的动作和走位各自表演,最终形成一只舞蹈。处于当前Saga下的各个服务,会产生某类事件,或者监听其它服务产生的事件并决定是否需要针对监听到的事件做出响应。
- 基于命令的方式。这种方式的工作形式就像一只乐队,由一个指挥家(协调中心)来协调大家的工作。协调中心来告诉Saga的参与方应该执行哪一个本地事务
基于事件的方式
事务回滚:
- 基于事件的回滚,需要相关服务提供补偿操作接口,某个节点发生无法执行事件操作时,需要发送事件通知,其他已执行了事务的节点监听事件并回应
- 优点:简单容易理解,适用于分布式事务只有2-4个步骤的场景。示例如:下单-扣款-库存减货-物流服务-订单完成 这样简单的场景
- 缺点:参与业务方多时,会出现很多问题
4.3本地消息表
本地消息表的核心是将分布式事务拆成本地事务进行处理,最初是由eBay提出的
下面以一个订单场景具体说明本地消息表的实现
例如,可以在订单库新增一个消息表,将新增订单和新增消息放到一个事务里完成,然后通过轮询的方式去查询消息表,将消息推送到 MQ,库存服务去消费 MQ。
执行流程为:
- 订单服务,在一个事务里增加一个订单和一条消息,并提交
- 订单服务通过轮询的方式,查出未同步的消息,发到MQ,从设置失败重试机制;
- 库存服务,负责接收MQ消息,进行消费修改库存,由消费方保证幂等性;
- 库存服务修改成功后,调RPC接口修改订单服务的消息表状态;
- 修改失败,等待重试
优点:方案轻量,消息可靠性不依赖消息中间件;
缺点:与业务强耦合,不可公用,消息数据与业务库同库,占资源;
4.4 MQ消息事务
MQ事务是对本地消息表的封装,将本地消息表存到MQ内部了,而不是业务数据库
将两个事务通过消息队列进行异步解耦,加上重试机制保证最终一致性
发消息逻辑如下:
- 发送方向MQ server端发送half消息;
- MQ server将消息持久化后,发ACK给发送方
- 发送方开始执行本地事务
- 执行完成后,向MQ server提交二次确认
- MQ server收到commit状态将半消息标记为可投递,订阅方将收到消息;MQ server如收到rollback状态则删除半消息,订阅方收不到消息;
缺点:一次消息发送需要两次网络请求(half + commit/rolllback消息)
4.5 最大努力通知
也成为定期校对,是对MQ事务的进一步优化。
事务发起方增加了消息校对接口,也就是查询接口,事务接收方可以自行调用接口主动获取操作结果
逻辑如下:
事务主动方尽最大努力(重试,轮询…)将事务发送给事务接收方,但是仍然存在消息接收不到,此时需要事务被动方主动调用事务主动方的消息校对接口查询业务消息并消费,这种通知的可靠性是由事务被动方保证的
适用场景:
业务通知类型的场景,如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口
5. Seata框架
开源的分布式事务解决方案,提供了AT、TCC、SAGA、XA事务模式,不需要自己手动实现分布式事务,直接使用框架就行
有以下几个角色:
- TC (Transaction Coordinator) - 事务协调者: 维护全局和分支事务的状态,驱动全局事务提交或回滚。
- TM (Transaction Manager) - 事务管理器: 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
- RM (Resource Manager) - 资源管理器: 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。