分布式事务之两阶段提交

做者:Philip A. Bernstein, Vassos Hadzilacos, Nathan Goodman. 1987html

原文:Concurrency Control and Recovery in Database Systems算法

译者:phylips@bmy 2013-02-14数据库

译文:http://duanple.blog.163.com/blog/static/70971767201311810939564/安全

 

[序:历史上,数据库领域共产生过三位图灵奖得主Charles Bachman,E.F.Codd和Jim Gray服务器

1961年,通用电气公司(General Electric Co.)的Charles Bachman成功地开发出世界上第一个网状DBMS也是第一个数据库管理系统——集成数据存储(Integrated DataStore IDS),奠基了网状数据库的基础,并在当时获得了普遍的发行和应用。后Charles Bachman因在数据库方面的贡献得到1973年图灵奖。网络

1970年6月,IBM圣约瑟研究实验室的高级研究员Edgar Frank Codd在Communications of ACM上发表了A Relational Model of Data for Large Shared Data Banks。首次明确提出关系数据库模型。此后,以前基于层次模型和网状模型的数据库产品很快消亡。1972年,Codd提出了关系代数和关系演算的概念, 定义了关系的并、交、投影、选择、链接等各类基本运算, 为往后成为标准的结构化查询语言(SQL)奠基了基础。 后来Codd又陆续发表多篇论文,论述了范式理论和衡量关系系统的12条标准,为关系数据库创建了一个严格的数学模型。而此时网状数据库的标准化工做正在进行,同时有人认为关系数据库是一个过于理想化的模型,对它的性能表示担心。又是出现了关系数据库与反关系数据库两派(历史老是如此类似)。后E.F.Codd得到1981年图灵奖。架构

1976年,IBM 的研究员 Jim Gray 发表了名为Granularity of Locks and Degrees of Consistency in a Shared DataBase的论文,正式定义了数据库事务的概念和数据一致性的机制。后Jim Gray因在数据库和事务处理研究和实现方面的开创性贡献得到1998年图灵奖。并发

 

迄今,数据库已是一个理论研究很是成熟同时商业化也足够高的领域了。同时,它自己所涉及的知识面也是很是广的,这篇文章咱们将主要关注事务处理方面的提交问题。分布式

 

上世纪70年代,在关系数据库理论基本成熟后,各大公司在关系数据库管理系统的实现和产品开发中遇到了一系列技术问题。主要是在数据库的规模越来越大,数据库的结构越来越复杂,又有越来越多的用户共享数据库的状况下,如何保障数据的完整性、安全性、并发性以及故障恢复的能力,这些问题成为数据库产品是否能实用化并最终为用户接受的关键因素。Jim Gray在解决这些重大技术问题,使RDBMS成熟并顺利进入市场的过程当中,起到了关键性做用。归纳地说,解决上述问题的主要技术手段和方法是:把对数据库的操做划分为称为“事务”的基本原子单位,一个事务要么全作,要么全不作(即all-or-nothing原则);用户在对数据库发出操做请求时,须要对有关的数据“加锁”,防止不一样用户的操做之间互相干扰;在事务运行过程当中,采用“日志”记录事务的运行状态,以便发生故障时进行恢复;对数据库的任何更新都采用“两阶段提交”策略。以上方法及其余各类方法总称为“事务处理技术”。oop

 

首先来回顾一下有关事务处理的一些经典书籍文章。Jim Gray在1977年写过一篇文章“Notes on Data Base Operating Systems”,总共80多页,应该算是早期很是完善地介绍数据库这门学科内容的教科书了,文章内容来源普遍,聚集了那个时代众多数据库先驱们的想法和思考。IBM的System R对该文章产生了重要影响。不少内容都是直接来源于Jim Gray与他人的讨论,所以不少想法已经没法找到明确的参考文献。在这篇文章中,将一个数据库系统划分红了以下四个主要组件:数据字典(dictionary),数据通讯(data communications),数据库管理(database manager),事务管理(transaction management)。尤为对事务管理中的并发控制(locking)和系统可靠性机制(recovery)进行了深刻讲解。好比5.7.5节,引用的就是Jim Gray 1976年的经典论文“Granularity of Locks and Degrees of Consistency in a Shared DataBase”的内容。同时描述了大量事务处理方面的细节,以及所用到一些重要协议算法,像两阶段提交协议。固然这里关于事务处理的大部分想法也都源自于IBM IMS开发者的实际经验以及做者自己参与的System R的开发过程。以后在1981年的文章“The Transaction Concept:Virtues and Limitations”中,Jim Gray提出事务应具备三个属性:Consistence,Atomicity,Durability。而ACID的叫法,则是在1983年由Theo Harder和Andreas Reuter在他们发表的文章“Principles of Transaction-Oriented Database Recovery”中首次提出,以后才被普遍使用的。顺便来看一些其余重要概念的起源,Write-Ahead-Log概念是Ron Obermark在1974年提出的,大概也是在那个时候他实现了nested-two-phase commit protocol;Earl Jenner和Steve Weick在1975年首次实现了两阶段提交协议,固然实际上它可能源于由Niko Garzado在1970年实现的某些系统,此后IMS,System R都提供了不一样形式的实现;Paul McJones和Jim Gray参考Warren Titlemann在INTERLISP中的实现,在System R中实现了DO-UNDO-REDO 策略。此外Lampson和Sturgis在1976年的论文” Crash Recovery in a Distributed System”中独立提出了两阶段提交协议,Lewis, Sterns和Rosenkrantz在论文” System Level Concurrency Control for Data Base Systems”中独立提出了nested commit protocol。1987年,Philip A. Bernstein, Vassos Hadzilacos, Nathan Goodman的书籍“Concurrency Control and Recovery in Database Systems”,全书共300多页,系统描述了数据库系统中的并发控制和恢复机制。

 

1992年,C.Mohan的ARIES出世,这篇论文详细描述了一系列关于恢复,并发和存储管理的算法。事实上该论文中的不少想法源于C.Mohan与Don Haderle的交流,那个时候Don Haderle正担任IBM DB2的主架构师。经过这个交流,C.Mohan意识到DB2对于某些问题的处理方式与System R有所不一样,同时System R基于shadow page的恢复算法留下了一个开放性问题“如何在使用write-ahead logging的状况下对记录加锁”。ARIES就是在这样的背景下写出来的。因为这篇文章试着综合了全部相关工做,并对那些可能成为ARIES算法一部分的特征进行了解释,致使这篇文章写出来后很是长,接近70页,这也使得它成为TODS历史上惟一超过50页限制而被接受的文章。1993年,Jim Gray和Andreas Reuter 合写的关于事务处理的经典书籍“Transaction Processing:Concepts and Techniques”出版。

 

最后再简要介绍下Jim Gray吧:关系数据库领域大师,因在数据库和事务处理研究和实现方面的开创性贡献而得到1998年图灵奖。美国科学院、工程院两院院士,ACM和IEEE两会会士。1966年在UC Berkeley拿到数学工程系学位,以后去纽约大学科朗研究所呆了一年,1967年又回到了Berkeley,仅用了一年半就拿到了博士学位,不过论文题目不是关于数据库的,而是关于语法分析理论的。博士毕业后,先在Berkeley作了两年博士后,继续从事理论研究与系统开发工做,以后去了IBM。在IBM工做期间参与和主持了IMS、System R、SQL/DS、DB2等项目的开发。后任职于微软研究院,主要关注应用数据库技术来处理各学科的海量信息。2007年1月独自驾船出海后失踪。

 

起初Jim Gray是作操做系统研究的,之因此转行研究数据库系统,据Jim Gray本身的说法是这样的:有一次,他上司的上司来到他办公室,对他说“Jim啊,你看如今市场上已经出现了这么多的操做系统,可是尚未一个像样的网络操做系统和数据库系统,若是你真想在IBM作出点成绩的话,研究网络操做系统和数据库系统是颇有前途的!”,因而Jim Gray就遵从了他的建议。其实当时Jim Gray正在作关于面向对象操做系统的研究,如今看来这个选择也是对的,由于面向对象操做系统这个方向很快就被人们放弃了。

 

Jim Gray有个好习惯,就是常常记笔记写备忘录。不管什么时候去旅行,都会写旅行报告;不管什么时候与人谈话都会把获得的想法作备忘录,凭借这些,他写了许多文章,参加了不少国际会议,并出了名。开发System R时,同事Franco一年写了两万行代码,但Jim Gray常常花时间写做,旅游,一年才写了一万行代码,因而老大常常会去敲他的门说“快点写代码!!-_-”,根据Jim Gray的回忆,整个开发过程当中他大概写了五万到七万行代码,主要是涉及并发控制,系统恢复,系统启动,安全性管理等方面。

 

本文主要参考“Concurrency Control and Recovery in Database Systems”,重点回顾下2PC的相关知识。

]

 

