摘要:本文尝试翻译了chandy和lamport分布式快照论文。在一个分布式系统中,只要所有节点都记录各自状态,发送、接收数据也都分别记录状态,并将各自状态数据汇总成系统全局状态,则这个系统内是可以实现数据一致性的,即系统故障能够精确的恢复到故障前的状态。就像用一台相机给空中的一群鸟拍照,无法拍到全景图,如果同时用10台甚至更多相机同时抓拍,最后再将所有相机拍摄的照片合成为全景图,则可以完整的保存某一个瞬间的飞鸟的状态。
1 导言
本文提出了分布式系统中进程在计算过程中确定系统全局状态的算法。分布式系统中的进程通过发送和接收消息进行通信。一个进程可以记录它自己的状态以及它发送和接收的消息;但是不能记录其他任何东西。为了确定一个全局系统状态,进程p必须获得其他进程的合作,这些进程必须记录自己的局部状态,并将记录的局部状态发送给进程p。所有进程都不能精确地记录它们的本地状态,除非它们可以访问公共时钟。我们假定进程不共享时钟或内存。问题是设计算法,通过这些算法,进程可以记录自己的状态和通信信道的状态,从而使记录的进程和信道状态集形成一个全局系统状态。全局状态检测算法要叠加在底层计算上:它必须与底层计算同时运行,但不能改变底层计算。
状态检测算法扮演着一组摄影师的角色,他们观察一个全景、动态的场景,比如天空中充满了候鸟:这个场景如此巨大,以至于一张照片都无法完整捕捉到。摄影师必须拍摄几张快照,并将快照拼凑在一起,形成整个场景的图片。由于同步问题,不可能在同一时刻拍摄到所有快照。此外,摄影者不应干扰正在拍摄的过程;例如,他们不能让天空中的所有鸟类在拍照时保持静止。然而,合成图应该是有意义的。摆在我们面前的问题是如何定义“有意义的”,然后决定如何拍摄这些照片。
我们现在描述一类可以用全局状态检测算法解决的重要问题。设y是一个定义在分布式系统D的全局状态上的谓词函数;也就是说,对于D的全局状态S,y(S)为真或假。如果y(S)为真意味着y(S')表示从D的全局状态S可到达D的所有全局状态S'。换句话说,如果y是一个稳定的性质,并且y在D的计算中的某个点为真,那么y在该计算中的所有后面的点都为真。稳定属性的示例有“计算已终止”、“系统已死锁”和“令牌环中的所有令牌已消失”。
几个分布式系统问题可以表述为设计一个算法的一般问题,通过该算法,分布式系统中的进程可以确定系统的稳定性质y是否成立。死锁检测[2,5,8,9,11]和终止检测[1,4,10]是稳定属性检测问题的特例。算法的细节将在后面介绍。该算法的基本思想是确定系统的全局状态S,并计算y(S),以确定y是否具有稳定性质。
已经发表了几种通过确定分布式系统的全局状态来解决死锁和终止问题的算法。Gligor和Shattuck[5]指出,许多已发表的算法是不正确的和不切实际的。不正确或不切实际的算法的一个原因可能是没有很好地理解分布式计算中局部过程状态、全局系统状态和点之间的关系。本文的贡献之一就是定义了这些关系。
许多分布式算法被构造成一系列的阶段,每个阶段都由一个完成有用工作的瞬态部分组成,然后是一个稳定的部分,在这个部分中,系统无休止地、无用地循环。稳定行为的出现表明一个阶段的结束。阶段类似于顺序程序中的一系列迭代,在连续迭代没有变化之前重复,即达到稳定性。必须检测稳定性,以便一个阶段可以终止,下一个阶段可以启动[10]。计算阶段的终止并不等同于计算的终止。当一个计算终止时,所有的活动停止,消息都不会被发送,进程状态也不会改变。在稳定行为期间可能存在指示计算阶段结束的活动消息可以被发送和接收,并且进程可以改变状态,但是该活动除了用信号表示阶段结束之外没有其他用途。在本文中,我们关注稳定系统性质的检测;停止活动只是稳定性质的一个例子。
严格地说,如果死锁被“打破”并重新初始化计算,则诸如“系统死锁”之类的属性是不稳定的。然而,为了简化说明,我们将整个问题划分为
(1)检测一个阶段的终止(并通知所有进程一个阶段已经结束)和
(2)启动一个新阶段的问题。
以下是一个稳定的性质:“第k个计算阶段已终止,”k=1,2,……。因此,本文提出的方法适用于检测给定k的第k相的终止。
在本文中,我们将注意力限制在检测稳定性质的问题上。这里不考虑启动下一阶段计算的问题,因为该问题的解决方案因应用程序而异,对于数据库死锁的检测与对于扩散计算的终止的检测是不同的。
我们必须根据系统模型来介绍我们的算法。所选择的模式本身并不重要;我们本可以用其他模式来表达我们的讨论。我们将非正式地描述我们的模型,并且只描述清楚算法所需的详细程度。
2 分布式系统模型
分布式系统由一组有限的进程和一组有限的通道组成。它由一个有标记的有向图来描述,其中顶点表示进程,边表示通道。图1是一个例子。
信道被假定有无限的缓冲区,没有错误,并且按照发送的顺序传递消息。(无限缓冲区假设是为了便于说明:如果有证据表明没有进程试图将消息添加到完全缓冲区,则可以假设有界缓冲区。)消息在通道中经历的延迟是任意的,但是时延是有限的。沿信道接收的消息序列是沿信道发送的消息序列的初始子序列。通道状态是沿通道发送的消息序列,不包括沿通道接收的消息。
一个进程由一组状态、一个初始状态(来自这个集合)和一组事件定义。进程p中的事件e是一个原子行为,它可以改变p本身的状态和p上最多一个信道c的状态:c的状态可以通过沿c发送消息(如果c指向p以外)或沿c接收消息(如果c指向p)来改变。事件e由
(1)事件发生的过程p,
(2)事件发生前p的状态s,
(3)事件发生后p的状态s’,
(4)其状态被事件改变的信道c(如果有的话),以及
(5)消息M(如果有的话),沿着c发送(如果c是一个从p指出的信道)或沿着c接收(如果c指入p)。
我们用五元组(p,s,s',M,c)定义e,其中M和c是一个特殊符号,如果e的出现不改变任何通道的状态,则为null。
分布式系统的全局状态是一组组件进程和通道状态:初始全局状态是每个进程的初始状态,每个通道的状态为空序列。事件的发生可能会改变全局状态。设e=(p,s,s',M,c),我们说e可以出现在全局状态s中,当且仅当
(1)全局状态s中进程p的状态是s,
(2)如果c是指向p的通道,那么全局状态s中c的状态是一个以M为其头部的消息序列。
我们定义了一个函数next,其中next(S,e)是全局状态S中事件e发生后的全局状态。只有当事件e可以发生在全局状态S中时,next(S,e)的值才被定义,在这种情况下,next(S,e)是与S相同的全局状态,以下几种情况除外:
(1)next(S,e)中p的状态是S’;
(2)如果e是指向p的通道,则next(S,e)中c的状态是c在S中的状态,消息M从其头部删除;
(3)如果c是远离p的通道,则next(S,e)中c的状态与S中c的状态相同,消息M添加到尾部。
设seq=(ei:0<=i<=n)是分布式系统组件进程中的事件序列。我们说seq是系统的一个计算,当且仅当事件ei可以发生在全局状态Si(0<=i<=n)中,其中S0是初始全局状态,并且
Si+1=next(Si,ei) (0<=i<=n)
另一种基于Lamport[6]的模型将计算视为事件的偏序集,见[7]。
例2.1。为了说明分布式系统的定义,考虑一个由两个进程p和q以及两个通道c和c'组成的简单系统,如图2所示。
这个系统包含一个从一个进程传递到另一个进程的令牌,因此我们称这个系统为“单令牌保护”系统。每个进程有两个状态s0和s1,其中s0是进程不拥有令牌的状态,s1是进程拥有令牌的状态。p的初始状态是s1,q的初始状态是s0。每个进程有两个事件:(1)从s1到s0的转换,发送令牌;(2)从s0到s1的转换,接收令牌。流程的状态转换图如图3所示。全局状态和转换如图4所示。
系统计算对应于全局状态转换图(图4)中从初始全局状态开始的路径。系统计算的例子有:(1)空序列和(2)(p发送令牌,q接收令牌,q发送令牌)。以下序列不是系统的计算:(p 发送令牌,q发送令牌),因为当q处于s0状态时,“q 发送令牌”事件不能发生(不能既发送又接收)。
为简洁起见,四个全局状态按转换顺序(见图4)将被称为(1)in-p、(2)in-c、(3)in-q和(4)in-c',以表示令牌的位置。稍后将使用此示例来说明算法。
例2.2。这个例子说明了不确定性计算。非确定性在快照算法中扮演着一个有趣的角色。
在示例2.1中,每个全局状态中正好有一个可能的事件。考虑一个与示例2.1(见图2)具有相同拓扑结构的系统,但过程p和q由图5和图6的状态转换图定义。
计算示例如图7所示。读者应该注意到,一个全局状态可能允许有多个转换。例如,事件“p发送M”和“q发送M”可能发生在初始全局状态,并且这些事件之后的下一个状态是不同的。
3 算法
3.1 算法步骤的动机
全局状态记录算法的工作原理是:每个进程记录自己的状态,一个信道所涉及的两个进程协同记录信道状态。由于没有全局时钟,我们无法确保所有进程和通道的状态都将在同一时刻被记录;但是,我们要求记录的进程和通道状态形成一个“有意义的”全局系统状态。
全局状态记录算法要叠加在底层计算上,也就是说,它必须与底层计算同时运行,但不能改变底层计算。该算法可以发送消息并要求进程执行计算;但是,记录全局状态所需的消息和计算不得干扰底层计算(干扰了底层也会导致状态变化)。
我们现在考虑一个例子来说明算法的步骤。在这个例子中,我们假设我们可以即时记录一个通道的状态;我们推迟讨论如何记录通道状态。设c是p到q的通道。该示例的目的是直观地理解要记录通道c的状态的时刻与要记录进程p和q的状态的时刻之间的关系。
例3.1、考虑单令牌保存系统。假设进程p的状态记录在全局状态in-p中。然后为p记录的状态显示p中的令牌。现在假设全局状态转移到in-c(因为p发送令牌)。假设通道c和c'以及进程q的状态记录在in-c的全局状态中,那么为通道c记录的状态用令牌表示,为通道c'和进程q记录的状态表示它们不拥有令牌。以这种方式记录的复合全局状态将显示系统中的两个令牌,一个在p中,另一个在c中。但是在一个令牌保存系统中,从初始全局状态到一个具有两个令牌的全局状态是不可到达的!出现这种不一致是因为p的状态是在p沿c发送消息之前记录的,而c的状态是在p发送消息之后记录的。设n是记录p的状态之前沿c发送的消息数,设n'是记录c的状态之前沿c'发送的消息数。我们的示例表明,如果n 现在考虑另一种情况。假设c的状态记录在全局状态in-p中,那么系统将转换到全局状态in-c,并且c′、p和q的状态记录在全局状态in-c中。记录的全局状态显示系统中没有令牌。该示例表明,如果c的状态在p沿c发送消息之前被记录,而p的状态在p沿c发送消息之后被记录,即,如果n>n’,则记录的全局状态可能不一致。 我们从这些例子中了解到(通常)一致的全局状态需要 (1)n=n’ 设m'是在记录c的状态之前沿c接收的消息数。我们让读者来扩展示例,以表明一致性需要。 (2)m=m’ 在每个状态下,一个通道接收的消息数不能超过该通道发送的消息数,即, (3)n'>=m’ 从上面的方程式中, (4)n>=m 记录的通道c的状态必须是在记录发送方状态之前沿通道发送的消息序列,不包括在记录接收方状态之前沿通道接收的消息序列,即,如果n'=m',则c的记录状态必须是空序列,如果n'>m',c的记录状态必须是(m'+1)*st,……,n’th 由p沿c发送的第n条消息。这一事实和等式。(1)—(4)提出了一种简单的算法,通过该算法q可以记录c通道的状态。进程p在沿c发送第n条消息之后(以及沿c发送进一步消息之前)发送一条特殊消息,称为标记。标记对基础计算没有影响。c的状态是q记录自己的状态之后,q沿着c接收到标记之前,q接收到的消息序列。为了确保等式(4),q必须记录其状态,如果它还没有这样做的话,在沿着c接收到一个标记之后,在q沿着c接收到更多消息之前。 我们的例子给出了以下全局状态检测算法的概要。 3.2 全局状态检测算法概述 进程p的标记发送规则。对于每个通道c,事件在p上,并指向远离p。 在p记录其状态之后,在p沿c发送更多消息之前,p沿c发送一个标记。 进程q的标记接收规则,在沿通道C接收标记时: if q没有记录它的状态,then begin q记录其状态; q将c记录为空序列 end else q将c的状态记录为在q的状态被记录之后、q沿c接收到标记之前沿c接收的消息序列。 3.3 算法的终止 标记接收和发送规则保证,如果沿着每个通道接收到标记,则每个进程将记录其状态和所有传入通道的状态。为了确保全局状态记录算法在有限时间内终止,每个进程必须确保 (L1)事件输入信道中没有永久标记,并且 (L2)它在算法启动后的有限时间内记录其状态。 该算法可以由一个或多个进程启动,每个进程自动记录其状态,而不接收其他进程的标记;我们推迟讨论什么可能导致进程自动记录其状态。如果进程p记录其状态,并且有一个从p到进程4的通道,那么q将在有限时间内记录其状态,因为p将沿着该通道发送一个标记,而q将在有限时间内接收该标记(L1)。因此,如果p记录其状态,并且有一条从p到进程q的路径(在表示系统的图中),那么q将在有限时间内记录其状态,因为通过归纳,路径上的每个进程都将在有限时间内记录其状态。如果对于每个进程q:q自发地记录其状态,或者存在一条从进程p自发地记录其状态到q的路径,则可以确保在有限时间内终止。 特别地,如果图是强连通的,并且至少有一个进程自发地记录它的状态,那么所有进程都将在有限时间内记录它们的状态(前提是L1得到了保证)。 到目前为止描述的算法允许每个进程记录其状态和传入通道的状态。必须收集和组合记录的进程和通道状态,以形成记录的全局状态。我们将不描述收集记录信息的算法,因为其他地方已经描述了这种算法[4,10]。在拓扑结构强连接的系统中收集信息的一个简单算法是,每个进程沿所有传出通道发送它记录的信息,而对于第一次接收信息的每个进程,则沿所有传出通道复制和传播它。所有记录的信息将在有限的时间内到达所有进程,允许所有进程确定记录的全局状态。 4 记录的全局状态的属性 为了直观地理解算法所记录的全局状态的性质,我们将学习示例2.2。假设p的状态记录在全局状态S0(图7)中,因此为p记录的状态是A。在记录其状态之后,p沿着信道c发送一个标记。现在假设系统进入全局状态S1,然后进入S2,然后进入S3,此时标记仍在传输中,当系统处于全局状态S3时,q接收到标记。在接收到标记时,q记录其状态,即状态D,并将c的状态记录为空序列。在记录其状态之后,q沿着信道c'发送一个标记。在接收到标记时,p将c'的状态记录为由单个消息M'组成的序列。记录的全局状态S*如图8所示。记录算法在全局状态s0中启动,在全局状态s3中终止。 注意,由算法记录的全局状态S*与计算中出现的任何全局状态S1、S1、S2、S3都不相同。如果记录的全局状态从未发生,算法有什么用?我们现在回答这个问题。 让seq=(,0⩽)是分布式计算,让成为事件之前的系统全局状态,0⩽,参见seq。设算法初始状态为,设全局终止状态为,0⩽⩽;换句话说,如果>0,算法在−1后且在前初始化完成,如果>0,算法在−1后且在前终止。我们在示例2.2中观察到,记录的全局状态S*可能不同于所有全局状态,⩽< 我们将展示: (1) S*可从访问,并且 (2) 可从S*访问 具体地说,我们将证明存在一个计算seq’,其中 (1)seq'是seq的排列,使得,S*和在seq’ (2)=∗或者在S*之前,并且 (3)=∗或者∗在seq’中早于之前。 定理1 存在一个计算序列seq’ =(ei’,0<=i)满足 (1)对于所有的i,当<或者⩾:′= 时 (2)子序列′,⩽<是(,≤<)子序列的排列 (3) 对于所有的i,满足条件⩽或者≥,’= (4)存在一些数k,≤≤,得到∗=′ 证明:当且仅当ci在进程p中且p在seq中的ei之后记录其状态时,seq中的事件ei称为预记录事件。当且仅当seq中的事件ei不是预记录事件时,即如果ei在进程p中,并且p在seq中的ei之前记录其状态,则seq中的事件ei称为后记录事件。 在序列seq中,满足i 对于一些j满足条件<<的事件,可能存在后继事件−1在前驱事件之前发生,这种情况只会存在于−1和在不同的进程中时发生。(因为如果−1和在同一个进程中,并且−1是一个后继事件,同理可得) 我们将通过排列seq导出一个计算seq',其中所有预记录事件发生在seq'中所有后记录事件之前。我们将证明S*是在所有预记录事件之后和所有后记录事件之前的seq'中的全局状态。 假设在seq中的预记录事件ej之前有一个后记录事件ej-1。我们将证明通过交换ej-1和ej得到的序列也必须是一个计算。事件ej-1和ej必须在不同的进程上。设p为ej-1发生的过程,q为ej发生的过程。在ej-1上发送的消息不能在ej-1上接收,因为(1)如果在事件ej-1发生时消息沿着通道c发送,那么标记必须在ej-1之前沿着通道c发送,因为ej-1是一个记录后事件,并且(2)如果在ej发生时消息沿着通道c接收,那么在ej发生之前,标记必须已经沿着c被接收(因为信道是先进先出的), 在这种情况下(根据标记接收规则),ej也是一个postrecording事件。 事件ej-1的发生不会改变进程q的状态,因为ej-1处于不同的进程p中。如果ej是一个事件,其中q沿着通道c接收消息M,那么M必须是事件ej-1之前c的头部的消息,因为在ej-1发送的消息不能在ej接收。因此,事件ej可以在全局状态Sj-1中发生。 过程p的状态不会因ej的出现而改变。因此,ej-1可能发生在ej之后。因此事件序列e1……,ej-2,ej,ej-1是一个计算。从上一段中的参数可以看出,计算后的全局状态e1……,ej与计算后的全局状态相同e1……,ej-2,ej,ej-1。 设seq*是seq的一个置换,它与seq相同,只是ej和ej-1互换。那么seq*也必须是一个计算。设¯为seq*中第i个事件前的全局状态。从上一段的论点来看, 对于所有的i,当≠时,¯= 通过反复交换紧跟在预录制事件之后的录制后事件,存在seq的一直置换seq’,满足 (1)所有预录制事件先于所有后录制事件 (2)seq'是一个计算 (3)对于所有的i,满足<或者≥时,′=, (4)对于所有的i,满足≤或者≥时,′= 现在我们将展示seq'中所有预记录事件之后和所有后记录事件之前的全局状态是S。要做到这一点,我们需要证明这一点 (1)S*中每个进程p的状态与由p上预先记录的事件序列组成的进程计算后的状态相同,并且 (2)S*中每个信道c的状态是(与c上预记录的发送相对应的消息序列)-(与c上预记录的接收相对应的消息序列) 第一部分的证明是微不足道的。现在我们证明第(2)部分。设c是从进程p到进程q的通道。在S*中记录的信道c的状态是在q记录其状态之后,q在c上接收到标记之前,q在c上接收到的消息序列。在p沿c发送标记之前p沿c发送的消息序列是与c上预先记录的发送相对应的序列。下面是第(2)部分。 例4.1。本例的目的是说明计算序列'是如何从计算序列中导出的。考虑示例2.2。图7的计算中显示的事件序列是: e0:p发送M并将状态更改为B(记录后事件) e1:q发送M'并将状态更改为D(预记录事件) e2;p接收M'并将状态更改为A(后记录事件) 由于e0是一个后录制事件,紧跟在e1之前,因此我们交换它们,以获得置换序列seq’: e0’:q发送M'并将状态更改为D(预记录事件) e1’:p发送M并将状态更改为B(记录后事件) e2’:p接收M'并将状态更改为A(后记录事件) 在seq'中,所有预录制事件先于所有后录制事件。我们把它留给读者来显示e'0之后的全局状态是记录的全局状态。 5 稳定性检测 我们现在解决第1节中描述的稳定性检测问题。我们研究稳定性检测问题是因为它是许多实际问题的范例,例如分布式死锁检测。稳定性检测算法定义如下: 输入:稳定性属性y 输出:有如下属性的布尔值 (y(Si)->一定的)并且(一定的->()) 其中S和分别是算法启动和终止时系统的全局状态。(符号->表示逻辑含义。) 算法的输入是函数y的定义。在算法的执行期间,一些全局状态S的值y(S)可以由系统中的过程通过将外部定义的函数y应用于全局状态S来确定。算法的输出是一个布尔值确定,我们的意思是(1)某个特别指定的进程(比如p)进入并保持在某个特殊状态,以表示输出的确定=真,以及(2) p进入并保持某种特殊状态,以表示definite=false的输出。 Definite=true表示在算法终止时稳定属性成立。但是,definite=false意味着在启动算法时稳定属性不成立。我们强调,definite=true在算法结束时为我们提供有关系统状态的信息,而definite=false在算法启动时为我们提供有关系统状态的信息。特别是,我们不能从definite=faLse推断出在算法终止时,稳定性不成立。 稳定性检测问题的解决方案是: begin 记录全局状态S* definite:=y(S*) end 稳定性检测算法的正确性如下: (1)S*可从访问, (2)、 可从S*(定理1)到达,并且 (3)y(S)->y(S’)表示从S(稳定属性的定义)可到达的所有S’。 一个每天都在超越旧我的IT从业者,保持终生学习,终生成长。目前专注架构与数据中台。 欢迎关注公众号,一起遇见未知的自己。