跳转到内容
← 返回计算理论
计算理论当代约 18 分钟阅读

Gray 1978:两阶段提交怎样把多处改动收成提交或中止

Notes on Data Base Operating Systems: Two-Phase Commit

两阶段提交原子提交协调者已准备阻塞

1978 年,吉姆·格雷(Jim Gray)的长篇讲义《Notes on Data Base Operating Systems》收入鲁道夫·拜耳(Rudolf Bayer)、罗伯特·格雷厄姆(Robert M. Graham)与格哈德·塞格米勒(Gerhard Seegmüller)主编的 Operating Systems: An Advanced Course,作为 Springer LNCS 第 60 卷印出,页码 393 至 481。讲义覆盖事务、锁与崩溃恢复。本页的对象停在其中被印出来的那套两阶段提交装置:一名协调者、若干参与者,先询问能否提交,再广播提交或中止,以及参与者一旦写下「已准备」之后可能被阻塞。

它不是 兼职议会与 Paxos。它不是 Raft,不是 etcd,也不是视图戳复制。那些协议要在缺席与丢信下锁定一个已被提出的值,或复制一条命令日志。本页要锁定的是另一句话:分散在多处的改动要么全部生效,要么全部作废。它也不是格雷与兰波特 2006 年的 Paxos Commit——那篇后文把提交决定交给共识去复制,用来对照 1978 年单协调者装置的脆弱点,不是本页的对象。它更不是一份教人如何在生产集群上挂起事务管理器的操作单。分布式系统 总览里已经点过这个名字;本页停在印出来的装置本身。

破除误解:两阶段提交不是多数共识,也不是两次举手

第一层误解把两阶段提交听成 分布式共识算法 的一种方言。共识问的是:若干进程各自提出一个值,最后选定同一个值,并且这个值必须是某个进程真正提出过的。原子提交问的是另一句:一笔事务在所有参与者上都提交,或在所有参与者上都中止;只要有一名参与者不能准备完成,全体就不得提交。多数在共识里是仲裁交集,用来锁住已被选定的值。两阶段提交里没有「过半数即可提交」。一名参与者投「否」,协调者必须中止。把 2PC 读成「Paxos 的简化版」,会把有效性条件偷换成另一道题。

第二层误解把「两阶段」读成两次举手,或读成 Paxos 的准备加接受。格雷印出的第一阶段不是收集「你们比较喜欢哪个值」,而是收集一张不得回头的字据:你能否把本地改动做成一旦听到提交就不可撤回的状态。参与者若答「能」,就必须先把「已准备」写进稳定存储,然后才能回「是」。这一步之后,它不再拥有单方面中止或单方面提交的权利。第二阶段不是第二轮偏好加总,而是协调者根据已收齐的字据,宣布唯一的全局决定。Paxos 的第一阶段约束后来者必须继承旧值;2PC 的第一阶段约束答「是」的人必须等待宣布。两套两阶段共享一个数字,不共享同一种约束。

第三层误解把发表年当成「格雷发明了提交」。1978 年的讲义把当时 System R 一线已经在用的恢复与提交装置写成可讲授的笔记,对象是印出来的协议骨架,不是一份专利权声明。几乎同时,巴特勒·兰普森(Butler Lampson)与霍华德·斯特吉斯(Howard Sturgis)在 Xerox PARC 的技术报告里,用崩溃恢复的语言描述了同一类先准备、再决定的协议。本页不把优先权官司写进标题。读者面对的是 1978 年 LNCS 里那套协调者—参与者装置,而不是后来写厚的事务教科书章节。

第四层误解把阻塞听成实现写错,或听成「加超时就能取消」。参与者在已准备之后,本地既不能当事务已提交——别人可能已经按中止在清场;也不能当事务已中止——别人可能已经按提交在释放。协调者若在写出决定之前消失,或决定已经写出却送不出去,已准备的参与者只能等。超时可以让尚未准备的人中止;超时不能让已经准备的人在不知道决定的情况下安全地自作主张。把阻塞写成某次参数配错,或写成一份可以在活集群上复现的挂起步骤,会离开这份对象。阻塞是已准备状态加上单一决定权的几何,不是菜谱。