背景介绍

 

事务处理的困难源于两个方面:concurrency和failures。为了达到高的性能,并发是必要的。而在现实中,计算机系统会面临各类各样的故障,操做系统可能会出错,硬件也有可能会出错。当这些错误发生时,应用程序可能会在正执行的过程当中被打断,而这可能会产生错误的结果。好比用户正在转帐,在中间失败可能会致使一个帐户上的钱少了,可是另外一个帐户却没有收到钱的状况。Recovery就是要避免因故障而产生错误结果。因此就引出了Concurrency Control和Recovery这两个概念。

 

对于一个解决了并发控制和恢复问题的系统来讲,在用户看来,全部程序的执行都是原子的(看起来就好像没有其余程序在并行执行),可靠的(看起来好像并无故障发生)。这种原子的可靠的程序执行过程就称之为事务。并发控制算法用来保证事务原子性地执行。

 

一个分布式事务T有一个主节点—即发起事务的那个节点。T首先将它的操做提交给位于主节点上的TM(transaction manager),以后TM再将这些操做转发给其余对应的节点。好比一个Read(x)或者Write(x)操做将会被转发给x所在的那个节点,而后由那个节点的调度器和DM进行处理,看起来就像是一个本地事务同样。以后会再将操做结果返回给主节点上的TM。所以,除了须要对节点间的请求和响应进行路由外,在一个分布式DBS中对读写操做的处理与一个集中式的系统并无什么不一样。

 

如今来考虑下T的Commit操做。这个操做须要转发给哪些节点呢?不像Read(x)或者Write(x)操做那样,只须要关注存储了x的那个节点,Commit操做须要关注全部与事务T的处理相关的节点。所以,T的主节点上的TM须要将T中的Commit操做转发给那些T中数据访问涉及的全部节点。对于Abort操做也是同样。也就是说要求单个处理操做(Commit或者Abort)必须做用在分布式DBS的多个节点上,这是集中式与分布式事务处理的一个根本区别。

 

这个问题比它表面上看起来的要复杂地多。仅仅让分布式事务主节点上的TM给其余全部节点发送Commit操做是不够的。这是由于TM发送了Commit操做并不意味着事务提交完成了,还要DM执行这个Commit操做。虽然TM发送了Commit操做,可是调度器有可能拒绝这个请求,同时将事务Abort。在这种状况下,若是事务是分布式的,那么还须要全部相关节点也都要进行Abort。

 

集中式与分布式事务的另外一个重要的不一样点在于它们各自所需关注的错误的属性上。在集中式系统中,错误都是要么不错要么全错(all-or-nothing),也就是说要么系统正常工做事务正常处理,要么系统出错不会有任何事务完成。可是在分布式系统中,可能出现部分失败(partial failures)的状况,某些节点正常工做可是其余一些节点出错了。

 

在分布式系统中故障不必定致使严重问题的这种属性为分布式系统提升自身可靠性创造了机会。同时这也是分布式系统倍受推崇的一个属性。可是不多被人提到的是,这种局部失败的状况正是形成分布式系统中不少难解的问题的根源。

 

