乐趣区

浅析分布式事务中的2PC和3PC

分布式事务

大型的分布式系统架构都会涉及到事务的分布式处理的问题,基本来说,分布式事务和普通事务都有一个共同原则:

  • A(Atomic)原子性,事务要么一起完成,要么一起回滚
  • C(Consistent)一致性,提交数据前后数据库的完整性不变
  • I(Isolation)隔离性,不同的事务相互独立,不互相影响
  • D(Duration)持久性,数据状态是永久的

另外一个设计原则,分布式系统要符合以下原则:

  • C(Consistent)一致性,分布式系统中存在的多个副本,在数据和内容上要一模一样
  • A(Availability)高可用性,在有限时间内保证系统响应
  • P(Partition tolerance)分区容错性,一个副本或一个分区出现宕机或其他错误时,不影响系统整体运行

由于在设计分布式系统时,难以保证 CAP 全部符合,最多保证其中两个,例如在一个分布式系统中:
要保证系统数据库的强一致性,需要跨表跨库占用数据库资源,在复杂度比较高的情况下,耗时难以保证,就会出现可用性无法保证的情况。

故而又出现了 BASE 理论

  • Basic Available 基本可用,分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用
  • Soft state 柔性状态,允许系统存在中间状态,这个状态不影响系统可用性
  • Eventually Consistent 最终一致性 指经过一段时间之后,所有数据状态保持一致的结果

实际上 BASE 理论是基于 AP 的一个扩展,一个分布式事务不需要强一致性,只需要达到一个最终一致性就可以了。

分布式事务就是处理分布式中各个节点的数据操作,使之能够在节点相互隔离的情况下完成多个节点数据的一致性操作。

分步式事务 – 2PC

2PC 即二阶段提交,在这里需要了解两个概念

  • 参与者:参与者即各个实际参与事务的节点,这些节点相互隔离,彼此不知道对方是否提交事务。
  • 协调者:收集和管理参与者的事务信息,负责统一管理节点信息,也就是分布式事务中的第三方。

准备阶段,也称投票阶段

准备阶段中,协调者向参与者发送 precommit 的消息,参与者在本地执行事务逻辑,如果没有超时以及运行无误,那么会记录 redo 和 undo 日志,将 ack 信息发送给协调者,当协调者收到所有节点参与者发送的 ack 信息时,准备进入下一阶段,否则会发送回滚信息给各个节点,各个节点根据之前记录好的 undo 信息回滚,本次事务提交失败。

提交阶段

提交阶段中,协调者已经收到所有节点的应答信息,接下来发送 commit 消息给各个节点,通知各个节点参与者可以提交事务,各个参与者提交完毕后一一发送完成信息给协调者,并释放本地占用的资源,协调者收到所有完成消息后,完成事务。

二阶段提交的图示如下:

二阶段提交的问题

二阶段提交虽然能够解决大多数的分布式事务的问题,且发生数据错误的概率极小,但仍然有以下几个问题:

  • 单点故障:如果协调者宕机,那么参与者会一直阻塞下去,如果在参与者收到提交消息之前协调者宕机,那么参与者一直保持未成功提交的状态,会一直占用资源,导致同时间其他事务可能要一直等待下去。
  • 同步阻塞问题:因为参与者是在本地处理的事务,是阻塞型的,在最终 commit 之前,一直占用锁资源,如果遇到时间较长而协调者未发回 commit 的情况,那么会导致锁等待。
  • 数据一致性问题:假如协调者在第二阶段中,发送 commit 给某些参与者失败,那么就导致某些参与者提交了事务,而某些没有提交,这就导致了数据不一致。
  • 二阶段根本无法解决的问题:假如协调者宕机的同时,最后一个参与者也宕机了,当协调者通过重新选举再参与到事务管理中时,它是没有办法知道事务执行到哪一步的。

针对于以上提出的一些问题,衍生出了 3PC。

分布式事务 – 3PC

3PC 即三阶段提交协议。

3PC 相对于 2PC,有了以下变化:

  • 引入超时机制,在参与者和协调者中都加入了各自的超时策略。
  • 加入了一个新的阶段,canCommit,所以阶段分成了三个:canCommit、PreCommit、DoCommit。

CanCommit

这是一个准备阶段,在这一阶段中,协调者向参与者发送 CanCommit 消息,参与者接收后,根据自身资源情况判断是否可以执行事务操作,如果可以并且未超时,则发送 yes 给协调者,反之,协调者会中断事务。

PreCommit

这是预备阶段,在这一阶段中,协调者向参与者发送 PreCommit 消息,参与者接收后,会执行事务操作,记录 undo 和 redo 日志,事务执行完毕后会将 Ack 消息发送给协调者,协调者在未超时的情况下收集所有参与者的信息,否则中断事务,通知所有参与者 Abort 消息。

DoCommit

当所有参与者 Ack 消息完毕之后,协调者会确认发送 DoCommit 消息给每一个参与者,执行提交事务。原则上所有参与者执行提交完毕之后,需要发送 Committed 给协调者,协调者完成事务。否则协调者中断事务,参与者接收 abort 消息会根据之前记录的 undo 回滚。
但也要注意,此阶段中如果参与者无法及时收到协调者发来的 Docommit 消息时,也会自行提交事务,因为从概率上来讲,PreCommit 这个阶段能够不被 abort 说明全部节点都可以正常执行事务提交,所以一般来讲单个节点提交不影响数据一致性,除非极端情况。

2PC 和 3PC 的区别

相对于 2PC,3PC 主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行 commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的 abort 响应没有及时被参与者接收到,那么参与者在等待超时之后执行了 commit 操作。这样就和其他接到 abort 命令并执行回滚的参与者之间存在数据不一致的情况。

消息中间件

上面提到的关于协调者在实践过程中由谁来担任是个问题,一般来讲这个协调者是异步的,可以在参与者和协调者之间双向通信的,有良好的消息传递机制,能够保证消息稳定投递。故而一般协调者的担任者是高性能的消息中间件例如 RocketMq、Kafka、RabbitMq 等。

如图就是一个 2PC 的实践:

退出移动版