目录
一、两阶段提交
两阶段提交就是把分布式事务分为两个阶段,一个是准备阶段、另一个是提交阶段。
1、两阶段提交协议的流程:
- 准备阶段:协调者向参与者发起指令,参与者评估自己的状态,如果参与者可以执行事务,则会写入redo或者undo日志,然后锁定资源,执行操作,但不提交。
- 提交阶段:如果每个参与者明确返回准备成功,也就是预留资源和执行操作成功,则协调者向参与者发起提交命令,参与者提交资源变更的事务,释放锁定资源,如果任何一个参与者明确返回准备失败,也就是预留资源或者执行操作失败,则协调者向参与者发起终止指令,参与者取消已变更的事务,执行undo日志,释放锁定的资源。
2、两阶段提交执行流程图:
- 成功场景:
- 失败场景:
3、两阶段提交问题:
- 性能问题:对于任何一次指令都必须收到明确的响应,才会继续进行下一步,否则处于阻塞状态,占用的资源被一直锁定,不会被释放。
- 单点故障:如果协调者宕机,参与者没有协调者指挥,则会一直阻塞,尽管可以通过选举新的协调者替代原有协调者,但是如果协调者在发送一个提交指令后宕机,而提交指令仅仅被一个参与者接收,并且参与者接收后也宕机,则新上任的协调者无法处理这种情况
- 脑裂:协调者发送提交指令,有的参与者接收到并执行了事务,有的参与者没有接收到事务就没有执行事务,多个参与者之间是不一致的。
二、三阶段提交
1、三阶段提交流程
- 询问阶段:协调者询问参与者是否可以完成指令,参与者只需回答是或不是,而不需要做真正的操作,这个阶段超时会导致终止。
- 准备阶段:如果在询问阶段所有参与者都返回可以执行操作,协调者想参与者发送预执行请求,然后参与者写日志,执行操作但是不提交操作;如果在询问阶段任意参与者返回不能执行操作的结果,则协调者向参与者发送终止请求,这里的逻辑与两阶段提交协议的准备阶段相似。
- 提交阶段:如果参与者在准备阶段返回成功,则协调者向参与者发起提交指令,参与者提交资源变更事务,释放锁定的资源;如果任何参与者返回准备失败,则协调者想参与者发起终止指令,参与者取消已经变更的事务。
2、三阶段提交流程示意图如下:
- 成功情况
- 失败情况
3、三阶段提交和两阶段提交的不同点
- 三阶段提交增加了询问阶段,询问阶段可以确保尽可能早地发现无法执行操作而需要终止的行为,但是他并不能发现所有的这种行为,只会减少这种情况的发生。
- 在准备阶段以后,协调者和参与者执行的任务中都增加了超时,一旦超时,则协调者和参与者都会继续提交事务,默认成功,这也是根据概率统计超时后默认成功的正确性最大。
三、TCC
前面讲的2PC提交通常用来解决两个数据库之间的分布式事务,比较局限。为了解决两个应用之间的分布式事务,支付宝提出了TCC协议。TCC是Try、Confirm、Cancel三个单词的缩写,其实是一个应用层面的2PC协议,Confirm对应2PC中的事务提交操作,Cancel对应2PC中的事务回滚操作。
1、TCC的操作流程如下:
- 准备阶段:调用方调用所有服务提供方的Try接口,该阶段各调用方做资源检查和资源锁定,为接下来的阶段2做准备。
- 提交阶段:如果所有的服务方都返回了YES,则进入提交阶段,调用方调用各个服务的Confirm接口,各服务方进行事务提交。如果有一个服务方在阶段1返回NO或者超时了,则调用方调用个服务方的Cancel接口。
2、执行流程实例如下:
- 事务提交示意图
- 事务回滚示意图
3、TCC存在的问题
当TCC中的服务宕机,或者超时时,调这会通过不停的进行重试来进行补偿,所有在服务提供者方需要保证幂等性。
四、最终一致性
最终一致性,一般是通过消息中间件,当系统A执行了本地事务后,发送操作消息到消息中间件服务,当系统B收到消息数据库在执行系统B的本地事务,但是这存在一个问题就是,系统A执行成功后,往消息服务器发送消息失败,或者消息丢失,再或者系统B收到消息后执行本地事务出现异常等。
最终一致性的集中具体实现思路:
1、最终一致性:错误的方案0
把发送消息和执行本地操作放到一个事务中,如果消息发送失败则回滚本地事务,如果消息发送成功才执行本地事务,但是还会存在以下问题:
- 网络2将军问题:发送消息失败,发送方并不知道是消息中间件没有收到消息,还是消息已经收到,只是在返回是出现了异常。
- 把网络调用放在事务中,可能会因为网络的延迟导致数据库长事务。严重的会阻塞整个数据库,风险很大。
2、最终一致性:第一种实现方式(业务自己实现)
如果消息中间件没有提供“事务消息”功能,
- 系统A添加一张消息表,系统A不直接给消息中间件发送消息,而是把消息存入到本地消息表中,把插入消息表和系统A的操作放到一个事务中。
- 系统A准备一个调度程序,查询消息表把为发送成功的消息发送到消息中间件中。消息中间件返回成功后更新消息表中消息为成功。
- 问题1:消息丢失。系统B从消息中间件取出消息后,如果处理一半系统B宕机并再次重启。此种情况是通过消息中间件的ACK机制,当系统B消费并且操作执行成功后才给消息中间件发送ACK。
- 问题2:重复消费,此种问题是需要系统B实现幂等性操作,解决重复消费问题。
此种方案的缺点:系统A增加消息表,同时添加了一个后台调度任务,不断扫描此消息表,会导致消息的处理和业务逻辑耦合,额外增加了业务方的开发负担。
3、最终一致性:第二种实现方式(基于RocketMQ事务消息)
RocketMQ提供了事务消息,把消息发送分为了消息预发送和确认发送两个阶段。具体使用方法如下:
- 步骤1:系统A调用Prepare接口,预发送消息。此时消息保存在消息中间间中,但消息中间件不会把消息发送给消费方。
- 步骤2:系统A更新数据库,进行操作。
- 步骤3:系统A调用Confirm接口,确认发送消息。此时消息中间件才会把消息发送给消费方进行消费。
这种方式有两个异常场景:
- 场景1:步骤1成功,步骤2成功,步骤3失败或超时。
- 场景2:步骤1成功,步骤2失败或超时,步骤3不会执行。
RocketMQ的处理方式为:RocketMQ会定期扫描所有的预发送但还未确认发送的消息,回调发送方,询问这条消息是是要发送出去,还是取消。
4、人工介入
对出现异常的分布式事务生成预警信息,有人工进行核对,进行处理。
参考:
《软件架构设计—大型网站技术架构与业务架构融合之道》——余春龙
《分布式服务架构—原理、设计与实战》—— 李鹏艳,杨彪