在事务处理中就有这样的一个难解问题,即事务终止的一致性。如前所述,分布式事务的Commit或Abort操做必需要保证在事务的数据访问涉及到的全部节点上执行。在容许局部失败的状况下,这个问题变得很是复杂。

 

咱们将能够保证这种一致性的算法称为原子性提交协议(ACP-atomic commitment protocol)。本章咱们的主要目标就是介绍这种能够容忍各类错误的ACP协议。在具体介绍以前,咱们先详细介绍下分布式系统中存在的各类错误类型。

 

分布式系统中的故障

 

分布式系统由两种组件组成:负责处理信息的节点和负责在节点间传递消息的通讯链路。一般能够用一个图来进行表示,图的结点表明节点,无向边表明双向的通讯链路(如图7-1)。

分布式事务之两阶段提交 - 星星 - 银河里的星星

咱们假设图是连通的,即任意结点间都存在一条路径。所以任意两个节点均可以经过它们间的一个或者是多个链路进行通讯。咱们将这种可让节点间进行消息传输的软硬件设备统称为计算机网络。咱们不须要担忧消息是如何被路由的,对于分布式数据库系统来讲路由是网络系统提供的基本服务。

 

节点故障

 

当节点发生系统故障时,进程将会异常终止同时内存内容会丢失。在这种状况下,咱们就说节点发生故障了。当节点从故障中恢复时将会首先执行一个恢复过程,使得节点能够达到一个一致性状态,以后才能够继续正常的处理过程。

 

在这种故障模型中:站点要么是能够正常工做(称它是operational的)要么是彻底没法工做(称之为down),它永远都不会执行错误的动做,这种类型的行为也被称为fail-stop。很明显这有些理想化。因为软硬件bug的存在,计算机偶尔仍是有可能会执行不正确的动做。经过不断的测试以及软硬件内建的冗余,能够构建出一个基本上知足fail-stop的系统。咱们并不想在本文中深刻讨论这些技术,咱们假设节点都是fail-stop的。本章中咱们将要讨论的各个协议的正确性将会基于这个假设。

 

尽管单个节点只有两个状态,要么正常工做要么中止工做,可是不一样节点可能处于不一样状态。这样仍然会存在一种局部故障(partial failure)的状况:某些节点正常某些节点down掉。若是全部节点都down掉的话就是total failure的状况。

 

Partial Failure很难处理。从根本上说,这是由于正常工做的节点根本没法肯定失败的那些节点的状态。在这些不肯定性解决以前,正常节点可能会被阻塞,没法Commit或者Abort一个事务。原子性提交协议的一个重要设计目标就是要尽可能将单个节点的故障的影响最小化,使得其余节点能够继续处理。

 

通讯故障

 

通讯链路也有可能发生故障。故障可能使得节点之间没法通讯。可能有各类各样的通讯故障:消息内容可能会因为链路上的噪声被破坏;链路可能临时失灵,致使单个消息彻底丢失;链路可能会中断一段时间,致使经过它的全部消息都丢失。

 

消息的损坏能够经过错误检测码进行检测,而后在接收者检测到错误的时候进行重传来进行高效地处理。因为链路故障致使的消息丢失能够经过重传丢失的消息进行处理。同时,也能够经过从新路由来下降链路丢包几率。若是消息是从节点A传给节点B的,可是因为链路断开致使网络没法对消息进行传输,那么网络系统会尝试寻找一条新的从A到B的路径。错误检测码,消息重传以及从新路由一般都是由计算机网络协议提供的功能。

 

不幸的是,即便有自动的从新路由,节点和链路的故障仍可能致使节点间没法通讯。当节点间的全部路径上都包含一个有故障的节点或链路时就会出现这种状况。一般称之为网络分区,它会将全部节点分割为两个或者更多个子集,在子集内部的节点相互能够通讯,子集之间没法通讯。好比,图7-2就展现了图7-1的一个系统分区。图中包含两个分区{B,C}和{D,E},是由节点A和链路(C,D)(C,E)的故障形成的。

 

当节点恢复和链路修复后,那些以前没法交换信息的节点的通讯就会恢复。好比图7-2,若是节点A恢复或者(C,D)(C,E)中有一个修复了,那么这两个分区就又链接起来了,全部的节点就又能够通讯了。

分布式事务之两阶段提交 - 星星 - 银河里的星星

 

咱们能够经过设计一个高度互联的网络来下降网络分区发生的几率,这样部分节点和链路的故障不会致使节点间的路径所有不通。可是,构建一个高度互联的网络须要使用更多的组件,所以成本会更高。此外,网络拓扑还会受到其余因素的限制,好比地理位置和通讯媒介。所以,分区是没法彻底避免的。

 

总之,通讯故障发生在节点A没法与节点B没法通讯时,尽管两个节点都没有down掉。通讯故障可能会引起网络分区。若是两个节点能够通讯,消息就会被正确地传输。

 

没法传送的消息

 

节点和通讯故障的存在须要咱们可以处理那些不能发送的消息。消息可能会由于接收者down掉或者网络分区的存在,而没法发送。有两种选择:

1. 持久化消息。由网络系统对消息进行存储,在能够发送时再发送

2. 丢失消息。网络系统再也不尝试重发

咱们选择第2种方式,这也是不少设备采用的方式。第1种方式须要很是复杂的协议,它们自己很是相似于ACPs,基本上等因而将原子性提交问题扩散到了系统中另外一个部分。

 

采用第2种方式的网络系统会尝试通知消息的发送者消息被丢掉了。可是这样也有其固有的不可靠性。若是节点没法对收到的消息进行确认,网络是没法判断出是这个节点根本没有收到消息仍是它已经收到了消息只是确认时失败了。即便它能区分出这个不一样,对未发送消息的发送者的通知也可能致使无穷递归,好比若是进行通知的消息自己未能发送成功,那么通知的发送者也须要再次被通知,就会再次产生通知消息,如此循环。所以,是不能依赖这种未发送消息的通知机制的。

 

