0
点赞
收藏
分享

微信扫一扫

两阶段提交(Two-phase Commit, 2PC)原理分析

两阶段提交(Two-phase Commit, 2PC),是一种为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的算法。本文介绍了两阶阶段提交协议的实现原理。

协议整体流程

Pasted image 20240216153737.png

上图Database A, Database C是2个参与者(worker/cohorts),一般是关系数据库。协调者(coordinator)是单独部署的节点。2PC的执行过程实际可以理解成3个阶段:

  1. 发送SQL语句阶段。这个阶段可能往多个worker分别发送多个多个读写SQL。
  2. 准备(Prepare)阶段。coordinator分别给各个worker发送询问确认单节点内的事务是否可以保证被提交。如有任何一个节点返回no,则回滚所有节点的事务。
  3. 提交(Commit)阶段。coordinator给每个worker发送提交事务的指令,最终提交本地的事务并释放资源相关的锁。

事务日志处理

Pasted image 20240601114705.png 上图有3个worker。coordinator 和 worker 都需要在一些进度点记录日志(图里面只拿了一个worker举例)。有了日志记录,任何时间点任何节点挂掉都可以通过日志来恢复事务进度。

  • coordinator需要在prepare阶段开始时记录 BEGIN 日志,内部包含事务ID和涉及的worker
  • prepare全部确认后coordinator记录COMMITTED日志。在coordinator记录完COMMITTED日志后,意味着这个事务一定会被提交。
  • worker确认事务可以提交后记录 BEGIN 和 PREPARED 日志。日志里包括写入的数据信息,比如mysql 用 redo log来记录prepare。这个日志保证了即使worker宕机,其恢复后依然可以确保事务能被提交。
  • worker在自己提交后记录 COMMITED 日志。比如,mysql 用 binlog 来记录 COMMITED 日志。
  • coordinator在所有worker返回commit成功的响应后,就可以清理掉该事务在coordinator上的日志并给客户端返回事务已完成。

故障点及处理

以下为可能的处理策略,并不对应任何软件的实现。

  • 故障发生在发送SQL语句阶段。如果任何节点发生故障,都可以直接给客户端返回错误。有些开始了事务的worker,coordinator可以将其回滚。如果coordinator挂了,则worker需要自己等待事务超时后回滚。
  • 故障发生在prepare阶段
    • 情况1:worker发生了故障。这种情况下coordinator需要不停的重试,直到worker恢复后给到响应为止。为什么coordinator不能直接回滚整体事务呢,因为coordinator无法确认故障worker是否已经进入了prepared阶段,如果忽略该worker的进度,可能导致worker上的临界资源被永久锁定。coordinator如果选择回滚整体事务也必须后续确认该故障worker也回滚了事务。
    • 情况2:coordinator发生了故障。coordinator在恢复后会从日志中读取到该已经 BIGIN 的但未 COMMITTED 的事务,coordinator会重新对每个worker发起prepare操作。worker需要保证prepare操作是幂等的(事务ID可以做为幂等Key)。
  • 故障发生在commit阶段
    • 情况1:worker发生了故障。coordinator需要重试,直到worker恢复后给回响应。因worker在prepare阶段记录了事务细节并锁定了临界资源,所以其能保证宕机恢复后事务依然一定可以提交成功。
    • 情况2:coordinator发生了故障。coordinator在恢复后会从日志中读取到该已经 COMMITTED 的但未被删除的事务记录。coordinator会重新对每个worker发起commit操作。worker需要保证commit操作是幂等的。

这里的2个核心逻辑是

  1. 通过日志可以保证事务的执行进度被记录下来,而不会因宕机而导致事务被丢失,进而导致一些worker一直处于一个异常的中间状态。
  2. 通过保证操作的幂等性,能保证在不确定操作是否已经执行成功情况下,可以进行重试。

2PC的问题

长久锁定的问题(blocking problem)

  • 在prepare之后每个worker需要继续锁定相关资源,一直到收到coordinator的commit指令。如果coordinator在prepare之后commit之前挂了,会导致临界资源在coordinator恢复前会一直处于锁定状态。(这里会阻塞掉有竞争的写入,但并不影响只读事务。)
  • 解决办法是让coordinator实现容错,让其做自动的故障恢复。 实践中coordinator可以用Raft,Paxos等协议来实现容错。Google's Spanner 使用Paxos协议来复制coordinators。但实现这些协议应该会导致coordinator的吞吐下降。

性能问题

  • 多次远程通讯,多次日志写入,锁定数据,TPS只能到几千(参考值)。

脏读的问题

  • 2PC因提交阶段是无法保证所有worker都在同一个时间点commit的,所以先被commit的资源会先被读取到。
  • 2PC是否能解决写偏差(write skew)的问题呢?理论上是可以的,最好对具体实现做测试。

思考

为什么要prepare阶段?

  • prepare 阶段 worker 会将事务进度持久化,从而保证即使 worker 宕机恢复后可以继续提交事务。所以 prepare 返回 true 代表 worker 承诺事务一定能提交
  • prepare 之前可能有多个 SQL语句,所以不能自动 prepare

2PC的设计中有没有可能去掉prepare阶段呢?

  • 是可能的,去掉prepare阶段依然可以保证写入的完整性,但出现读取到不一致数据的概率也更高。去掉prepare就跟saga的设计有些类似了。

资料

  • http://dbmsmusings.blogspot.com/2019/01/its-time-to-move-on-from-two-phase.html
  • https://www.youtube.com/watch?v=B6btpukqHpM
  • Principles of Computer System Design An Introduction_Chapter 9_atomicity.pdf
举报

相关推荐

0 条评论