这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
分布式事务
2PC
二阶段提交(Two-phase Commit):为了保证分布在不同节点上的分布式事务的一致性,我们需要引入一个协调者来管理所有的节点,负责各个本地资源的提交和回滚,并确保这些节点正确提交操作结果,若提交失败则放弃事务。即保证了分布式事务的原子性:即所有结点要么全做要么全不做
三个假设:
-
引入
协调者(Coordinator)和参与者(Participants),互相进行网络通信 -
所有节点都采用
预写式日志,且日志被写入后即被保持在可可靠的存储设备上 -
所有
节点不会永久性损坏,即使损坏后仍然可以恢复
工作过程:
-
协调者作为一个
中心节点,负责处理参与者之间可能存在的冲突,参与者负责提交事务并告知协调者。 -
当希望提交事务时,会先进入
Prepare阶段,协调者向参与者发送询问信息,参与者执行事务,但不提交,返回自己当前的状态是否正常。 -
当所有参与者返回正常的确认信号后,进入
Commit阶段,协调者即可通知参与者进行事务的 commit,并收到 Ack 信号确认 commit 完成。
三种异常情况如下:
- Coordinator不宕机,Participant宕机。如下图所示,需要进进行
回滚操作,此时由于并没有commit,所以这也是2PC的优势
-
Coordinator宕机,Participant不宕机。可以起新的Coordinator,待查询状态后,重复二阶段提交
-
Coordinator宕机,Participant宕机。需要数据库管理员介入保证一致性,这也是2PC无法解决的问题
回滚:在Prepare阶段,如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。
2PC存在的问题:
-
性能问题。由于二阶段提交需要多次节点间的网络通信,耗时过大,同时资源需要进行锁定,徒增资源等待时间。
-
协调者单点故障问题。如果事务协调者节点宕机,需要另起新的协调者,否则参与者处于中间状态无法完成事务。
-
网络分区带来的数据不一致问题。一部分参与者收到了协调者的commit 消息,另一部分参与者没收到 commit 消息,导致节点之间数据的不一致。
例如给A扣款,B的金额没有变,所以当commit失败需要整个事务进行回滚。但是由于A已经看到了扣款但是又回滚了,影响不好,所以需要引入锁,在事务未完成是不可以进行查询的,但是引入锁会造成阻塞影响可用性 -
commit成功,但是ack信息协调者没有收到,此时仍然需要回滚,浪费性能。
整体2PC流程如下:
3PC
三阶段提交在二阶段提交的基础上进行了优化,引入了超时机制和准备阶段,以解决二阶段提交单点故障和阻塞的问题。
三阶段提交将 Prepare阶段进一步拆分,得到 CanCommit,PreCommit,DoCommit 三个阶段:
超时机制
同时在协调者和参与者中引入超时机制。如果协调者或参与者在规定的时间内没有接收到来自其他节点的响应,就会根据当前的状态选择提交或者终止整个事务,解决协调者单点故障导致参与者长期阻塞的问题,此时也说明3PC并没有完全解决2PC数据不一致的问题
准备阶段
在第一阶段和第二阶段中间引入了一个准备阶段,也就是在提交阶段之前,加入了一个预提交阶段。在预提交阶段排除一些不一致的情况,保证在最后提交之前各参与节点的状态是一致的。
CanCommit 阶段
协调者向参与者发送请求操作(CanCommit 请求),询问参与者是否可以commit,例如账户只有两万却提交转账三万的请求,然后等待参与者的响应;参与者收到 CanCommit 请求之后,回复 Yes,表示可以顺利执行事务;否则回复 No。(我个人理解类似做TCC中Try操作)
PreCommit 阶段
协调者根据参与者的回复情况,来决定是否可以进行 PreCommit 操作 或 中断事务。
如果所有参与者回复的都是“Yes”,那么协调者就会执行事务的预执行:
-
发送预提交请求。协调者向参与者发送 PreCommit 请求,进入预提交阶段。
-
事务预提交。参与者接收到 PreCommit 请求后执行事务操作,
并将 Undo 和 Redo 信息记录到事务日志中,同时锁定当前记录。这也是增加CanCmmmit的优势,避免了不成功还要写入日志即资源浪费,避免后面真正commit无法成功的阻塞 -
响应反馈。如果参与者成功执行了事务操作,则返回 ACK 响应,同时开始等待最终指令
如果任何一个参与者向协调者发送了“No”消息,或者等待超时之后,协调者都没有收到参与者的响应,就执行中断事务的操作:
-
发送中断请求。协调者向所有参与者发送“Abort”消息。
-
中断事务。参与者收到“Abort”消息之后,或超时后仍未收到协调者的消息,执行事务的中断操作。