分布式事务基解决方案之2PC

Posted by Liao on 2023-03-08

一、2PC定义

2PC是Two-Phase commit protocol,原子性提交的两阶段提交协议

  • Trasaction Coordinator(TC,事务协调器):实质是一台服务器,负责对事务进行协调
  • Participants(事务参与者):可以有多个,负责执行事务中部分任务的服务器叫参与者。它们各自负责自己的那部分事务,存在于不同的服务器上

只有TC来决定是否提交事务或终止事务,Participants之间不会通信

二、2PC流程

此处假设TC是调度器,假设有A、B是两个事务参与者,A负责对x+1,B负责对y-1,

客户端请求TC执行A、B事务,等待TC的响应

TC运行整个事务,向服务器A发送get请求, A执行事务;TC向B发送put请求,B执行事务。A、B执行事务过程中,会锁住它要读的这部分数据

1、 阶段一 Prepare phase:投票阶段

  • 事务询问:进行任何事务之前,TC需要确保不同的Participants有能力去执行它们负责的那部分事务。因此TC会给所有Participants发送Prepare消息

  • 执行事务:各个Participants执行事务操作,并将UndoRedo信息记录到事务日志中。

    Participants收到Prepare消息后,知道该事务的进度接近完成,但还没完成,它们回去查看它们的状态,判断它们实际是否能完成事务中的操作(当它们进行最后操作时,它们可能需要中断这个事务来干预这个死锁,或者因为它们崩溃而重启了,会忘记了事务相关的事情,并且无法完成事务,所以要check下状态)

  • 反馈事务询问响应:如果Participants成功完成了事务,则给TC回复YES响应,表示事务可以执行;如果Participants没有成功执行事务,则返回 NO响应,表示事务不可以执行。

TC将事务的信息记录到日志中。。。

2、阶段二 Commit phase

  • 执行事务提交:所有Participants都返回OK响应
    • 发送提交请求:如果A、B都返回OK,表示可以提交事务,TC将会给给A、B发送Commit请求。
    • 事务提交:Participants接收到Commit请求后,会正式执行事务提交操作,并在完成事务提交之后释放整个事务执行期间占用的事务资源。(可以把事务相关的信息从日志中清除。)
    • 反馈事务提交结果:Participants完成事务提交后,会给TC返回ACK消息(YES,我收到结果了!)
    • 完成事务:TC收到所有Participants的ACK响应后,完成事务,并提交该事务,并持久化到磁盘上;为了遵循Two-Phase Locking规则,当Participants看到commit或abort消息后,会释放该事务中用到的所有锁(其它事务就可以使用数据),并让事务的修改结果对外可见(展示给其它事务),它可能会对客户端或用户进行回复,删除日志相关事务的记录。
  • 中断事务:任何一个Participants返回NO响应
    • 发送回滚请求:但如果任何一个Participants回复NO(可能因为故障、数据不一致而需要中断),会对所有Participants节点发送Rollback请求,以回滚事务(体现了原子性,要不全部做,要不全部不做)
    • 事务回滚:Participants收到Rollback请求后,会利用在阶段一记录的Undo日志来执行事务回滚操作,并在完成回滚后释放整个事务执行期占用的资源。
    • 反馈事务回滚结果:Participants在完成事务回滚后,向TC发送ACK消息。
    • 中断事务:TC收到所有Participants的ACK响应后,完成事务中断。

三、2PC不同情况下的崩溃

参与者角度
  • B在回复YES给TC之前已经崩溃了(包括电源故障),则TC不会返回commit。

  • B在回复YES给TC之后崩溃(主要)。这时B不知道TC返回的commit,只能等机器恢复之后再继续操作事务:

    • 由于不能因为机器故障而丢失事务,因此事务B需要在回复TC发来的Prepare之前,必须把提交事务所需要的信息持久化到磁盘上的LOG中(记录事务B在内存中事务锁管理数据的中间状态,即它所做的修改以及持有的锁,put请求锁产生的新值以及lock列表写入磁盘),以防止发生崩溃中止。若事务B在回复YES之后崩溃了,当它重启后即会恢复,并查看LOG知道崩溃前执行的那部分事务的状态,并进行恢复。当TC接发送commit/abort消息后,会读取日志来弄清楚如何结束事务B所负责的事务。

  • B收到commit消息之后,提交完事务后崩溃。这种情况B一般不需要做任何操作,因为事务已经结束了。B收到commit消息后,它需要把该事务所做的修改保存到数据库中,并且落地到磁盘,释放相关的锁,将事务信息从日志中移除,并发送ACK消息。

  • B可能会出现收到2次commit消息的情况,因为有可能TC在发送commit后崩溃了,机器恢复后要重发。

  • 由于网络原因丢失,Participants长时间没有收到Prepare消息,允许单方面向TC中止该事务。

  • 由于网络原因丢失,在Participants发送YES/NO后,长时间没有收到TC的commit/abort消息,但没权利因为超时而单方终止事务 (因为此时TC可能已经发送了commit消息给其它Participants),只能无限期地等待TC的回复(阻塞状态),让人来对TC进行修复并重启,然后读取上面的日志,发送延迟很久的commit/abort消息。TC的崩溃导致全员阻塞是2PC缺陷的主要原因!

TC角度
  • 在发送commit消息之前崩溃。没什么影响,因为并没有发送commit消息给任何一个Participants,TC可以直接终止该事务。

  • TC发送一条或多条commit消息后发生崩溃(主要问题)。同样地,不允许TC忘记事务相关信息, 因此需要TC收到Participants的回复做出commit/abort的决定前,需要把该事务的信息(事务的结果、事务id)写入日志,并持久化到存储设备中(磁盘)。当崩溃恢复后,TC会查看日志,知道当前正在执行一个事务的期间,根据日志知道应该回复commit/abort消息。

  • 由于网络原因丢失,TC长时间没有收到事务B YES/NO的回复,则会触发超时,可以直接中止该事务,并将事务状态从所保存的表中移除。

四、2PC优缺点

  • 优点:原理简单,实现方便。
  • 缺点:
    • 同步阻塞:2PC执行过程中事务执行逻辑都处于阻塞状态,其它事务需要等待很长时间才能获得锁,阻塞了其它事务的执行。同步阻塞会阻碍分布式系统的性能,可用性低。
    • 数据不一致:TC发送一条或多条commit消息后发生崩溃,导致有部分Participants收到commit消息完成事务操作,部分没有收到而没法完成事务提交,整个分布式系统会出现数据不一致的情况。
    • 单点问题:一旦TC出现问题,整个2PC流程无法运转;而且如果TC在commit后出现问题,那么其他Participants一直会处于锁定事务资源的状态,无法继续完成事务操作。(实际生产应用中,TC 都会有相应的备选节点;)
    • 速度慢:(1)大量的磁盘写入操作。为了避免因为机器崩溃而丢失事务需要写日志中到磁盘,多个磁盘写入操作,整个2PC过程会进行得稍慢;(2)大量的通信。在TC和Participants之间要发送多轮消息,里面存在大量通信,以此来让涉及多个参与者的事务执行完毕。

五、使用场景

小机房、小范围的服务器中的分片数据库或存储系统上,它们需要支持可以读取或写入多条记录的事务,并将数据分片并存储到多台服务器上,获得ACID特性的事务,可以使用2PC协议