二阶段提交和三阶段提交

缘起

事务是一段访问或者更新数据的程序。它的特点是ACID。本地事务只涉及到一个数据库能够保证事务ACID了。当涉及到多个不同数据库(广义的数据库,他们可以是MQ,甚至缓存)操作时,为了保证每个数据库的操作要么都成功或者都失败,就需要额外的技术来处理。这是因为单个数据库操作会失败,同时通信失败会导致整个事务无法感知这个失败。二阶段提交(2PC)或者三阶段提交(3PC)就是用来解决这个问题。

一般来说这个有两个角色一个是事务协调者,简称TC,一个是事务参,简称TP。下文用简称来说明。

2PC

准备完毕状态

当TP把事务中修改的结果持久化到存储以后,它才能算是准备完毕。

在所有的TP准备完毕之前,不能有任何一个TP提交事务。不然就会破坏事务的原子性。比如:TP1提交了,TP2还没准备完毕,这个时候TP2crash了,因为TP2并没有保存事务相关的after-images,就没法恢复。这时就出现了TP1成功,但是TP2没有执行的情况。

2PC本质:在提交之前确保所有的TP都已经准备完毕。

协议

2pc包含两个阶段。准备阶段和提交(终止)阶段,TC和TP的通信如下。

                       TC                              TP
                     +----+  REQUEST-TO-PREPARE      +----+
                     |    | -----------------------> |    |
                     |    |         PREPARE          |    |
                     |    | <----------------------- |    |
                     |    |           NO             |    |
                     |    |                          |    |
                     |    |         COMMIT           |    |
                     |    | -----------------------> |    |
                     |    |          ABORT           |    |
                     |    |          DONE            |    |
                     |    | <----------------------- |    |
                     +----+                          +----+
                                    Messages

2pc本质是任何TP提交之前,所有的TP都必须已经准备好。

第一阶段

  1. TC向每个TP发送REQUEST-TO-PREPARE消息。
  2. TC等待每个TP的投票。
  3. TP收到REQUEST-TO-PREPARE返回消息:
    1. TP确认可以提交事务,返回PREPARE消息。
    2. TP准备资源失败,返回NO消息。
    3. 因为系统负载原因或者系统挂掉了,一直没有投票

第二阶段

  1. TC决定,分三种情况:
    1. 收到所有的TP返回PREPARE,决定COMMIT。
    2. 收到某个TP返回NO,决定ABORT。
    3. 等待TP的消息超时,决定ABORT。
  2. 向所有TP发送COMMIT或者ABORT消息。
  3. TP收到COMMIT或者ABORT之后发回DONE消息。
  4. TC收到所有的TP发回的DONE消息之后,清理本事务的资源。

阻塞

当TP发送完PREPARE之后,收到COMMIT或者ABORT之前,它无法做任何操作。因为,它不知道该提交还是终止事务。这个时候,TP就会处在一直等待状态。因此,当TC挂了,同时也没有其他已经提交的TP情况下,事务就会一直阻塞在那里。

错误处理与恢复

错误处理

等待消息超时时的处理。

TC视角

  1. 向TP发送REQUEST-TO-PREPARE消息

    没等待消息,没有处理。

  2. 等待TP的PREPARE或者NO消息

    一段时间以后没有收到所有TP的PREPARE消息或者某个TP的NO消息,TC自己决定终止事务。向每个TP发送ABORT消息。

  3. 决定提交还是终止

    没等待消息,没有处理。

  4. 向TP发送COMMIT或者ABORT消息

    没等待消息,没有处理。

  5. 等待TP的DONE消息

    等待超时以后,重新发送COMMIT或者ABORT消息。这个消息除了事务本身资源没有释放,其他资源都可以释放。

  6. 释放事务资源

    没等待消息,没有处理。

TP视角

  1. 等待TC发送REQUEST-TO-PREPARE消息

    一段时间以后没有收到所有TC的消息,TP自己决定终止事务。如果,后面再收到REQUEST-TO-PREPARE回复NO消息。

  2. 进行PREPARE操作

    没等待消息,没有处理。

  3. 如果(2)成功向TC发送PREPARE,否则发送NO

    没等待消息,没有处理。

  4. 等待TC的COMMIT或者ABORT消息

    阻塞,一直等待,超时执行终止协议。

  5. 向TC发送DONE消息

    没等待消息,没有处理。