装置:协调者、参与者、准备,然后提交或中止

装置从角色开始。协调者(coordinator)是这笔分布式事务的事务管理器:它发起准备,收集表决,强制写出自己的决定,再把决定送给所有参与者。参与者(participant)是各处的资源管理器,各自只能看见自己那一块。在 1978 年的骨架里,协调者只是那个被指定来宣布唯一决定的角色,不是「最可靠的那台机器」这一经验判断。一台机器可以在不同事务里扮演不同角色。本页不把角色写成机房名单。

第一阶段常被叫作准备(prepare)。协调者向全体参与者发出询问:你们能否提交?参与者若不能——锁无法化解、本地约束被违反——就在本地中止,回答「否」,此后不必再等别人。这一侧没有阻塞:投「否」的人已经把结局写成中止。参与者若能,就必须先把「已准备」记录强制写进稳定存储,再回答「是」。强制写不是礼貌。崩溃之后若忘掉这张字据,恢复程序就不知道自己曾经许诺过等待。数据库与事务 里那句「要么全做、要么不做」,在单机上靠的是同一份日志;在多处资源上,靠的是每处都先把自己许诺写牢。

第二阶段常被叫作提交或中止(commit / abort)。协调者必须先从全体参与者收齐表决。若有任何「否」,或有人迟迟不答而被模型允许当成失败,协调者把「中止」强制写进自己的日志,再把中止送给所有人。若全部为「是」,协调者把「提交」强制写进自己的日志,再把提交送给所有人。参与者收到决定后,同样把决定写进稳定存储,释放为准备而持有的资源,并向协调者确认。决定一旦被协调者写进稳定存储,就不能改口。参与者在已准备之后看见的,不是一份可供协商的草案,而是那条已经写出的决定的抄本。

稳定存储把时间切开。准备记录写成功之前,参与者崩溃可以当自己从未答应,恢复后中止是安全的。准备记录写成功之后,重启读到「已准备」,就还不能单方面结束。协调者一侧对称:决定记录写成功之前崩溃,恢复后可以视为尚未决定,通常按中止收场——因为还没有人被允许根据一条已写出的提交去生效。决定记录写成功之后崩溃,恢复后必须重发那条已经写出的决定,而不能换成相反的结局。这些句子是装置的不变量。把「强制写」读成文件系统调优,会把持久记忆误认成运维旋钮。

装置保证的是原子性这一侧。所有作出决定的参与者,决定必须相同。没有人被允许先提交再中止。提交只在全体已经答应准备的前提下才合法。若没有故障并且全体都答「是」,决定就是提交——否则协议会把一次人人都能完成的事务凭空杀掉。1978 年的讲义用角色与日志把这套几何写在纸上。它没有保证「协调者永远活着」,也没有保证「已准备的人在有限时间内一定听见决定」。后一句就是阻塞要占据的位置。

兼职议会与 Paxos 的多数相交允许在部分缺席下仍选定一个值;2PC 的提交要求全体准备完成,缺席的那一名若尚未表决,协调者不能当作默认的「是」。视图戳复制与 Raft 把领导者写进复制日志;1978 年的协调者不是被多数选出来的日志领导者,它是这笔事务预先指定的宣布人。线性化 谈的是并发对象的调用与返回能否收成一条合法顺序历史;两阶段提交谈的是一笔跨资源事务的结局是提交还是中止。PostgreSQL 的 MVCC 让同一行上的读不阻塞写,靠的是版本;它不代替跨资源的原子结局。SQL 里后来出现的准备事务语句,是这份装置的工程方言,不是本页要填写的运行手册。

阻塞意味着什么,以及它没有证明什么

阻塞发生在一个精确的窗口里。窗口的左端是参与者已经写下「已准备」并投出「是」。窗口的右端是它收到并记下协调者的决定。窗口之内,参与者持有为准备而获取的资源,既不能对本地客户端宣称已提交,也不能把改动丢掉当中止。若协调者在这个窗口里变得不可达,已准备的参与者就停在窗口里。尚未进入已准备的参与者可以中止;已经进入的那些人不能。这不是「系统死锁」的同义词:本地锁等待图可以是空的,事务仍然结束不了,因为它在等一个尚未送达的宣布。