经过超时检测故障

 

节点故障和通讯故障均可能使得一个节点没法与另外一个节点进行通讯。也就是说,若是节点A没法与节点B进行通讯,那么要么是由于B出问题了,要么是由于A和B分属于不一样的分区。一般,A没法区分出这两种状况。它只是知道没法与B进行通讯。

 

那A怎么知道没法与B进行通讯呢?一般都是经过超时机制来肯定的。A向B发送一个消息,而后等待一个预先肯定的时间段,称之为超时时间段。在此期间,若是收到了响应,那么很明显A和B是能够通讯的。若是超过了这个时间段,A尚未收到响应,那么A就认为它没法与B完成通讯。这个时间段必须是从A到B发送消息加上B处理消息再加上返回给A这三个过程所花费的最大可能时间。找出一个合理的超时时间并非一件简单的事情。它依赖于不少难以量化的参数:节点和通讯链路的物理特性,系统负载,消息路由算法,时钟精度等等不少因素。实践中一般也只是选择一个对大多数状况都算合理的超时时间。在使用超时机制时,咱们都是假设已经定好了这样的一个超时时间取值。事实上,即便是还没到超时时间,节点也有可能认为它没法与另外一节点通讯。这种状况的发生一般是由于时钟不精确致使的。咱们一般将这类故障称为timeout failures或者performance failures。与网络分区不一样,这类故障可能会致使很奇怪的状况,好比A认为它能够与B进行通讯,可是B却认为它没法与A通讯;或者是A认为它能够与B通讯,同时B认为能够跟C通讯,可是A却认为它不能与C进行通讯。

 

原子性提交

 

考虑一个执行过程涉及到节点S1,S2…Sn的分布式事务T。假设S1上的TM负责管控T的执行。在S1上的TM向S1,S2…Sn发送Commit操做以前,它必须确保每一个节点上的调度器和DM已经准备好而且能够进行Commit。不然,T就可能在某些节点上进行了Commit,而在某些节点上进行了Abort,这样就产生了不一致。咱们来看一下调度器和DM知足什么样的条件才算是准备好而且能够进行Commit了。

 

只要T在节点上知足了可恢复条件,那么该节点上的调度器就容许进行Commit(T)操做。也就是说,只要其余事务针对事务T读取的全部值的相关写入都提交了就能够了。须要注意的是若是调度器产生的执行过程是不会级联abort的,那么上面的条件就老是成立的。在这种状况下,由于调度器随时均可以处理Commit(T)操做,S1的TM发送Commit操做就不须要征求调度器的意见。

 

只要T在节点上知足了Redo规则,那么该节点上的DM就能够执行Commit(T)了。也就是说,该节点上全部由T写入的value值都已经进入了可靠性存储中—数据库或者日志中,取决于DM的恢复算法。若是T仅仅是向某些节点提交了读请求,那么它就不须要征求这些节点的DM意见。

 

只有当获得了来自全部节点的调度器和DM的容许后,S1上的TM才能向全部节点的调度器和DM发送Commit(T)。实际上,这就是咱们要在下一节中讨论的两阶段提交协议(2PC)。为何咱们要将这样一个看起来很简单的思路放到单独的一节中讨论呢?缘由是前面的这些讨论并未解决节点或者通讯故障。若是说在处理中有一个或者多个节点出错了会怎么样呢?若是有一个或多个消息丢失了会怎样呢?原子性提交问题的真正难点就在于设计一个具备高度容错性的协议。

 

为简化讨论以及专一于原子性提交问题的本质,咱们再也不局限于TM-调度器-DM模型。为将原子性提交问题从事务处理的其余概念中剥离出来,咱们假设对于每一个分布式事务T,在执行T的每一个节点上都有一个进程。这些进程负责为事务T实现原子性提交。咱们把在T主节点上的进程称为T的协调者。剩余进程称为T的参与者。协调者知道全部参与者的名字,所以它能够向它们发送消息。参与者知道协调者的名字,可是它们相互之间并不知晓。

 

须要强调的是协调者和参与者都是咱们为了阐述的方便进行的抽象。实际实现中并不须要参与事务执行的每一个节点为每一个事务建立一个独立进程。一般,这样的实现都会是很低效的,由于须要管理大量的进程。协调者和参与者进程都是一种抽象,实际中在每一个节点上能够由单个或多个进程提供它们的功能,并且一般都是由多个事务共享的。

 

咱们还假设每一个节点都包含一个分布式的事务日志(DT log),协调者和参与者能够将事务相关的信息记录在日志中。DT log必须保存在可靠性存储中,由于它的内容必须不受节点故障的影响。

 

严格地讲,原子性提交协议(ACP)是一种由协调者和参与者执行的算法,经过它来保证协调者和全部的参与者要么是将事务提交要么是将事务回滚。咱们能够更精确地描述以下。每一个进程只能投两种票:Yes或No,同时最终只能达成一个决定:Commit或Abort。ACP是一种可让进程达成以下决定的算法:

AC1:全部达成决定的进程达成的都是相同决定

AC2:进程一旦达成决定,就不能再改变该决定

AC3:只有当全部进程都投Yes的时候,才能达成Commit决定

AC4:若是没有故障而且全部进程投的都是Yes,那么决定最终将会是Commit

AC5:假设执行中只包含算法设计中能够容忍的那些故障,在执行中的任意时刻,若是全部现有故障都已修复同时在足够长的时间内都再也不有新故障发生,那么全部进程最终将会达成一个决定。

 

该问题的抽象形式与TM-调度器-DM模型的事务处理联系以下。只有当A的调度器和DM已经准备好而且能够进行Commit,节点A上的进程才能投Yes。若是进程决定Commit(或Abort),那么A的DM要执行Commit(或Abort)操做。在执行该操做时,节点A就像一个集中式DBS,采用第6章中的算法。实际上,处理事务的不一样节点可使用不一样的DM算法。

 

