分布式事务与解决方案
什么事分布式事务
分布式事务是指事物的操作位于不同的节点上,需要保证事物的 AICD 特性。
例如,在下单的场景中,库存与订单如果在不同的节点上,就涉及分布式事务。
解决方案
1 两阶段提交(2PC)
通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。
运行过程
准备阶段
协调者询问参与者事务是否执行成功,等待答复,参与者发回事务执行结果。
各参与者执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。
如参与者执行成功,给协调者反馈 yes,否则反馈 no。
提交阶段
如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务;否则,协调者发送通知让参与者回滚事务。
需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。
优缺点
同步阻塞 所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作。
单点问题 协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会一直等待状态,无法完成其它操作。
数据不一致 在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
太过保守 任意一个节点失败就会导致整个事务失败,没有完善的容错机制。
2 三段提交(3PC)
三阶段提交是在二阶段提交上的改进版本,3PC 最关键要解决的就是协调者和参与者同时挂掉的问题,所以 3PC 把 2PC 的准备阶段再次一分为二,这样三阶段提交。
运行过程
阶段一
协调者向所有参与者发出包含事务内容的
canCommit
请求,询问是否可以提交事务,并等待所有参与者答复。参与者收到
canCommit
请求后,如果认为可以执行事务操作,则反馈yes
并进入预备状态,否则反馈no
。
阶段二
协调者根据参与者响应情况,有以下两种可能。
所有参与者均反馈 yes,协调者预执行事务
1. 协调者向所有参与者发出 preCommit 请求,进入准备阶段。
2. 参与者收到 preCommit 请求后,执行事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)。
3. 各参与者向协调者反馈 ack 响应或 no 响应,并等待最终指令。
只要有一个参与者反馈 no,或者等待超时后协调者尚无法收到所有提供者的反馈,即中断事务。
* 协调者向所有参与者发出 abort 请求。
* 无论收到协调者发出的 abort 请求,或者在等待协调者请求过程中出现超时,参与者均会中断事务。
阶段三
该阶段进行真正的事务提交,也可以分为以下两种情况。
所有参与者均反馈 ack 响应,执行真正的事务提交。
1. 如果协调者处于工作状态,则向所有参与者发出 do Commit 请求。
2. 参与者收到 do Commit 请求后,会正式执行事务提交,并释放整个事务期间占用的资源。
3. 各参与者向协调者反馈 ack 完成的消息。
4. 协调者收到所有参与者反馈的 ack 消息后,即完成事务提交。
只要有一个参与者反馈 no,或者等待超时后协调组尚无法收到所有提供者的反馈,即回滚事务。
1. 如果协调者处于工作状态,向所有参与者发出 rollback 请求。
2. 参与者使用阶段 1 中的 undo 信息执行回滚操作,并释放整个事务期间占用的资源。
3. 各参与者向协调组反馈 ack 完成的消息。
4. 协调组收到所有参与者反馈的 ack 消息后,即完成事务回滚。
优缺点
优点:相比二阶段提交,三阶段提交降低了阻塞范围,在等待超时后协调者或参与者会中断事务。避免了协调者单点问题。阶段 3 中协调者出现问题时,参与者会继续提交事务。
缺点:数据不一致问题依然存在,当在参与者收到 preCommit 请求后等待 do commite 指令时,此时如果协调者请求中断事务,而协调者无法与参与者正常通信,会导致参与者继续提交事务,造成数据不一致。
3 补充事务(TCC)
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。
运行过程
它分为三个阶段:
Try 阶段主要是对业务系统做检测及资源预留
Confirm 阶段主要是对业务系统做确认提交,Try 阶段执行成功并开始执行 Confirm 阶段时,默认 Confirm 阶段是不会出错的。即:只要 Try 成功,Confirm 一定成功。
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
举个例子,假入 Bob 要向 Smith 转账,思路大概是: 我们有一个本地方法,里面依次调用
首先在 Try 阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。
在 Confirm 阶段,执行远程调用的转账的操作,转账成功进行解冻。
如果第 2 步执行成功,那么转账成功,如果第二步执行失败,则调用远程冻结接口对应的解冻方法 (Cancel)。
优缺点
优点:跟 2PC 比起来,实现以及流程相对简单了一些,但数据的一致性比 2PC 也要差一些
缺点:缺点还是比较明显的,在 2,3 步中都有可能失败。TCC 属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用 TCC 不太好定义及处理。
4 本地消息表(异步确保)
本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证在对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性
运行过程
在分布式事务操作的一方完成写业务数据的操作之后向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中。
之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发。
在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作。
优缺点
优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。
5 MQ 事务消息
有一些第三方的 MQ 是支持事务消息的,比如 RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的 MQ 都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。
运行过程
以阿里的 RocketMQ 中间件为例,其思路大致为:
第一阶段 Prepared 消息,会拿到消息的地址。
第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。
也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了 RocketMQ 会定期扫描消息集群中的事务消息,这时候发现了 Prepared 消息,它会向消息发送者确认,所以生产方需要实现一个 check 接口,RocketMQ 会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。
优缺点
优点:实现了最终一致性,不需要依赖本地数据库事务。
缺点:实现难度大,主流 MQ 不支持,RocketMQ 事务消息部分代码也未开源。
评论