2PC

SQL 分布式事务两阶段提交协议(2PC)是一种原子承诺协议(ACP)。它是一种分布式算法,协调参与分布式原子事务的所有进程,以决定是否提交或回滚事务(这是一种专门的共识协议类型)。

概念

在事务处理、数据库和计算机网络中,两阶段提交协议(2PC)是一种原子承诺协议(ACP)。

它是一种分布式算法,协调参与分布式原子事务的所有进程,以决定是否提交或回滚事务(这是一种专门的共识协议类型)。即使在许多临时系统故障(包括进程、网络节点、通信等故障)中,协议也达到了它的目的,因此得到了广泛的应用。

然而,并不能适应所有可能的故障配置,在极少数情况下,需要用户(例如系统管理员)的干预来补救结果。

为了适应故障恢复(在大多数情况下是自动的),协议的参与者使用协议状态的日志记录。日志记录通常生成速度较慢,但在失败后仍能正常运行,协议的恢复过程使用日志记录。存在许多协议变体,主要在日志记录策略和恢复机制上有所不同。由于协议要考虑和支持许多可能的故障场景,虽然恢复过程通常不经常使用,但它构成了协议的很大一部分。

协议

在任何单个分布式事务的“正常执行”中(例如,当没有发生故障时(这通常是最常见的情况),协议包括两个阶段:

投票阶段

commit-request阶段(或投票阶段),一个协调过程试图准备所有事务的参与过程(名叫参与者、群组或工人)采取必要的步骤提交或中止事务和投票,要么“是的”:提交(如果正确地执行事务参与者的本地部分已经结束),或“不”:中止(如果一个问题已经被发现与当地的部分)

提交阶段

提交阶段,在此阶段中,协调器根据队列的投票决定是提交(仅当所有队列都投了“是”)还是中止事务(否则),并将结果通知所有队列。

队列接着使用本地事务资源(也称为可恢复资源)执行所需的操作(提交或中止);例如,数据库数据)和它们在事务的其他输出中的各自部分(如果适用的话)。

缺点

两阶段提交中的第二阶段, 协调者需要等待所有参与者发出yes请求, 或者一个参与者发出no请求后, 才能执行提交或者中断操作. 这会造成长时间同时锁住多个资源, 造成性能瓶颈, 如果参与者有一个耗时长的操作, 性能损耗会更明显。

实现复杂, 不利于系统的扩展。

理解二阶段提交协议及故障恢复

假设

二阶段提交算法的成立基于以下假设:

  1. 所有节点不会永久性损坏,即使损坏后仍然可以恢复

  2. 所有节点都采用WAL,日志写入后就被保存在可靠的存储设备上

  3. 所有节点上的本地事务即使机器crash也可从WAL日志上恢复

回滚流程

2pc-rollback

提交流程

2pc-commit

准备阶段

  1. 协调者记录事务开始日志

  2. 协调者向所有参与者发送prepare消息,询问是否可以执行事务提交,并等待参与者响应

  3. 参与者收到prepare消息后,根据自身情况,进行事务预处理,执行询问发起为止的所有事务操作

  4. 如果能够提交该事务,将undo信息和redo信息写入日志,进入预提交状态

  5. 如果不能提交该事务,撤销所做的变更,并记录日志

  6. 参与者响应协调者发起的询问。如果事务预处理成功返回commit,否者返回abort。

准备阶段只保留了最后一步耗时短暂的正式提交操作给第二阶段执行。

提交阶段

协调者等待所有参与者准备阶段的反馈

  • 如果收到某个参与者发来的abort消息或者迟迟未收到某个参与者发来的消息

标识该事务不能提交,协调者记录abort日志

向所有参与者发送abort消息,让所有参与者撤销准备阶段的预处理

  • 如果协调者收到所有参与者发来的commit消息

标识着该事务可以提交,协调者记录commit日志

向所有参与者发送commit消息,让所有参与者提交事务

参与者等待协调者的指令

  • 如果参与者收到的是abort消息

中止事务,利用之前写入的undo日志执行回滚,释放准备阶段锁定的资源

记录abort日志

向协调者发送rollback done消息

  • 如果参与者收到的是commit消息