它证明了什么。它证明:用一名协调者加全体表决,可以把多处资源的结局收成单一的提交或单一的中止,只要决定被写进稳定存储、已准备的人愿意等待。它证明:准备记录是一道单向门,门后的正确动作是等待,不是猜。它证明:把「我本地已经能提交」误当成「全局已经提交」,会拆开原子性——另一名参与者可能投了「否」,或协调者已经按中止落笔。原子性在这里是一条安全性质:任何有限执行前缀都不得出现一部分提交、一部分中止。等待保护的是这条安全性质。把等待讲成可用性承诺,句子就接错了。

它没有证明参与者在协调者缺席时仍能作出安全决定。非阻塞提交是另一份对象:1981 年斯肯(Dale Skeen)的三阶段提交,以及后来用共识复制决定的协议,都改了装置,不是把 1978 年的两阶段再读一遍。它没有推翻 FLP。FLP 说的是完全异步下确定性共识不能在所有合法调度上既安全又终止;两阶段提交从一开始就没有在完全异步里承诺终止。阻塞是单点决定权在已准备窗口里的活性失败,不是「选出了两个不同的提交结局」。把 2PC 的阻塞读成 FLP 的一个例子,或把 FLP 读成「所以 2PC 会分叉」,都把安全与活性接反了。

它也没有证明可以用多数派提交去代替全体准备。2006 年,格雷与兰波特在 ACM Transactions on Database Systems 发表《Consensus on Transaction Commit》,把提交或中止决定交给 Paxos 去复制,从而拿掉「唯一事务管理器崩溃则已准备者无限等待」这一脆弱点。那是后来的对照,不是 1978 年讲义里已经印好的协议。共识可以在多数活着时选定一个值;原子提交仍然禁止在有人未准备时提交。用共识去复制的是已经可以宣布的决定,不是把「一名参与者说不」凭空改写成「多数说是」。把 Paxos Commit 提前写进 1978 年,会把后文的修补误认成本页的装置。

概念上,已准备之后的参与者依赖协调者的决定抄本;抄本不到,窗口就不关闭。这句话足够说明阻塞。它不需要被翻译成对运行中集群的干扰说明书。缩短窗口是后来的实现工作,不把 1978 年的不变量扩成「集群永远可写」。分布式系统 可以在分区下继续提供别的服务;那笔停在已准备窗口里的事务,仍然在等它的那一条决定。

两阶段提交是一种原子提交协议,不是一种共识协议,也不是一种复制日志。它把「全体准备」写成提交的前提,把「已准备则等待宣布」写成崩溃后仍须遵守的字据,并把决定权放在单一协调者的稳定存储上。它因此能保证不会出现分裂的结局,也能在协调者缺席时让已准备者停住。停住不是证明失败,是活性未被写进定理。单机上的 事务 原子性、同一节点上的 多版本并发、跨副本的共识与线性化读,各管各的句子。本页只把 1978 年印出的协调者、参与者、准备与提交—中止讲清楚。概念证据到此为止。