如今咱们再讨论下这些条件。AC1是说事务终止的一致性。须要注意的是,咱们并未要求全部进程达成一个决定。这也是一个不现实的目标,由于一个进程可能在发生故障后永不恢复。咱们甚至也不要求全部正常的进程达成一个决定。这也是不现实的,尽管缘由没有那么显而易见。可是,咱们确实是要求一旦全部故障被修复全部进程能达成一个决定(AC5),这个需求就将那些一旦发生故障就容许进程永远处于未决议状态的无心义协议排除了。

 

AC2是说节点上事务的执行结果是不可更改的。若是事务一旦提交(或abort),那么以后它就不能再被abort(或提交)。

 

AC3是说只有当事务执行中涉及的全部节点都赞成时事务才能提交。AC4是AC3的逆的一个弱化版本。它确保了在某些状况下必须达成Commit决定,所以就将那些老是采用选择Abort的平凡解法的协议排除在外了。可是咱们也并不要求AC3的逆彻底成立。由于就算全部进程都投的是Yes,可是最终仍是有可能Abort的(好比发生了故障,进程投的Yes并未被接收到)。关于AC3的一个很是重要的推论是,在进程还未投Yes的状况下,它能够在任意时刻单方面的选择Abort。另外一方面,一旦投了Yes它就不能再单方面地采起行动。在进程投了Yes以后到它获取足够的信息来肯定最终决定是啥以前,这中间的这个时间段称为进程的不肯定区间(uncertainty period)。当进程处在这个时间段时,咱们就说进程是不肯定的(uncertain)。在这个时间段内,进程既不知道最终决定是要Commit仍是Abort,也不能单方面地决定Abort。

 

场景1:在P处于不肯定状态时,故障致使进程P与其余进程不可通讯。根据不肯定区间的定义,在故障恢复以前进程没法达到决定状态。

 

在继续处理以前进程必须等待故障被修复,也就是说它被阻塞了。阻塞并非咱们指望的,由于它可能致使进程等待任意长的时间。场景1代表通讯故障可能致使进程被阻塞。

 

场景2:在处于不肯定状态时,进程P发生故障。在P恢复时,它没法仅经过它自身决定状态。它必须与其余进程进行通讯以肯定决定是啥。

 

咱们将进程不须要与其余进程进行通讯就可以进行恢复的能力称为独立可恢复性(independent recovery)。这种能力很是吸引人,由于它简单低廉。此外,若是缺少这种能力那么在全部进程都发生故障的状况下,会致使阻塞。好比,咱们假设p是在一个total failure中第一个恢复的进程,由于p处于不肯定状态,所以它须要与其余进程进行通讯以肯定状态,可是其余的都还down着呢,所以它就没法与它们进行通讯,所以p就被阻塞了。

 

这两个场景代表,在进程处于不肯定状态时发生的故障可能致使严重的问题。那么咱们是否可以设计出一种没有不肯定阶段的ACPs?不幸的是,不能。咱们有以下一些结论:

1. 若是可能发生通讯故障或者彻底故障,那么全部的ACPs均可能会致使进程被阻塞

2. 没有一种ACP能够保证故障进程的独立可恢复性

 

两阶段提交协议

 

两阶段提交协议是最简单最流行的ACP。在没有故障发生的状况下,它的执行过程以下:

1. 协调者发送一个VOTE-REQ消息给全部的参与者

2. 当参与者接收到VOTE-REQ消息后,它会发送一个包含参与者投票结果的消息(YES或NO)给协调者做为响应。若是参与者投的是No,它会决定Abort事务并中止运行

3. 协调者收集来自全部参与者的投票信息。若是全部的消息都是YES,同时协调者投的也是Yes,那么协调者就会决定进行Commit,并向全部参与者发送COMMIT消息。不然协调者就会决定进行Abort,并向全部投Yes的参与者发送ABORT消息(那些投No的参与者已经在第2步中决定Abort了)。以后,对于这两种状况协调者都会中止运行

4. 每一个投Yes的参与者等待来自协调者的COMMIT或ABORT消息。收到消息后执行相应动做而后中止运行。

 

2PC的两个阶段是指投票阶段(步骤1和2)和决定阶段(步骤3和4)。参与者的不肯定区间始于向协调者发送YES(步骤2),终于接收到COMMIT或ABORT消息(步骤4)。协调者没有不肯定区间,由于只要它投了票结果就肯定了,固然它投票时须要知道参与者的投票结果(步骤3)。

 

很容易能够看出2PC知足条件AC1-AC4。不幸的是,目前为止的描述并不知足AC5。有两个缘由,首先在协议的不少点上,进程在继续处理以前必需要等待消息。可是消息可能会因为故障而没法到达。所以,进程可能会无限等待下去。为避免这个问题,须要使用超时机制。当进程的等待由于超时而打断时,进程必须采起特定的动做,称之为超时动做。所以,为知足AC5,必须为协议中进程须要等待消息的地方引入合理的超时动做。

 

其次,当进程从故障中恢复时,AC5要求进程可以达成与其余进程可能已经达成的决定相一致的决定(可能还必需要等待某些其余故障修复以后才能达成这样的决定)。所以,进程还必需要将某些信息存入可靠性存储中,好比DT log中。为知足AC5,咱们还必需要说明须要将哪些信息存入DT log以及如何在恢复时使用它们。下面咱们分别来考虑这两个问题。

 

超时处理

 

在2PC中有以下三个须要进程等待消息的地方:在步骤2,3和4的开始阶段。在步骤2中,参与者须要等待来自协调者的VOTE-REQ消息。这发生在参与者进行投票以前。因为任何一个进程在它投Yes以前均可以单方面地决定进行Abort,所以若是参与者在等待VOTE-REQ消息时超时,它能够简单地决定进行Abort,而后中止运行。

 

