分布式快照
Distributed Snapshots: Determining Global States of Distributed Systems
这篇论文是Chandy和Lamport大神的作品,理论性非常强,没有具体应用的例子,读起来非常吃力。
我的能力有限,同时阅读这篇文章为了找到一种分布式系统做全局快照的方法,所以只对前面三章做了攻读。
如果想要找到一种算法实现分布式死锁检测、分布式数据库的全局快照,或者系统处于某个状态,都可以通过这个算法来解决。
论文链接: http://lamport.azurewebsites.net/pubs/chandy.pdf
1. 介绍
这篇论文想要介绍一个进程在分布式系统中可以确定一个全局状态,并且不中断计算(不影响业务)。分布式系统中的进程通过收发消息来通讯。
一个进程记录它自己的状态和收发的消息,也可以什么都不记录。
为了确定一个全局系统的状态,进程p必须召集其它合作进程记录它们的本地状态,然后发送记录的本地状态给p。
所有进程不可能在同一时刻记录本地状态,除非它们可以有一个共同的时钟。
这里要假设进程没有共同的时钟,也不共享内存。
其实这是大部分分布式系统的实现,不过即使在同一台机器上的进程,也不能保证访问同一个时钟的时间点是同一个时刻,没办法让进程在某个时刻静止。
所有机器在同时在某个时间点做某个事情也是不现实的,因为这个“时间”在每台机器上都是不同的,都有“误差”。
这个全局状态检测算法会与正在进行的计算并行执行(不影响业务)。
这个状态检测算法就像几个摄影师拍摄一个全景的动态的场景。比如天空中有一批迁徙的鸟,一个很大的景观,没办法用一个相机拍出来。鸟就像系统中一直做的程序一样,不会停止。摄影师要拍摄好几张照片,然后把这些照片放一起拼成一个景观。因为存在同步的问题,几个摄影师也没办法在同一个瞬间同时拍摄。并且,摄影师拍照的时候也不能干扰这些东西,因为他们不能让鸟静止下来(不能暂停进程的计算)。
先介绍一些这些问题的特征,后面可以用全局状态检测(global-state-detection)算法来解决。
假设判定函数y作用于分布式系统D的全局状态,y(S)表示D的全局状态S是true或false。
稳定属性(stable property):如果y(S)意味着y(S’)对所有的全局状态S’从全局状态S是可达的(可以从状态S,经过某些变换,或运算,达到状态S’),那么就说判定函数y是D的一个稳定属性(stable property)。
The predicate y is said to be a stable property of D if y(S) implies y(S’) for all global states S’ of D reachable from global state S of D.
换句话说,如果y是一个稳定属性,那么在某个点D的一个运算,y是true,后面所有这个运算的点y都是true。
if y is a stable property and y is true at a point in a computation of D, then y is true at all later points in that computation.
稳定属性的例子:
- 运算终止;
- 系统死锁;
- 令牌环上的所有令牌都消失了
很多分布式系统问题都可以抽象成通用问题,通过分布式系统中的一个进程可以确定这个系统是否包含一个稳定属性(stable property)y设计一个算法。
这个算法稍后会描述。算法的基本思想是系统的全局状态S是确定的,然后计算出y(S)来看是否有稳定状态y(S)。
很多分布式算法结构化成一系列阶段,每个阶段包含一个很小的部分,做一些有用的事情(会影响系统状态的动作),后面跟着一个固定部分不会终止也没有用的系统周期(system cycles)。存在固定状态的时候说明一个阶段结束了。一个阶段类似一个循序程序中一连串的迭代,一直重复,直到后面的迭代不会再有变化,就是达到了固定状态。
必须能够检测到固定状态,这样一个阶段才能终止,后面的阶段接着初始化。一个计算的(computational)阶段的终止不等于一个计算的终止(这里太拗口,意思是当前阶段结束了,不是整个程序或系统的终止,程序还在继续)。当计算终止时,所有活动都是停止,不再发送消息,进程状态不再变化。在固定状态期间可能会有些活动,表示一个计算的(computational)阶段终止了,消息还可以收发,进程状态还可以变更,但是这些活动只是为了发送阶段结束的信号。这篇论文中考虑检测系统的稳定属性,这些活动中止只是稳定属性的一个例子。
严格地讲,如果死锁“打破”了并且计算重新初始化,“系统死锁”这样的属性不是稳定的。但是为了简便,把问题分解一下:
- 检测一个阶段的终止(还有通知所有进程这个阶段结束了);
- 发起一个新阶段。
下面是一个稳定属性:
第k个计算阶段终止了,k = 1,2,…
这篇论文中的方法可以检测到第k个阶段的终止了。
这篇论文只关注怎么检测稳定属性,不考虑怎么发起下一个阶段,因为这个与应用程序相关比较大。
这里没有解释会不会有多个“阶段”并行执行的情况,比如两个线程同时做某些事情。而不是单个线程接收某个消息,做一个运算,然后取下一个消息,再处理。
下面会描述一个模型,用来解释算法的原理。
2. 分布式系统模型
一个分布式系统包含一个有限集合的进程和有限集合的通信通道(channel)。可以使用有向图表示,顶点代表进程,边表示通信通道。
假设通道包含无限大的buffer,不会出错,消息发送按照顺序。通道中的消息可能会延迟,但不会无限延迟。
这个在实际场景中比较难抽象,有很多系统都不是按照顺序来收发消息的,还要考虑消息发送接收的异常。
通道的状态是指通道上发送的消息,不包括接收的消息。
一个双向的“通道”可以拆成两个单向通道,类似于无向边拆成两个有向边。
一个进程由一系列状态定义,一个初始状态和一系列事件。一个进程p中的事件e是这样的一个原子动作:可能会改变进程p的状态,还有最多一个与P相关的通道c的状态。通道c的状态改变可能是发送数据(通道方向背离p)也可能是接收数据(通道方向朝向p)。
与事件e相关的定义:
- 事件发生的进程p;
- 事件发生前进程p的即时状态s;
- 事件发生后进程p的即时状态s’;
- 因为事件而改变状态的通道c;
- 通过通道c发送或接收的消息M.
使用一个五元组定义事件e: <p, s, s’, M, c>。如果事件e没有改变M和c的状态,就标记为null。
分布式系统的全局状态就是一组进程和通道的状态:系统的原始状态就是每个进程的原始状态,通道的状态是空序列。
一个事件的发生可能会改变全局状态。假设一个事件e = <p, s, s’,M, c>只有在这种情况下可以出现在全局状态S:
- p的状态在全局状态S中是s;
- 如果通道c是朝向p的(p通过c接收数据),那么全局状态S下,通道c中的消息序列中,M在最前面(head),进程p收到的下一个消息就是M。
定义函数next, next(S, e)
指全局状态在S时,事件e发生后系统的即时全局状态。
只有e可以在全局状态S下发生时,next(S, e)
的值才是明确的。除了以下描述的情况next(S, e)
与S是相同的:
next(S, e)
状态下进程p的状态是s’;- 如果c是指向p的通道,那么c在
next(S, e)
下的状态就是在状态S时,将消息M从消息队列中移除; - 如果c是从p背离的通道(p向其他进程发消息的通道),c在
next(S, e)
的状态就等于在状态S中时,消息队列尾部增加消息M。
假设seq = (ei: 0 <= i <= n)是分布式系统中的一系列事件。当且只有当ei可以出现在全局状态Si(0<=i<=n)发生时,认为seq是系统的运算(computation),S0是原始状态:
图2 最简单的分布式系统
Si+1 = next(Si, ei) 0 <= i <= n
为了描述一个分布式系统,考虑最简单的系统,包含两个进程p和q,两个通道c和c’,如图2。
这个系统有一个令牌(token),在两个进程之间传递,就称这个系统为"单令牌守恒"系统。每个进程都有两个状态s0和s1,s0表示进程没有令牌,s1表示令牌就在这个进程中。进程p的初始状态是s1,q的初始状态是s0。每个进程有两个事件:
- 发送令牌时从状态s1转换为s0;
- 收到令牌从状态s0转换为s1。
图3是状态转换图。
图3 单个进程状态转换图
图4 单令牌守恒系统全局状态和转换图
系统运算(computation)的一个示例:
- 空序列;
- <p发送令牌,q接收令牌>
下面的一个序列事件就不是这个系统的一个运算(computation):
<p发送令牌,q发送令牌>
这个事件是不可能在系统中任何状态下发生的。
这4个全局状态,可以称为:
- in-p;
- in-c;
- in-q;
- in-c’。
这样可以描述令牌所在的位置。
另一个例子:描述一个不确定性的运算(computation)。
上个例子中每个全局状态有一个事件。延续上个例子的拓扑结构,不过进程p和q按照下面两个图转换。
图5 进程p和进程q的状态转换
有一个运算如下图
注意某个全局状态允许超过一个换换发生。比如"p发送M"和"q发送M’"在初始状态下可以同时发生。
3. 算法
3.1 算法步骤
全局状态记录步骤:
每个进程记录自己的状态,两个进程关联的通道记录通道状态。
不可能保证所有的进程和通道在同一个瞬间记录状态,因为没有全局时钟。
全局状态记录算法要求必须与正在进行的运算(computation)并行执行。
算法中可以发送消息要求进程做某些运算(computation),但是用来记录全局状态的消息不能干涉正在进行的运算。
举个例子,先假设通道的状态记录可以瞬时完成。假设c是p到q的通道,这个例子是为了直观的理解记录通道c的瞬间和进程pq记录状态的瞬间的关系。
示例3.1 回想一下单令牌守恒系统。假设进程p在全局状态中记录了in-p
。再假设全局状态转换到了in-c
(p发送了令牌)。假设通道c和c’,进程p都在in-c
状态下记录了状态,那么c就记录了令牌在c,通道c’和q没有令牌。这个方式记录的全局状态会有两个令牌,一个在p一个在c。
这个不一致是由p在发送消息前记录了状态,而c在p发送消息后记录导致的。
假设n是p记录状态前通过c发送的消息个数,n’是c记录状态前通过c发送的消息个数。在这个例子中,记录的全局状态是不一致的,就是因为n<n’。
考虑另一个场景。c在全局状态in-p
时记录状态,系统切换到状态in-c
,c’、p和q在in-c
时记录状态。这次记录的全局状态显示系统中没有令牌。这个例子说明的是系统的不一致是因为p发送消息之前c记录了状态,p在消息发送之后记录了状态导致的,就是n > n’。
从这些例子中可以看出一致的全局状态要求:
n = n'
设m为q状态记录之前通过c接收的消息个数,m’为c的状态记录之前c接收的消息个数。系统一致性要求:
m = m'
任何一个状态,一个通道接收的消息个数不能超过通过这个通道发送的消息个数:
n' >= m'
==>
n >= m
通道c记录的状态是通过通道c发送消息的发送者记录状态前的消息序列,除去通过通道c接收消息的接收者记录状态前接收的消息。就是说,如果n’=m’,c的状态就是一个空消息队列。如果n’>m’,c的状态就是p通过c发送的消息,第(m’+1)、(m’+2) … n’个消息组成的消息队列。
上面几个公式提供一个简单的方法记录状态。进程p在发送第n个消息后,紧接着发送一个特殊消息marker,进程q收到这个消息后记录自己的状态。消息marker对整个系统的运算没有影响。这样c就不用记录状态。
理论看起来很容易,但是实际操作的时候不太方便。比如pq之间有多个通道,那每个通道上都要发送marker?q收到marker消息后怎么处理?
3.2 Global-State-Detection Algorithm Outline
**Marker-Sending Rule: **
背离进程p的通道c:p记录自己的状态后就通过c发送marker消息,在这之前不会再发送新的消息
Marker-Receiving Rule:
收到marker消息时:
if q还没有记录状态:
begin
q记录状态
q记录通道c的状态是空队列
end
else:
q记录c的状态是q记录状态后接收*marker*消息前的消息队列
q收到marker消息后就要记录其它进程发向发的消息状态,以便在收到其它进程的marker消息后,记录通道c的状态。
3.3 Termination of the Algorithm
marker的收发规则保证了如果哪个通道接收到了marker,对应的进程就会记录状态,还有接收消息的通道状态。
为了确保全局状态记录算法在有限的时间内结束,每个进程都要保证:
- marker不会永久停留在输入通道中;
- 在有限的时间内记录状态。
这个算法可以由一个或者多个进程发起,每个进程自己记录状态,不需要等待其它进程的marker消息。
如果进程p记录了状态,p到q之间有一个通道,那么q也会在有限的时间内完成状态记录,因为p会向q发送marker消息,并且q会在有限的时间内接收到这个消息。
如果系统拓扑是强连接的(strong connected),如果有一个进程会自发的记录状态,那么所有进程都会在有限的时间内完成状态记录。
记录的进程状态和通道状态要汇总起来成为一个完整的全局状态。
下面的两章虽然看了,理解不透,更总结不出来。
4. PROPERTIES OF THE RECORDED GLOBAL STATE
略
5. STABILITY DETECTION
一些状态检测,比如死锁检测,在这里描述,但是需要先理解第4章。
来源:https://blog.csdn.net/hnwyllmm/article/details/99302874