跨域连接

  • 社会选择理论:阿罗定理问的是如何把许多人的排序加总成社会排序;两阶段提交问的是如何在「有人不能履行」时禁止生效。多数在社会选择里是偏好聚合规则,可以循环、可以被议程操纵。2PC 的「全体说是才提交」不是偏好加总,而是一票否决式的有效性:一名参与者投「否」,结局必须是中止,并不比较各处改动谁更值得。把准备阶段读成「少数服从多数的民主算法」,会把资源约束错认成福利判断。协议不评价事务好不好,只保证不会出现一半账户已扣款、另一半尚未入账还被当成已经成功。
  • 合同的生命周期:要约与承诺要形成合意,履行却常常不是瞬时完成。提存与托管把标的交给一名中间人,双方先把自己这一侧做到不可单方面撤回,再等中间人按约定放行。已准备很像这张不可撤回的字据:参与者已经把自己的给付准备好,却还没有被允许视为已经清偿。阻塞则像中间人在放行之前失踪——债务人不能把货拿回去,债权人也不能把货当成已经到手。比喻能讲清「先约束自己、再等宣布」,不能把协调者写成法庭:合同有意思表示瑕疵与救济;1978 年的装置假定参与者不会对协调者说两套互相矛盾的已准备记录,也没有损害赔偿这门词汇。
  • 认识论:参与者写下「已准备」,是关于自身能力的一次不可撤回的断言;协调者写出「提交」或「中止」,才是关于全局结局的一次断言。两者不是同一件知识。已准备的人知道自己许诺了等待,并不因此知道结局是什么。协调者知道自己写进日志的决定,并不因此知道每名参与者都已经学会它。没有上帝视角的节点,只能用收到的消息当证据。证据不足时,正确动作是继续等待,而不是猜一份结局。这不是怀疑主义游戏,而是把「真」与「知」拆开:真由协调者的稳定存储构成,知由抄本送达构成。工程事故常发生在把本地内存里的「我能提交」当成已被选定的公共知识。
  • 知识:公共知识要求不仅人人知道,而且人人知道人人知道。两阶段提交并不生产这种高阶闭合。提交记录被协调者写出的那一刻,结局在模型里已经成为事实;参与者要等到抄本到达才知道这个事实。分区可以让知道再推迟,推迟不是把事实改成两份。把「集群里已经提交」与「我已经知道已经提交」混成同一个时刻,会把活性失败误诊成数据分叉。活性失败是知识迟迟不能送达;分叉才是安全被打破。本页的装置管的是后一种错误不得发生,不管抄本在路上走多久。
  • 时间哲学:事务在何时成为已提交?协议拒绝一个全体共享的「此刻」。协调者强制写出提交记录,是模型里结局锁定的那一侧;各参与者记下抄本,是各自局部历史上的另一组事件。这些事件不必同时,也不必被墙上时钟标成同一毫秒。先后由消息与日志的因果给出。把提交时刻写成统一墙钟,会把原子性绑到校准误差上;装置绑定的是字据与决定记录,不是同时性。已准备窗口里没有薄薄一层人人共用的现在,只有一张已经写出、尚未被所有人读到的决定。

参考文献

  • Jim Gray, "Notes on Data Base Operating Systems", in R. Bayer, R. M. Graham & G. Seegmüller (eds.), Operating Systems: An Advanced Course, Lecture Notes in Computer Science 60, Springer, 1978, pp. 393–481. DOI: 10.1007/3-540-08755-9_9. —— 本文对象:协调者、参与者、准备与提交/中止,以及已准备之后的阻塞。
  • Butler W. Lampson & Howard E. Sturgis, "Crash Recovery in a Distributed Data Storage System", Xerox PARC Technical Report, 1979(稿本约 1976 年起流传). —— 几乎同时的崩溃恢复叙述,用以标明 1978 年讲义不是真空中的唯一文本。
  • Jim Gray & Leslie Lamport, "Consensus on Transaction Commit", ACM Transactions on Database Systems 31(1), 2006, pp. 133–160. DOI: 10.1145/1132863.1132867. —— 后来的对照:用 Paxos 复制提交决定,拿掉单协调者阻塞点;不是本页装置。
  • Michael J. Fischer, Nancy A. Lynch & Michael S. Paterson, "Impossibility of Distributed Consensus with One Faulty Process", Journal of the ACM 32(2), 1985, pp. 374–382. DOI: 10.1145/3149.214121. —— 用以标明阻塞是活性失败,不是异步确定性共识被「证伪」或「实例化」成 2PC。

延伸阅读

  • 与 数据库与事务 对读:单机 ACID 的原子性是同一句话的局部情形;跨资源时才需要准备字据与单一宣布。
  • 与 兼职议会与 Paxos、分布式共识算法 对读:先分清「全体准备才能提交」与「多数相交锁定一个已提出的值」,再看 2006 年 Paxos Commit 改的是哪一层。
  • Dale Skeen, "Nonblocking Commit Protocols", ACM SIGMOD, 1981, pp. 133–142. —— 后来的非阻塞提交;假设与分区行为都不是 1978 年两阶段的缺页。
  • 与 FLP、线性化、PostgreSQL 的 MVCC 对读:活性边界、并发对象的顺序历史、单节点版本链,各不是原子提交协议本身。