在步骤3中,协调者须要等待来自全部参与者的YES或NO消息。在这个阶段,协调者也尚未达成任何决定。此外,也没有任何参与者已经决定要Commit。所以协调者能够决定进行Abort,可是必需要向给它发送YES的每一个参与者发送ABORT消息。

 

在步骤4中,投了Yes的参与者p要等待来自协调者的COMMIT或ABORT消息。此时,p处于不肯定状态。所以,与前面两种状况下进程能够单方面进行决定不一样,在这种状况下参与者必须与其余进程商议决定如何动做。这个商议过程须要经过执行一个terminaion protocol来完成。

 

最简单的terminaion protocol以下:在与协调者的通讯恢复以前p始终保持阻塞。以后,协调者通知p对应的决定结果。协调者确定支持这样作,由于它没有不肯定区间。该terminaion protocol知足AC5,由于若是全部的故障都修复了的话,p就能与协调者通讯,而后就能达到决定状态。

 

这种简单的terminaion protocol缺点在于,p可能要经历没必要要的阻塞。好比,假设如今有两个参与者p和q。协调者先给q发送了一个COMMIT或ABORT消息,可是在发送给p以前发生了故障。所以,尽管p是不肯定的,可是q不是。若是p能够与q进行通讯,那么它就能够从q那得知最终的决定结果。并不须要一直等待着协调者的恢复。

 

可是这意味着须要参与者之间须要相互知晓,这样它们才能相互直接通讯。可是咱们前面描述原子性提交问题时,只是说协调者认识全部参与者,参与者认识协调者,可是参与者初始时相互并不知晓。可是这也不是什么大问题,咱们可让协调者在发送VOTE-REQ消息时将参与者信息附加在上面,发给全部参与者。这样在参与者接收到该消息后,相互就都知道了。事实上,在发送该消息以前它们之间也是不须要相互知晓的,由于若是参与者在接收到VOTE-REQ消息前超时了,它能够单方面地决定进行Abort。

 

下面咱们再来介绍下cooperative terminaion protocol:参与者p若是在不肯定区间超时,它会发送一个DECISION-REQ消息给全部其余进程,设为q,问下q是否知道决定结果或者可否单方面地作出决定。在这个场景中,p是initiator,q是responder。有以下三种状况:

1. q已经决定进行Commit(或Abort):q简单地发送一个COMMIT(或ABORT)消息给p,而后p进行相应动做

2. q还未进行投票:q能够单方面地决定进行Abort。而后它发送ABORT消息给p,p会所以决定进行ABORT

3. q已经投了Yes可是还未作决定:q也是处于不肯定状态,所以没法帮助p达成决定。

 

对于该协议来讲,若是p能够同某个进程q通讯而且上述1或2成立,那么p就能够不经阻塞地达成决定。另外一方面,若是p通讯的全部进程都是3成立,那么p就会被阻塞。那么p将会一直阻塞,直到故障修复的出现了某个进程q知足条件1或2为止。至少会有一个这样的进程,即协调者。所以这个terminaion protocol知足AC5。

 

恢复

 

考虑一个从故障中恢复的进程p,为知足AC5,p必须可以达成一个与其余进程相一致的决定—不必定是恢复完成后就要达成,多是在等其余故障恢复后的某个时间段内。

 

假设p恢复时它还记得发生故障时的状态—后面咱们会再讨论如何作到这一点。若是p是在它向协调者发送YES以前发生了故障(2PC的步骤2),那么p能够单方面地决定进行Abort。另外,若是p是在接收到来自协调者的COMMIT或ABORT消息或者已经单方面地决定Abort以后发生的故障,那么此时它已经完成了决定。在这些状况下,p均可以独立地完成恢复。

 

可是若是p是在处于不肯定区间时发生了故障,那么恢复时就没法仅经过自身来完成决定。由于它已经投了Yes,有可能全部其余进程也都投了Yes,这样在p挂掉的时候就已经决定要Commit了。可是也有可能其余一些进程投了No或者是根本尚未投,最终决定变成是要Abort。仅仅依赖本地信息,p没法区分出这两种可能,所以必须与其余进程通讯再作决定。这也从一个方面说明了为什么无法进行独立地恢复(independent recovery)。

 

这种状况就跟p等待来自协调者的COMMIT或ABORT消息而超时了的状况是同样的。所以p能够经过使用terminaion protocol达成决定。须要注意的是p可能会被阻塞,由于它能够进行通讯的那些进程也是处于不肯定状态。

 

为了记住故障发生时的状态,每一个进程必须保存一些信息到节点的DT log中。固然,每一个进程只能访问它本地的那个DT log。假设使用的是cooperative terminaion protocol,DT log的管理方式以下:

1. 当协调者发送VOTE-REQ消息时,它会在DT log中写入一条start-2PC记录。该记录包含了全部参与者的标识符,同时写入能够发生在发送消息以前或以后。

2. 若是参与者投了Yes,它会在向协调者发送YES消息前向DT log中写入一条记录。该记录包含了协调者名称及参与者列表(由协调者经过VOTE-REQ消息提供)。若是参与者投了No,它能够在向协调者发送NO消息以前或以后写入一个abort记录。

3. 在协调者向参与者发送COMMIT消息以前,它会向DT log中写入一条commit记录。

4. 当协调者向参与者发送ABORT消息时,它会向DT log中写入一条abort记录。写入能够发生在发送消息以前或以后。

5. 在收到COMMIT(或ABORT)消息后,参与者向DT log中写入一条commit(或abort)记录。

在上述讨论中,在DT log中写入一条commit(或abort)记录实际上就表明进程决定了是Commit仍是Abort。

 

如今能够来简单介绍下事务提交过程是如何与事务处理的其余活动进行交互的。一旦commit(或abort)记录写入了DT log,DM就能够执行Commit(或Abort)操做了。固然这其中还有大量的细节须要考虑。好比,若是DT log是做为DM log的一部分实现的,那么commit(或abort)记录的写入就可能须要经过调用本地DM的接口来实现。一般来讲,细节上如何来操做取决于本地DM采用了什么样的算法。

 