恢复

当TC或者TP宕机重启以后需要日志辅助事务的恢复。如果没有日志,事务就没法正常结束了。

日志

                     TC                                             TP
            +-------------------+                          +-------------------+
        1 ===>                  |                          |                   |
            |log start-two-phase|                          |                   |
            |                   |   REQUEST-TO-PREPARE     |                   |
            |                   | -----------------------> |                  <=== 1
        2 ===>                  |                          |                   |
            |                   |         PREPARE          |    log prepare    |
            |                   | <----------------------- |                   |
            |                   |           NO             |                   |
            | log commit/abort  |                          |                  <=== 2
            |                   |                          |                   |
        3 ===>                  |         COMMIT           |                   |
            |                   | -----------------------> |                   |
            |                   |          ABORT           |                   |
            |                   |                          |                   |
            |                   |          DONE            |    log commited   |
            |                   | <----------------------- |                   |
            |      log done     |                          |                  <=== 3
            |                   |                          |                   |
        4 ===>                  |                          |                   |
            +-------------------+                          +-------------------+

如上图TC需要3条日志,TP需要2条日志。

TC日志

  1. log start-two-phase

    在开始2PC之前记录start-two-phase日志。包含所有TP的信息,不然TC恢复的时候,就不知道哪些TP,也就无法发送恢复信息了。

  2. log commit/abort

    在提交之前记录commit/abort日志。不然,TC发送了COMMIT消息之后失败,恢复的时候就不知道到底是提交还是失败。

  3. log done

    当收到所有的TP的DONE消息后,记录done日志。恢复的时候可以用来清除事务资源。

TP日志

  1. log prepare

    在发送PREPARE消息之前记录prepare日志。当TP没有记录日志的情况下发送了PREPARE消息给TC然后crash,恢复的时候,因为没有准备和提交日志,就不知道自己的状态了。这个时候,就会终止事务。特别在它已经发送了PREPARE消息情况下,而TC已经提交事务,就会造成数据的不一致。

  2. log commited

    在收到TC的COMMIT或者ABORT的消息以后在发送DONE之前记录commited日志。这样恢复的时候知道自己的状态,不然就会陷入不确定状态。同时,可以尽快释放一些锁资源。

恢复处理

通过上面的日志,TC有四种恢复情况,如上图:

  1. 没有任何日志。终止事务。TP等待REQUEST-TO-PREPARE超时以后会终止事务。
  2. 有start-two-phase日志,但是还没决定COMMIT或者ABORT。终止事务。因为,TP可能等待COMMIT或者ABORT,需要向每个TP发送ABORT消息。有些TP可能没有收到REQUEST-TO-PREPARE,但是发送ABORT消息是没关系的。
  3. 有commit/abort日志,还没有done日志。同样有可能TP正在等待COMMIT或者ABORT,因此向每个TP发送日志中记录的COMMIT或者ABORT。
  4. 有done日志。说明所有的TP都已经处理完了,不需要做任何事情。

通过上面的日志,TP有三种恢复情况,如上图:

  1. 没有任何日志。终止事务。

  2. 有prepare日志,还没有commited日志。执行终止协议

  3. 有commited日志。向TC发送DONE消息,或者等TC发送COMMIT或者ABORT的时候回复DONE消息。

终止协议是说当TP恢复的时候终止事务的协议。最简单的做法就是等到恢复和TC的通信,从TC哪里得到消息。这样的坏处是一直阻塞到和TC的通信恢复为止,只要TC挂了事务永远阻塞。

总结

2PC是XA规范的标准实现。最重要的问题是阻塞,即当TC以及知道事务状态的TP都挂的情况下,事务没法终止。

3PC