提交事务,释放准备阶段锁定的资源

记录commit日志

向协调者发送commit done消息

协调者等待所有参与者提交阶段的反馈

  • 如果协调者收到所有参与者发来的commit done消息

完成事务,记录事务完成日志

  • 如果协调者收到所有参与者发来的rollback done消息

取消事务,记录事务取消日志

为什么先写日志

如果没有日志,宕机后重启将无法知道事务状态,无法进行故障恢复。

宕机异常处理

正常情况下,两阶段提交机制都能较好的运行,当在事务进行过程中,参与者或协调者宕机了怎么办?

阶段一

阶段一,协调者记录全局事务开始日志前宕机 协调者重启后,所有参与者均未开始分支事务,不做任何处理

阶段一,协调者记录全局事务开始日志后,发出prepare消息后宕机 协调者重启后,可能部分参与者已经完成一阶段准备,所以需要进行回滚

阶段一,协调者收到参与者响应后,记录准备完成日志前宕机 协调者重启后,因为没有记录准备完成日志,所以需要进行回滚

阶段一,协调者收到参与者响应后,记录准备完成日志后宕机 协调者重启后,已经记录准备完成日志,可以根据协商结果进行提交或回滚

阶段二

阶段二,协调者发出commit消息后宕机 协调者重启后,已经记录准备完成日志,可以根据协商结果进行提交或回滚

阶段二,协调者收到参与者响应后,记录事务完成日志前宕机 协调者重启后,已经记录准备完成日志,可以根据协商结果进行提交或回滚

阶段二,协调者收到参与者响应后,记录事务完成日志后宕机 协调者重启后,发现事务已经完成,不做任何处理

阶段一,某个参与者宕机(未完成prepare) 参与者重启后,需要进行回滚(分布式事务的状态是abort)

阶段二,某个参与者宕机(已完成prepare) 参与者重启后,参与者如果没有询问其他参与者或协调者事务是否提交的能力,恢复后事务处于悬挂状态,等待协调者指令(分布式事务的决策可能是提交,可能是回滚)

超时异常处理

阶段一,协调者发出prepare消息,但没有收到所有参与者的响应 协调者给所有参与者发送回滚指令

阶段二,协调者发出commit消息,但没有收到所有参与者的响应 协调者可异步不断对超时节点尝试提交

二阶段提交的缺点

同步阻塞

事务执行过程中,所有参与节点都是事务阻塞的。当参与者占有资源时,其他访问相关资源的进程也将处于阻塞状态。

参与者对锁资源的释放必须等到事务结束,所以与一阶段提交相比,执行同样的事务,二阶段会耗费更多时间。

事务执行时间的延长意味着锁资源发生冲突的概率增加,当事务并发量达到一定数量时,会出现大量事务积压甚至出现死锁,系统性能会严重下滑。

单点故障

一旦协调者发生故障,参与者会一直阻塞。参与者完成准备阶段后,协调者发生故障,所有的参与者都将处于锁定事务资源的状态中(事务悬挂状态),无法继续完成事务操作。

数据不一致

在提交阶段中,当协调者向参与者发送commit消息后,发生了局部网络异常或者在发送commit消息过程中协调者发生了故障, 这会导致只有一部分参与者接受到了commit消息。而这些参与者收到commit消息后就会执行commit操作。 但是其他未收到commit消息的参与者无法执行commit。 于是整个分布式系统出现了数据不一致性。

spring 实现

由于系统涉及到多个数据源,所以想用两阶段提交事务(分布式事务)来保证数据的一致性,完整性。

TODO…

参考资料

  • 2pc

https://en.wikipedia.org/wiki/Two-phase_commit_protocol

https://www.kancloud.cn/sql-jdxia/mysql/513205

https://www.linuxidc.com/Linux/2015-11/124942.htm

https://zhuanlan.zhihu.com/p/38197629

http://www.itboth.com/d/f2qaya/delete-sql-insert-ssh-mysql

http://www.voidcn.com/article/p-ngewxhzc-bhd.html

https://jin-yang.github.io/post/mysql-group-commit.html

  • spring

http://victor-jan.iteye.com/blog/671310