当节点S从故障中恢复时,分布式事务在S上的执行取决于DT log的内容:

? 若是DT log包含一个start-2PC记录,那么说明S就是协调者所在节点。若是它还有commit(或abort)记录,那么说明在发生故障前协调者已经作出了决定。若是这两种记录(commit或abort)都没有找到,那么协调者能够经过向DT log中插入一条abort记录来单方面地决定进行Abort。这样能够工做的关键在于,协调者是先将commit记录写入DT log,而后再发送COMMIT消息的(上面的第3点)。

? 若是DT log中没有start-2PC记录,那么S就是参与者节点。那么有以下三种可能:

? DT log中包含一个commit(或abort)记录。那么说明在发生故障以前,参与者已经达成了决定。

? DT log中没有yes记录。那么要么是参与者是在投票前发生的故障,要么投的是No(可是在发生故障前尚未完成abort记录的写入)。(这也是为什么yes记录必需要在发送YES消息前写入日志的缘由;参考上面的第2点。)所以,它能够单方面地经过向DT log中写入一条abort记录决定进行Abort。

? DT log中包含了yes记录,可是没有commit(或abort)记录。那么说明参与者是在不肯定区间内发生的故障。它能够经过使用terminaion protocol来达成决定。回想一下,yes记录中包含了协调者名称以及全部的参与者,这正是terminaion protocol所须要的。

 

图7-3和7-4给出了2PC协议和cooperative terminaion protocol的具体实现,包括了上面讨论的关于超时处理和DT log相关的处理动做。算法的表达比较随意,可是直接明了。咱们采用send和wait来表示进程间通讯。好比“send m to p”,m表明一个消息,p表明一个或多个进程,总的意思就是执行进程将m发送给p中的全部进程。“wait for m from p”,m表明一个或多个消息,p表明一个进程,总的意思就是执行进程一直等待,直到收到来自p的消息m。若是消息可能来自多个目标,那么以下两种表示方式,“wait for m from all p”表示进程会一直等待直到收到p中全部进程发送的消息,“wait for m from any p”表示进程会一直等待直到收到p中某个进程发送的消息。为避免无限等待,能够在“wait for”语句后面加上一个“on timeout S”进行限定,S表明某种执行语句。这意味着若是消息在一个预先定义的超时时间内还未收到,那么就再也不继续等待,同时语句S将会被执行,以后控制流将会继续正常往下执行,除非S中作了一些特殊控制。若是消息在正常时间内到达,那么S将会被忽略。

分布式事务之两阶段提交 - 星星 - 银河里的星星

分布式事务之两阶段提交 - 星星 - 银河里的星星

 

  

尽管咱们一直描述的是针对单个事务的ACPs,可是很明显DT log将会包含来自多个事务的与原子性提交相关的记录信息。所以,为防止不一样事务间记录信息的混淆,start-2PC,yes,commit和abort记录都会包含一些信息标识它们属于哪一个事务。此外,也须要对DT log中的那些过时信息进行垃圾回收。在垃圾回收时,有以下两个基本原则须要遵照:

GC1:至少要等到RM执行了RM-Commit(T)和RM-Abort(T)以后,节点才能将事务T的相关日志记录从它的DT log中删除。

GC2:至少有一个节点在它收到事务T涉及的全部节点都执行了RM-Commit(T)和RM-Abort(T)的消息以前,不会将事务T的相关日志记录从它的DT log中删除。

 

GC1是说与事务T的执行相关的节点,在该事务做用于该节点以前它必须牢记该事务的存在。GC2是说,与事务T的执行相关的某个节点,在该节点确认事务在全部节点上成功执行以前,必须记住事务T的状态。若是不是这样的话,那么从故障中恢复的一个节点可能会没法获取T的状态,同时它也永远都不能肯定事务T的状态,这就违背了AC5。

 

经过使用每一个节点上的本地信息就能够确保GC1。可是GC2须要节点间通讯的支持。为了实现GC2一般有两种极端策略:GC2只对一个节点成立,一般都是协调者;GC2对于T的执行相关的全部节点都成立。

 

咱们关于ACP的研究都是从单个事务的角度来看的,这也隐藏了节点恢复时的一些问题。在一个节点恢复时,它必需要为那些故障发生前还未commit或abort的全部事务完成ACP的执行。何时节点能够恢复正常的事务处理呢?在一个集中化的DBS恢复后,在重启过程完成以前事务是没法被处理的,由于须要恢复那些已存储的数据库状态。分布式DBS中的节点恢复也与之相似,由于某些事务可能会被阻塞。在这种状况下,在该恢复节点的DBS在全部被阻塞的事务被提交或abort以前也是不可访问的。

 

避免这种问题的方法取决于所使用的调度器类型。考虑strict 2PL的状况。当节点的恢复过程已经为全部的未阻塞事务完成决定时,它应该通知调度器来从新获取那些在故障发生以前由被阻塞的事务占有的锁。实际上,一个被阻塞的事务T只须要获取它的写锁。问题在于锁表一般是保存在内存中的,所以在系统发生故障时会丢失。为避免丢失这些信息,负责管理T的原子性提交的进程须要将T的写锁也记录到它写入到DT log中的yes记录中。固然若是这些信息能够从该节点的RM维护的日志中获取,就不必这样作。好比,在第6章描述的undo/redo算法,由于在T投Yes以前,全部的更新都会进入日志。这些记录就能够用来肯定T的写锁集合。

 

针对2PC的评价

 

咱们能够从以下几个维度来对2PC进行评价:

Resiliency:能够容忍哪些故障?

Blocking:进程是否有可能被阻塞?若是是,什么状况下会被阻塞?

时间复杂度:达成决定须要花多长时间?

消息复杂度:达成决定须要多少次消息交换?

 