2PC之所以会阻塞是因为通信失败的原因会发生当一个TP处在不确定状态时,不确定其他TP是否已经终止或者提交,从而使TP一直阻塞。如果能够确保任何TP提交时确保不会存在不确定状态的TP,就不会阻塞事务了。3PC是2PC的一种改进,主要是把第二阶段拆分为两个阶段,通过PRE-COMMIT消息做到所有的TP都直到其他TP已经确定可以提交。

2PC在只出现机器故障的时候,它也是一定阻塞的。而3PC可以避免这个问题,如果出现通信故障,还是没法避免阻塞,可以降低阻塞的频率。实际系统中,由于阻塞的概率比较低、3PC实现负责以及性能比较低,因此使用的都是2PC。

协议

                       TC                              TP
                     +----+          VOTE-REQ        +----+
                     |    | -----------------------> |    |
                     |    |           YES            |    |
                     |    | <----------------------- |    |
                     |    |           NO             |    |
                     |    |                          |    |
                     |    |        PRE-PREPARE       |    |
                     |    | -----------------------> |    |
                     |    |           ABORT          |    |
                     |    |           ACK            |    |
                     |    | <----------------------- |    |
                     |    |           DONE           |    |
                     |    |                          |    |
                     |    |       DO-COMMIT          |    |
                     |    | -----------------------> |    |
                     |    |          DONE            |    |
                     |    | <----------------------- |    |
                     +----+                          +----+
                                    Messages

第一阶段

  1. TC向所有的TP发送VOTE-REQ消息。
  2. TC等待TP的消息。
  3. TP收到VOTE-REQ后,分两种情况:
    1. 如果可以执行事务,返回YES
    2. 否则终止事务返回NO。
  4. 如果(3)中返回YES,TP等待TC的PRE-PREPARE消息。

第二阶段

  1. TC决定
    1. 收到了所有TP的YES消息,决定执行事务。
    2. 收到一个或者多个TP的NO消息,决定终止事务。
  2. TC发送PRE-PREPARE消息或者ABORT消息。
  3. 如果(2)发送PRE-PREPARE,TC等待TP的ACK消息。
  4. TP向TC发送ACK消息。
  5. TP等待TC的DO-COMMIT消息。

第三阶段

  1. TC收集到所有的TP返回的ACK消息以后,向TP发送DO-COMMIT消息。
  2. TC等待TP的DONE消息。
  3. TP收到DO-COMMIT消息,向TC发送DONE消息。
  4. TC收到TP的DONE消息之后释放事务资源。

错误处理与恢复

错误处理

等待消息超时时的处理。

TC视角

  1. 向所有的TP发送VOTE-REQ消息

    没等待消息,没有处理。

  2. 等待TP的YES或者NO消息

    一段时间以后TC没有收集到所有TP的YES消息或者某个TP的NO消息,自行终止事务,并向每个返回YES消息的TP发送ABORT消息。

  3. 决定预提交或者终止

    没等待消息,没有处理。

  4. 等待TP的ACK消息

    一段时间以后TC没有收到所有TP的ACK消息。向TP发送DO-COMMIT消息,就当所有的TP都发回了ACK消息。因为,毕竟所有的TP针对VOTE-REQ消息的回答是YES,说明是可以提交的。

  5. 等待TP的DONE消息

    等待超时以后,重新发送DO-COMMIT消息。这个消息除了事务本身资源没有释放,其他资源都可以释放。

TP视角

  1. 等待TC的VOTE-REQ消息

    一段时间以后TP没有收到VOTE-REQ消息,自行终止事务。

  2. 发送YES或者NO消息

    没等待消息,没有处理。

  3. 等待TC的PRE-PREPARE或者ABORT消息

    阻塞,一直等待,超时执行终止协议。

  4. 发送ACK或者DONE消息

    没等待消息,没有处理。

  5. 等待TC的DO-COMMIT消息

    阻塞,一致等待,超时执行终止协议。这是因为,这个点上TP能够直到其他的TP都已经同意执行事务了。但是,有可能一个TP因为TC没有向它发送PRE-COMMIT就挂了,这条TP就会处于不确定状态。这样就会出现一个TP已经提交了,一个TP还不确定。如果,已经确定状态的TP都挂了,事务就没法终止。

  6. 发送DONE消息

    没等待消息,没有处理。

恢复

日志

日志内容和2PC是一样的。

                     TC                                             TP
            +---------------------+                          +-------------------+
        1 ===>                    |                          |                   |
            |log start-three-phase|                          |                   |
            |                     |         VOTE-REQ         |                   |
            |                     | -----------------------> |                  <=== 1
            |                     |           YES            |    log prepare    |
        2 ===>                    | <----------------------- |                   |
            |                     |           NO             |                   |
            |                     |                          |                  <=== 2
            |                     |                          |                   |
            |                     |      PRE-PREPARE         |                   |
            |   log commit/abort  | -----------------------> |                   |
            |                     |          ABORT           |                   |
        3 ===>                    |           ACK            |    log commited   |
            |                     | <----------------------- |                   |
            |                     |          DONE            |                  <=== 3
            |                     |                          |                   |
            |                     |        DO-COMMIT         |                   |
            |                     | -----------------------> |                   |
            |      log done       |          DONE            |                   |
            |                     | <----------------------- |                   |
        4 ===>                    |                          |                   |
            +---------------------+                          +-------------------+

TC日志

  1. log start-three-phase

    在开始3PC之前记录start-three-phase日志。包含所有TP的信息,不然TC恢复的时候,就不知道哪些TP,也就无法发送恢复信息了。

  2. log commit/abort

    在提交之前记录commit/abort日志。不然,TC发送了PRE-PREPARE消息之后失败,恢复的时候就不知道到底是提交还是失败。

  3. log done

    当收到所有的TP的DONE消息后,记录done日志。恢复的时候可以用来清除事务资源。

TP日志

  1. log prepare

    在发送YES消息之前记录prepare日志。当TP没有记录日志的情况下发送了YES消息给TC然后crash,恢复的时候,因为没有准备和提交日志,就不知道自己的状态了。这个时候,就会终止事务。特别在它已经发送了YES消息情况下,而TC已经提交事务,就会造成数据的不一致。

  2. log commited

    在收到TC的PRE-PREPARE或者ABORT的消息以后在发送ACK或者DONE之前记录commited日志。这样恢复的时候知道自己的状态,不然就会陷入不确定状态。

恢复处理

通过上面的日志,TC有四种恢复情况,如上图:

  1. 没有任何日志。终止事务。TP等待VOTE-REQ超时以后会终止事务。
  2. 有start-three-phase日志,但是还没决定PRE-PREPARE或者ABORT。终止事务。因为,TP可能等待YES或者NO,需要向每个TP发送ABORT消息。有些TP可能没有收到VOTE-REQ,但是发送ABORT消息是没关系的。
  3. 有commit/abort日志,还没有done日志。同样有可能TP正在等待PRE-PREPARE或者ABORT。因此,如果决定是提交,那么向每个TP发送日志中记录的DO-COMMIT,否则发送ABORT。这里发送PRE-PREPARE有点多余,因为在3PC里面本身就有可能存在一些故障的TP没有收到PRE-PREPARE,就收到DO-COMMIT的情况。
  4. 有done日志。说明所有的TP都已经处理完了,不需要做任何事情。

通过上面的日志,TP有三种恢复情况,如上图:

  1. 没有任何日志。终止事务。
  2. 有prepare日志,还没有commited日志。执行终止协议
  3. 有commited日志。向TC发送DONE消息,或者等TC发送COMMIT或者ABORT的时候回复DONE消息。

终止协议

不管是2PC还是3PC当TP恢复的时候,如果处在不确定状态就需要执行终止协议。在确定了事务状态的情况,最简单的做法等待恢复和TC之间的通信,获得事务状态。还有,通过其他TP的获得事务状态。如果事务状态还不确定,可以通过重新开始事务或者终止事务的方式搞定。详细的算法《Concurrency Control and Recovery in Database Systems》中7.5节有详细说明。

← 高内聚和低耦合 mysql连接中的serverTimezone参数解析 →
存档 关于