前两个维度用来衡量协议的可靠性,后两个用来衡量协议的效率。可靠性和效率是两个冲突的目标:任意一个均可以以另外一个为代价来得到。协议如何选择取决于对于特定的应用来讲哪一个目标更重要。可是,不管协议如何选择,咱们都须要尽力对无端障时的状况进行优化—主要是提升系统的正常工做速率。

 

咱们经过对非阻塞状况下节点达成一个决定最坏状况下所须要的消息交换轮数进行计数,来衡量ACP的时间复杂度。轮表明了消息到达目标节点所需的最大时间。用于故障检测的超时机制就是基于这个最大的消息延迟是已知的这样一个假设。须要注意的是,一轮内可能会有不少消息被发送—就看有多少个发送者-接收者对。若是一个消息必须等另外一个消息接收到以后才能发送,那么这两个消息就确定属于不一样的轮。好比,2PC中的COMMIT和YES消息就属于不一样的轮,由于前者必需要等接收到后者后才能发送。另外一方面,全部的VOTE-REQ消息就属于同一轮,由于它们是并发地发送到目标节点的。对于全部的COMMIT消息来讲也是这样。用于计算轮数的一种简单方式就是认为同一轮中发送的消息都是同时发出的,同时具备相同的延迟。所以,每一轮都是从消息发送时开始,在消息被接收后结束。

 

使用轮来衡量时间复杂度实际上就忽略了消息处理所需的时间。这也是合理的,由于一般状况下消息传输延迟都要远远大于消息处理延迟。可是,若是要想获得一个更精确的时间复杂度度量,还须要将另外两个因素考虑进来。

 

首先,如目前所知,进程必须在发送或接收特定消息时将它们记录到DT log中。在某些状况下,这样的一个文件服务器是在一个本地网络中,这种针对可靠性存储的访问也会引入与消息发送至关的开销。所以针对可靠性存储的访问次数也会成为影响协议的时间复杂度的重要因素。

 

其次,在某些轮中,进程是要将某个消息发送到全部其余进程。好比,在第一轮协调者会向全部参与者发送VOTE-REQ消息。这种行为称为广播。为了广播一个消息,进程必须将同一消息的n个拷贝放到网络上,n表明接收者数目。一般来讲,将消息放到网络上的时间与在网络上传输的时间相比要小不少。可是若是n足够大,那么为广播所进行的准备时间也会变得很大,须要考虑在内。

 

所以,关于时间复杂度的更精确的衡量可能须要将总的轮数,访问可靠性存储的时间以及消息广播时间加起来。可是,后面的分析中咱们仍是会忽略后面两个因素,只关注轮数。

 

咱们经过协议所使用的消息总数来衡量消息复杂性。这也是合理的,由于消息自己并不长。可是若是它们很长的话,咱们也是须要将长度考虑在内的,而不能仅仅考虑个数。在咱们本章中所讨论的协议的消息都是很短的,所以咱们仍是只关注消息个数。

 

如今咱们来考察下2PC的resiliency,blocking,时间和消息复杂度。

Resiliency:2PC能够容忍节点故障和通讯故障(不管是网络分区仍是超时故障)。咱们前面介绍的超时动做那部分的内容自己并未对超时产生的缘由作任何假设, 经过这一点就能够得出该结论。超时多是由节点故障,分区或者仅仅是由于伪超时致使的。

Blocking:2PC是会经历阻塞的。若是进程在不肯定区间超时,同时能够进行通讯的那些进程自己也是不肯定的,那么进程就会被阻塞。实际上,即便是在只有节点故障的状况下,2PC仍然可能会被阻塞。若是要精确计算阻塞发生的几率,必需首先要知道故障发生的几率,同时这种类型的分析也超出了本书的范围。

时间复杂度:在没有故障发生的状况下,2PC须要三轮:(1)协调者广播VOTE-REQ消息;(2)参与者回复投票信息;(3)协调者广播最终决定结果。若是有故障发生,可能还要额外加上terminaion protocol须要的那两轮:第一轮用于超时的参与者发送DECISION-REQ请求,第二轮用于接收到消息的进程度过不肯定区间后进行响应。可能会有多个参与者同时调用terminaion protocol。可是不一样的调用能够重叠进行,所以合起来也仍是只有两轮。

所以,在有故障发生的状况下那些未被阻塞或没有发生故障的进程须要五轮才能达成决定。这与故障发生的个数无关。根据定义,一个被阻塞的进程可能会被阻塞无限长的时间。所以,为了获得有意义的结论,咱们在考虑时间复杂度时须要将那些被阻塞的进程排除在外。

消息复杂度:令n表明参与者数目(所以总进程数就是n+1)。在2PC的每轮中,有n个消息被发送。所以在没有故障的状况下,协议将会使用3n个消息。

cooperative terminaion protocol将会被那些投了Yes可是未收到来自协调者的COMMIT或ABORT消息的全部参与者调用。假设有m个这样的参与者。所以将会有m个进程初始化terminaion protocol的执行,每一个发送n个DECISION-REQ消息。最多有n-m+1(未处于不肯定状态的最大进程数)个进程会响应第一个DECISION-REQ消息。收到这些响应后,将会有一个新的进程从不肯定状态退出,所以它会向另外一个terminaion protocol执行实例发送响应消息。所以最坏状况下,由terminaion protocol发送的消息数将是:

 

在n=m时该多项式取得最大值,也就是当全部参与者都在处于不肯定区间时超时。所以,terminaion protocol贡献了多达n(3n+1)/2个消息,对于整个2PC协议来讲就是n(3n+7)/2。

 

参考文献

 

http://stevens0102.blogbus.com/logs/43402056.html

 

附录

 

分布式事务之两阶段提交 - 星星 - 银河里的星星

 

左一Ken Thompson、左二Butler Lampson、右二Jim Gray, 右一Niklaus Wirth

 

 

分布式事务之两阶段提交 - 星星 - 银河里的星星

 

JimGray与它的同事Gianfranco Putzulo and Irving Traiger

相关文章
相关标签/搜索