不定期更新
中文书翻译很烂。。。各种下标错误等。。
本读书笔记首发于:https://github.com/rsy56640/Distributed_System_Learning/tree/master/Distributed Computing Principles%2C Algorithms%2C and Systems - Kshemkalyani
分布式系统:处理器、存储器、通信网络
图左是UMA(均匀存储器访问体系结构),右NUMA
多计算机并行系统
阵列处理器
每一个共享的位置可以建模为一个隔离的进程。
处理器间通过共享内存进行通信;计算机间使用消息传递进行通信。
消息发送原语Send()
, 消息接受原语Receive()
。
Send()
发送方式:缓冲(拷贝到内核缓冲区,再到网络)与 非缓冲(直接拷贝到网络)。
Receive()
通常采用缓冲方式。
Send()
和Receive()
两端都实现了握手,则原语是同步的
Receive()
原语被调用并且接受操作完成,Send()
原语才算完成Receive()
原语被认为完成Send()
原语被称为异步的Receive()
原语无意义怎样理解阻塞非阻塞与同步异步的区别?- 知乎
同步屏障:保证在所有处理器完成前面所分配的指令之前都不会去执行下一步的代码。
同步系统实际上是一个特殊的异步系统——所有的通信都在其发起的轮次内完成。
在无错误的系统中,异步/同步 共享内存/消息传递 这4类程序可以互相仿真,即等价。
但是在有错误的系统中,情况并不是这样;一个同步系统相比于异步系统具有更高的可计算性。
在分布式系统中,通信消息可能在传递过程中乱序、丢失、受到篡改或者重复传递。
分布式系统可以以一个有向图的方式建模,其中结点表示处理器而边则表示单向通信信道。
分布式程序有一组 n n n 个异步进程 p 1 , p 2 , . . . , p n p_1,\ p_2,\ ...,\ p_n p1, p2, ..., pn 组成,令 C i j C_{ij} Cij 表示从进程 p i p_i pi 到进程 p j p_j pj 的通信信道, m i j m_{ij} mij 表示由 p i p_i pi 发往 p j p_j pj 的消息。
一个进程的运行可以描述为三类原子操作:内部事件、消息发送事件、消息接受事件。令 e i x e_i^x eix 表示进程 p i p_i pi 上的第 x x x 个事件。对一个消息 m m m,令 s e n d ( m ) send(m) send(m) 和 r e c ( m ) rec(m) rec(m) 分别表示其发送和接收的消息。一个内部事件改变其所处的进程的状态,一个发送或接受事件改变双方的状态。
进程中的事件以出现顺序进行排序: e i 1 , e i 2 , . . . , e i x , . . . e_i^1,\ e_i^2,\ ...,\ e_i^x,\ ... ei1, ei2, ..., eix, ...,该序列记为 H i \mathcal{H}_i Hi:
H i = ( h i , → i ) \mathcal{H}_i = (h_i, \to_i) Hi=(hi,→i)
其中 h i h_i hi 是由 p i p_i pi 产生的事件集合;二元关系 → i \to_i →i 则定义了这些事件间的序。
关系 → m s g \to_{msg} →msg 表示因消息交换所导致的因果依赖关系:
s e n d ( m ) → m s g r e c ( m ) send(m) \to_{msg} rec(m) send(m)→msgrec(m)
令 H = ⋃ h i H = \bigcup h_i H=⋃hi 表示在一次分布式计算过程中执行的时间集合。我们在集合 H H H 上定义一个关系 → \to →,表示事件间的因果依赖关系。
KaTeX parse error: No such environment: equation at position 93: …htarrow \begin{̲e̲q̲u̲a̲t̲i̲o̲n̲}̲ \left\{ …
关系 → \to → 是 Lamport 的 “happerns before” 关系。如果 e i → e j e_i \to e_j ei→ej,则事件 e j e_j ej 直接或间接依赖于 e i e_i ei,存在着一条起始于 e i e_i ei,终止于 e j e_j ej 的路径。
e i ↛ e j e_i \nrightarrow e_j ei↛ej 表示事件 e j e_j ej 不直接或间接依赖于 e i e_i ei,即事件 e i e_i ei 不会对 e j e_j ej 产生因果影响。
并发:如果 e i ↛ e j e_i \nrightarrow e_j ei↛ej 且 e j ↛ e i e_j \nrightarrow e_i ej↛ei,则事件 e i e_i ei 和 e j e_j ej 被称为是并发的,其关系被记为 e i ∣ ∣ e j e_i || e_j ei∣∣ej。
两个事件是逻辑并发的,当且仅当它们之间无因果影响。与此相对,物理并发的含义是不同事件在物理时间的同一时刻发生。不论一组逻辑并发的事件是否在物理时间上同时发生,也无论它们在物理时间上实际发生的顺序如何,都不会改变计算的结果。因此。虽然一组逻辑并发的事件可能不会在物理时间的同一时刻发生,但我们总是假定这些事件在物理时间的同一时刻发生。
因果依赖(CO):对任意两个消息 m i j m_{ij} mij 和 m k j m_{kj} mkj,假设 s e n d ( m i j ) → s e n d ( m k j ) send(m_{ij}) \to send(m_{kj}) send(mij)→send(mkj),则 r e c ( m i j ) → r e c ( m k j ) rec(m_{ij}) \to rec(m_{kj}) rec(mij)→rec(mkj)。确保那些发往同一目标的因果依赖的消息,以符合它们之间因果依赖关系的顺序进行发送。
通信网络模型:CO ⊂ \subset ⊂ FIFO ⊂ \subset ⊂ 非FIFO(随机)
因果依赖模型提供了一个内在的同步机制。
分布式系统的全局状态是所有 处理器状态 和 信道状态 的集合。
处理器状态:令 L S i x LS_i^x LSix 表示处理器 p i p_i pi 在事件 e i x e_i^x eix 发生之后,以及事件 e i x + 1 e_i^{x+1} eix+1 发生之前的状态。特别地, L S i 0 LS_i^0 LSi0 表示 p i p_i pi 的初始状态。
若 y ≤ x y \le x y≤x,记为 e i y ≤ L S i x e_i^y \le LS_i^x eiy≤LSix,表示 e i y e_i^y eiy 在处理器状态之内。
信道状态:记 S C i j x , y SC_{ij}^{x, y} SCijx,y 表示信道 C i j C_{ij} Cij 的状态:
S C i j x , y = { m i j ∣ s e n d ( m i j ) ≤ L S i x ∧ r e c ( m i j ) ≰ L S j y } SC_{ij}^{x, y} = \{ m_{ij}\ |\ send(m_{ij}) \le LS_i^x\ \land\ rec(m_{ij}) \nleq LS_j^y \} SCijx,y={mij ∣ send(mij)≤LSix ∧ rec(mij)≰LSjy}
通俗地讲,信道是消息的集合。消息 m i j m_{ij} mij, p i p_i pi 在 x x x 之内 send; p j p_j pj 在 y y y 之后 rec。即 S C i j x , y = { m i j ∣ m i j 从 左 穿 过 线 段 e i x + 1 e j y } SC_{ij}^{x, y} = \{ m_{ij}\ |\ m_{ij}\ 从左穿过线段e_i^{x+1} e_j^y \} SCijx,y={mij ∣ mij 从左穿过线段eix+1ejy}
全局状态:$GS = {\ \cup_iLS_i^{x_i},\ \cup_{j, k}SC_{jk}^{y_j, z_k} } $
一致性全局状态:称 G S GS GS 是一致的,如果满足
∀ m i j : s e n d ( m i j ) ≰ L S i x i ⇒ m i j ∉ S C i j x i , y j ∧ r e c ( m i j ) ≰ L S j y j \forall m_{ij}:\ send(m_{ij}) \nleq LS_i^{x_i} \Rightarrow m_{ij} \notin SC_{ij}^{x_i, y_j}\ \land\ rec(m_{ij}) \nleq LS_j^{y_j} ∀mij: send(mij)≰LSixi⇒mij∈/SCijxi,yj ∧ rec(mij)≰LSjyj
这里中文书上写错了,第一个符号写成 ≤ \le ≤
通俗地讲: p i p_i pi 在 x i x_i xi 之后的 send,那么必须有 p j p_j pj 在 y j y_j yj 之后 rec。即
∀ m i j , m i j 不 能 从 右 穿 过 线 段 e i x e j y + 1 \forall m_{ij}, m_{ij}\ 不能从右穿过线段e_i^x e_j^{y+1} ∀mij,mij 不能从右穿过线段eixejy+1
非中转全局状态:所有信道为空
强一致的全局状态:一致且非中转的
一条分割线 C C C 将时空图分割为2各部分, P A S T ( C ) PAST(C) PAST(C) 表示分割线左边的所有事件的集合, F U T U R E ( C ) FUTURE(C) FUTURE(C) 表示分割线右边的所有事件的集合。
C 1 C_1 C1 是非一致性分割线; C 2 C_2 C2 是一致性分割线
一致性全局状态对应一条分割线,其中每个 P A S T PAST PAST 集合中的 rec 事件都是在 P A S T PAST PAST 集中 send。
一条分割线是非一致性的,如果某个消息跨越了分割线从 F U T U R E FUTURE FUTURE 集传到 P A S T PAST PAST 集。
令 P a s t ( e j ) Past(e_j) Past(ej) 表示在计算 ( H , → ) (H, \to) (H,→) 中 e j e_j ej 的过去事件。则,
P a s t ( e j ) = { e i ∣ ∀ e i ∈ H , e i → e j } Past(e_j) = \{ e_i\ |\ \forall e_i \in H, e_i \to e_j \} Past(ej)={ei ∣ ∀ei∈H,ei→ej}
记 P a s t i ( e j ) Past_i(e_j) Pasti(ej) 是进程 p i p_i pi 上所有属于 P a s t ( e j ) Past(e_j) Past(ej) 事件的集合。注意到 P a s t i ( e j ) Past_i(e_j) Pasti(ej) 是全序的,最大元素记为 m a x ( P a s t i ( e j ) ) max(Past_i(e_j)) max(Pasti(ej))。注意到 m a x ( P a s t i ( e j ) ) max(Past_i(e_j)) max(Pasti(ej)) 总是一个 send 事件。
令 M a x _ P a s t ( e j ) = ⋃ i { m a x ( P a s t i ( e j ) ) } Max\_Past(e_j) = \bigcup_i \{ max(Past_i(e_j)) \} Max_Past(ej)=⋃i{max(Pasti(ej))},其包含每个进程上影响 e j e_j ej 的最新事件,它被称为事件 e j e_j ej 的过去锥面。
用反证法易知: M a x _ P a s t ( e j ) Max\_Past(e_j) Max_Past(ej) 是一条一致性分割线。
类似地,事件 e j e_j ej 的未来事件记作 F u t u r e ( e j ) Future(e_j) Future(ej),它包含所有受到 e j e_j ej 影响的事件 e i e_i ei,定义为
F u t u r e ( e j ) = { e i ∣ ∀ e i ∈ H , e j → e i } Future(e_j) = \{ e_i\ |\ \forall e_i \in H, e_j \to e_i \} Future(ej)={ei ∣ ∀ei∈H,ej→ei}
定义 F u t u r e i ( e j ) Future_i(e_j) Futurei(ej) 为进程 p i p_i pi 上所有属于 F u t u r e ( e j ) Future(e_j) Future(ej) 事件的集合。 m i n ( F u t u r e i ( e j ) ) min(Future_i(e_j)) min(Futurei(ej)) 作为 p i p_i pi 上受 e j e_j ej 影响的第一个事件。注意到 m i n ( F u t u r e i ( e j ) ) min(Future_i(e_j)) min(Futurei(ej)) 总是 rec 事件。 M i n _ F u t u r e ( e j ) Min\_Future(e_j) Min_Future(ej) 定义为 ⋃ i { m i n ( F u t u r e i ( e j ) ) } \bigcup_i \{ min(Future_i(e_j)) \} ⋃i{min(Futurei(ej))},它包含了每个进程上受到事件 e j e_j ej 影响的第一个事件的集合,被称为事件 e j e_j ej 的未来锥面。易证它是一条一致性分割线。
在一个计算 H H H 中,一个事件 e e e 与 e j e_j ej 是并发的,当且仅当
e ∈ H − P a s t ( e j ) − F u t u r e ( e j ) e \in H - Past(e_j) - Future(e_j) e∈H−Past(ej)−Future(ej)
进程间的消息交换显示出进程间的信息流向并且建立了进程间的因果依赖关系。进程间的优先因果关系由 Lamport 的 hanppens-before 关系确定。
逻辑时间系统由一个时间域 T T T 和一个逻辑时钟 C C C 组成。 T T T 的元素形成关系 < < < (happens-before) 上的偏序集合。逻辑时钟 C C C 是一个函数,把事件 e e e 映射到时间域 T T T 中的一个元素,表示为 C ( e ) C(e) C(e) 且成为 e e e 的时间戳,定义为
C : H ↦ T C:H\mapsto T C:H↦T
使得对于事件 e i e_i ei 和 e j e_j ej,有 e i → e j ⇒ C ( e i ) < C ( e j ) e_i \to e_j \Rightarrow C(e_i) < C(e_j) ei→ej⇒C(ei)<C(ej) ,这种单调性称为时钟一致性条件。
若 e i → e j ⇔ C ( e i ) < C ( e j ) e_i \to e_j \Leftrightarrow C(e_i) < C(e_j) ei→ej⇔C(ei)<C(ej),则这个系统称为强一致的。
每个进程 p i p_i pi 维护数据结构:
更新数据结构的协议:
时间域 T = N T = N T=N,进程 p i p_i pi 的本地逻辑时钟和本地全局时钟用同一个整数 C i C_i Ci 表示。
(1) R1 规则:
在执行一个事件之前,进程 p i p_i pi 执行如下动作:
C i : = C i + d , d > 0 C_i := C_i + d, \quad d > 0 Ci:=Ci+d,d>0
通常 d d d 会有不同的值,典型的值保持为 1.
(2) R2 规则:
每个消息附加有它的发送方在发送时的时钟值,当进程 p i p_i pi 接收到一个带有时间戳 C m s g C_{msg} Cmsg 的消息时,它执行如下动作:
单调性蕴含了一致性。
注意到 C ( e i ) = C ( e j ) ⇒ e i ∣ ∣ e j C(e_i) = C(e_j) \Rightarrow e_i || e_j C(ei)=C(ej)⇒ei∣∣ej
定义时间戳为 ( t , i ) (t, i) (t,i),其中 t t t 是本地逻辑时钟, i i i 是进程号。
全序关系 ≺ \prec ≺ 定义如下:
KaTeX parse error: Expected 'EOF', got '\or' at position 36: …arrow (h < k)\ \̲o̲r̲\ ((h = k)\ \la…
注意到 x ≺ y ⇒ x → y ∨ x ∣ ∣ y x\prec y \Rightarrow x\to y \ \lor \ x||y x≺y⇒x→y ∨ x∣∣y,这没啥用。
如果 d d d 总是1,则标量时间有如下性质:对于一个事件 e e e 和时间戳 h h h,在这之前一定顺序产生了 h − 1 h-1 h−1 个事件,不管是哪些进程产生的。
C ( e i ) < C ( e j ) ⇏ e i → e j C(e_i) < C(e_j) \nRightarrow e_i \to e_j C(ei)<C(ej)⇏ei→ej
本地逻辑时钟和本地全局时钟被压缩成一个,导致了不同进程的事件之间因果依赖关系的缺失。
时间域 T = N n T = N^n T=Nn,每个进程维护一个向量 v t i [ 1.. n ] vt_i[1..n] vti[1..n],其中 v t i [ j ] vt_i[j] vti[j] 表示进程 p i p_i pi 的有关 p j p_j pj 本地时间的最近信息。用整个向量 v t i vt_i vti 代表 p i p_i pi 所见的全局时间,并且用于给事件打上时间戳。
(1) R1 规则:
在执行一个事件之前,进程 p i p_i pi 更新其本地逻辑时钟如下:
v t i [ i ] : = v t i [ i ] + d vt_i[i] := vt_i[i] + d vti[i]:=vti[i]+d
(2) R2 规则:
把每个消息 m m m 加入到发送方进程在发送时的向量时钟 v t vt vt。一旦接收到这样一个消息 ( m , v t ) (m, vt) (m,vt),进程 p i p_i pi 执行如下一系列动作:
v t i [ k ] : = m a x ( v t i [ k ] , v t [ k ] ) , k ∈ [ 1 , n ] vt_i[k] := max(vt_i[k], vt[k]),\quad k\in[1, n] vti[k]:=max(vti[k],vt[k]),k∈[1,n]
( H , → ) (H, \to) (H,→) 同构于 ( T , < ) (T, <) (T,<)
e i x → e j y ⇔ v x < v y e i x ∣ ∣ e j y ⇔ v x ∣ ∣ v y e i x → e j y ⇔ v x [ i ] ≤ v y [ i ] e i x ∣ ∣ e j y ⇔ ( v x [ i ] > v y [ i ] ) ∧ ( v x [ j ] < v y [ j ] ) \begin{aligned} e_i^x \to e_j^y & \Leftrightarrow vx < vy \\ e_i^x\ ||\ e_j^y & \Leftrightarrow vx \ ||\ vy \\ \\ e_i^x \to e_j^y & \Leftrightarrow vx[i]\le vy[i] \\ e_i^x\ ||\ e_j^y & \Leftrightarrow (vx[i] > vy[i]) \ \land\ (vx[j] < vy[j]) \\ \end{aligned} eix→ejyeix ∣∣ ejyeix→ejyeix ∣∣ ejy⇔vx<vy⇔vx ∣∣ vy⇔vx[i]≤vy[i]⇔(vx[i]>vy[i]) ∧ (vx[j]<vy[j])
时间戳长度过大,需要优化
当 p i p_i pi 向 p j p_j pj 发送消息时,将现在的向量与上一次发送给 p j p_j pj 的向量做差,为 0 0 0 的忽略,其余的标记位置和当前值打包发送。
即 p i p_i pi 向 p j p_j pj 发送 { ( i , v ) } \{(i,v)\} {(i,v)}, p j p_j pj 更新向量如下:
v t j ( i k ) = m a x ( v t i [ k ] , v k ) vt_j(i_k) = max(vt_i[k], v_k) vtj(ik)=max(vti[k],vk)
每个进程需要记录上一次发给其他所有进程的时间戳向量。这个技术的主要价值在于将进程的存储空间开销降低到 O ( n ) O(n) O(n),方式如下:
进程 p i p_i pi 维护两个向量:
显然有 L U i [ i ] = v t i [ i ] LU_i[i] = vt_i[i] LUi[i]=vti[i],并且只有 rec 导致更新 v t i [ j ] vt_i[j] vti[j] 时 L U i [ j ] LU_i[j] LUi[j] 才需要更新;只有 p i p_i pi send p j pj pj 时 L S i [ j ] LS_i[j] LSi[j] 需要更新。
因此,从上次 p i p_i pi 到 p j p_j pj 通信以来,向量时钟中只有 v t i [ k ] vt_i[k] vti[k] 改变,其中 k k k 满足 L S i [ j ] < L U i [ k ] LS_i[j] < LU_i[k] LSi[j]<LUi[k]。于是 p i p_i pi 发送 p j p_j pj 的集合为:
{ ( x , v t i [ x ] ) ∣ L S i [ j ] < L U i [ x ] } \{\ (x, vt_i[x])\ |\ LS_i[j] < LU_i[x]\ \} { (x,vti[x]) ∣ LSi[j]<LUi[x] }
通俗地讲:记录上次通信以来本地时钟的改变,因为 r e c rec rec 操作总是会更新本地时钟。
中文版怎么回事???各种下标错误,我才看到P51,都有好几处错误了。还有不少翻译很迷。。
通俗地讲:只计算直接依赖,也就是通过消息同步的事件,或者本地事件。
每个进程维护一个依赖向量 D i [ 1.. n ] = { 0 } D_i[1..n] = \{0\} Di[1..n]={0},按如下方式更新:
依赖向量 D i D_i Di 仅仅反应直接依赖。(图中 p 2 p_2 p2 直接依赖于 p 3 p_3 p3, p 3 p_3 p3 直接依赖于 p 4 p_4 p4,但 p 2 p_2 p2 不知道自己间接依赖于 p 4 p_4 p4)
方法:间接依赖可以通过脱机的递归跟踪事件的直接依赖向量来获得。
这个方法适用于不要求频繁计算传递依赖的应用,如因果断点和异步检查点的恢复等离线计算。
离线计算算法:递归地更新过去锥面。
缺点:如果事件频繁的发生,该技术需要记录大量事件的历史。
T = N n × n T = N_{n \times n} T=Nn×n,进程 p i p_i pi 维护一个矩阵 m t i [ 1.. n ] [ 1.. n ] mt_i[1..n][1..n] mti[1..n][1..n]
m t i [ i ] [ i ] mt_i[i][i] mti[i][i] 表示 p i p_i pi 本地逻辑时钟;
m t i [ i ] [ j ] mt_i[i][j] mti[i][j] 表示 p i p_i pi 具有的有关进程 p j p_j pj 的本地逻辑时钟的最新知识;
m t i [ j ] [ k ] mt_i[j][k] mti[j][k] 表示 p i p_i pi 具有的有关进程 p j p_j pj 的知识,该知识是 p j p_j pj 具有的 p k p_k pk 本地逻辑时钟的最新知识;
通俗地讲:就是存储了所有 v t i [ 1.. n ] vt_i[1..n] vti[1..n]
(1) R1 规则:
在执行一个事件前,进程 p i p_i pi 更新本地逻辑时钟:
m t i [ i ] [ i ] : = m t i [ i ] [ i ] + d mt_i[i][i] := mt_i[i][i] + d mti[i][i]:=mti[i][i]+d
(2) R2 规则:
每个消息附带矩阵时间 m t mt mt,当 p i p_i pi 收到 p j p_j pj 的消息 ( m , m t ) (m,mt) (m,mt) 时, p i p_i pi 执行如下:
m t i [ i ] [ k ] : = m a x ( m t i [ i ] [ k ] , m t [ j ] [ k ] ) , k ∈ [ 1 , n ] m t i [ k ] [ l ] : = m a x ( m t i [ k ] [ l ] , m t [ k ] [ l ] ) , k , l ∈ [ 1 , n ] \begin{aligned} mt_i[i][k] & := max(mt_i[i][k], mt[j][k]),\quad k\in [1,n] \\ mt_i[k][l] & := max(mt_i[k][l], mt[k][l]),\quad k,l\in [1,n] \end{aligned} mti[i][k]mti[k][l]:=max(mti[i][k],mt[j][k]),k∈[1,n]:=max(mti[k][l],mt[k][l]),k,l∈[1,n]
通俗地讲:总是保证 m t i [ i ] [ . ] ≥ m t i [ j ] [ . ] , j ≠ i mt_i[i][.] \ge mt_i[j][.],\quad j \neq i mti[i][.]≥mti[j][.],j̸=i;即总是保证自己关于其他进程的知识在自己的矩阵时间里是最新的。
m t i [ i ] [ . ] mt_i[i][.] mti[i][.] 包含了向量时钟的所有性质。此外还具有如下性质:
$min_k(mt_i[k][l] \ge t) \Rightarrow\ $进程 p i p_i pi 知道 “每个进程知道 p l p_l pl 本地时间的进展,直到 t t t”,这意味着进程 p i p_i pi 知道 “所有其他进程知道 p l p_l pl 不会发送本地时间 ≤ t \le t ≤t 的消息”,据此可以做一些优化。
虚拟时间是分布式计算中一个全局的、一维的临时坐标系统,以测量计算的进展和定义同步。
消息:发送方,虚拟发送时间,接收方,虚拟接收时间。
规则 1:每个消息的虚拟发送时间 < < < 该消息的虚拟接收时间
规则 2:进程中事件的虚拟时间是递增的
感觉像是标量时间到向量时间的一种过渡,但是表达力不及向量时间。
在虚拟时间中,时钟是按照乐观方式前进的,一旦检测到违反的情况就会采取纠正行动。
消息的虚拟接收时间被认为是其时间戳。
时间变形机制的组成:
进程只有本地时钟,没有全局时钟。
一个消息的虚拟发送时间为发送方的时钟。
虚拟时间的语义要求输入消息严格按时间戳次序被每个进程接收。实现该要求的唯一方法是:在收到一个迟到的消息时,接收者回滚到一个较早的一个虚拟时间,撤销其间产生的一切附带影响,然后通过合适的顺序顺序执行这个迟到的消息并再次向前执行。
分布式系统中的回滚因这样的原因而复杂:要回滚的进程可能已经给其他进程发送了很多消息,而其他进程有可能因此产生其他事件,这就导致了多层次的附带影响。
进程运行时的组成如下:
反消息:与消息内容相同,符号相反。传递消息时,该消息的一个拷贝进入接收方的输入队列,一个负拷贝留在发送方的输出队列以便发送方回滚。
当一个消息与其反消息出现在同一个队列中时,它们立刻无效。
回滚的触发:如果消息时间戳 < < < 接收方本地时间,则接收方必须进行回滚。
回滚机制:
反消息的特点:健壮,无死锁。最坏的情况下,所有进程回滚到最初的虚拟时间,然后再向前运行。
全局控制机制解决下面的问题:
定义:在实际时间 r r r,全局虚拟时间是下面的较小值:
两个重要性质:
即使回滚,GVT 也不减。
虚拟时间 < < < GVT 的事件不能回滚,可以被安全地提交。
有时间复杂度 O ( d ) O(d) O(d) 的 GVT 估计算法,其中 d d d 是广播的延迟。在虚拟时间系统执行期间,必须定期来估计 GVT。
(1) 内存管理和流控制
早于 GVT 的事件可以被提交,之后被销毁以避免内存开销。
溢出队列的消息被返回给发送者来撤销。
(2) 正常终止检测
进程结束时,虚拟时间被设置为 i n f inf inf,当 GVT 是 i n f inf inf 时,表明系统终止。
(3) 错误处理
(4) I/O
只有当消息的虚拟接收时间 < < < GVT,输出活动才能被执行。
(5) 快照和毁坏恢复
虚拟时间最广的应用是分布式离散事件模拟。
C a C_a Ca 和 C b C_b Cb 是两个时钟
(1) 时间
在一个机器 p p p 中,时钟时间由函数 C p ( t ) C_p(t) Cp(t) 给出,对于一个理想的时钟, C p ( t ) = t C_p(t)=t Cp(t)=t
(2) 频率
频率是时钟的速度: C a ′ ( t ) C_a^{'}(t) Ca′(t)
(3) 位移 (Offset)
时钟位移是时钟与实际时间之差: C p ( t ) − t C_p(t) - t Cp(t)−t
(4) 偏离 (Skew)
时钟的偏离是时钟和理想时钟的频率差。时钟 C a C_a Ca 相对于 C b C_b Cb 的偏移是 C a ′ ( t ) − C b ′ ( t ) C_a^{'}(t) - C_b^{'}(t) Ca′(t)−Cb′(t)
(5) 漂移 (Drift)
时钟的漂移是时钟的二阶导数: C a ′ ′ ( t ) C_a^{''}(t) Ca′′(t)
规格: C a ′ ( t ) ∈ [ 1 − ρ , 1 + ρ ] C_a^{'}(t) \in [1 - \rho, 1 + \rho] Ca′(t)∈[1−ρ,1+ρ]
happens-before relation 是分布式程序设计和分析的基础。
分布式计算中的偏序事件集合与向量时间戳同构。
进程 p i p_i pi 的本地状态由 L S i LS_i LSi 表示,是进程直到此时执行的所有事件序列的结果。(事件集合的因果关系扩展到本地状态集合)
C i j C_{ij} Cij 表示从 p i p_i pi 到 p j p_j pj 的通道,它的状态为:
S C i j = t r a n s i t ( L S i , L S j ) = { m i j ∣ s e n d ( m i j ) ∈ L S i ∧ r e c ( m i j ) ∉ L S j } SC_{ij} = transit(LS_i, LS_j) = \{ m_{ij}\ |\ send(m{ij})\in LS_i\ \land\ rec(m_{ij}) \notin LS_j \} SCij=transit(LSi,LSj)={mij ∣ send(mij)∈LSi ∧ rec(mij)∈/LSj}
通信网络模型:CO ⊂ \subset ⊂ FIFO ⊂ \subset ⊂ 非FIFO(随机)
分布式系统的全局状态是进程的本地状态和通道状态的集合,记为:
G S = { ∪ i L S i , ∪ i , j S C i j } GS = \{ \cup_iLS_i,\ \cup_{i,j}SC_{ij} \} GS={∪iLSi, ∪i,jSCij}
全局状态是一个一致性全局状态,当且仅当满足下面2个条件:
不一致的全局状态是无意义的。
一致性分割:在 PAST 接收的消息一定是在 PAST 发送。
图中分割线 C 1 C_1 C1 是不一致的; C 2 C_2 C2 是一致的。
(1) 如何判别消息是否会被记录在快照中
快照前的发送消息一定会被记录在全局快照中。如果全局快照包含快照后的发送消息,则不一致。
(2) 如何确定进行快照的瞬间
如果 p j p_j pj 接收到的消息 m i j m_{ij} mij 是在 p i p_i pi 进行快照之后发送的,那么 p j p_j pj 先进行快照,然后处理消息。
有两类消息:
L S i LS_i LSi 由 p i p_i pi 记录;通道状态 S C i j SC_{ij} SCij 由接收方 p j p_j pj 记录。
记录本地状态 L S i LS_i LSi
向每个输出通道 C i k C_{ik} Cik 发送标记
p j p_j pj 从通道 C i j C_{ij} Cij 接收到标记时:
If p j p_j pj 并未记录本地状态(即第一次接收标记)
Else(即已经接收过标记)
proof:
这翻译真是扯淡。。。
对于“消息守恒”条件:发送事件先于本地标记事件,根据FIFO模型,接收方会在本方发送的标记前收到消息。如果本方标记是接收方的第一个标记,那么接收事件一定属于接收方本地状态;否则本方标记不是接收方的第一个标记,那么消息要么属于通道状态,要么属于接收方本地状态。
对于“因果”条件:发送事件晚于本地标记事件,根据FIFO模型,接收方会在本方发送的标记后收到消息。如果本方标记是接收方的第一个标记,接收方本地快照,快照并未包含接受这个消息的操作,并且通道状态为空,于是消息一定不属于通道状态;否则本方标记不是接收方的第一个标记,通道状态的记录已经停止,不会记录到这个消息。
复杂度:
算法需要 O ( e ) O(e) O(e) 个消息和 O ( d ) O(d) O(d) 时间,其中 e = ( n 2 ) e=\binom{n}{2} e=(2n) 为网络的边, d d d 是网络的直径,即最大的两点最短距离。
参考:
将系统划分为若干个组,每个组内自己收集全局状态,组间不传播标记,也不做快照。然后每个组的启动者互相交换快照。(不过我没懂那组间的接收通道的状态怎么来?)
适用于需要重复收集系统的全局快照。
每个快照有版本号,修改了标记发送规则。(讲得很粗,不懂)
在非FIFO模型中,通道中的消息以随机的方式被接收。需要采取其他技术保证“因果”条件被满足。
(1) 每个进程开始是白色的,快照时变为红色。红色进程执行“标记发送规则”
(2) 进程发送的消息的颜色与进程相同。因此,白色消息是快照前发送,红色消息是快照后发送。
(3) 每一个白色进程必须在接收第一个红色消息之前快照
这保证了一个进程在记录了本地快照之后发送的消息(红),不会被未进行快照的接收进程处理。(保证“因果”条件)
FIFO模型自动区分快照前后的消息,但是非FIFO模型不行,必须给消息加入一个tag,用来区分快照前后发送的消息。
(4) 每个白色进程记录了每个通道发送或接收白色消息的历史
(5) 进程变红时,它把历史和自己的快照一起发送给收集全局快照的启动者
(6) 启动者评估 t r a n s i t ( L S i , L S j ) transit(LS_i, LS_j) transit(LSi,LSj),计算 C i j C_{ij} Cij 的状态:
S C i j = p i 在 C i j 上 发 送 的 白 消 息 − p j 在 C i j 上 接 收 的 白 消 息 = { m i j ∣ s e n d ( m i j ) ∈ L S i } − { m i j ∣ r e c ( m i j ) ∈ L S j } \begin{aligned} SC_{ij} & = p_i\ 在\ C_{ij}\ 上发送的白消息\ -\ p_j\ 在\ C_{ij}\ 上接收的白消息 \\ & = \{ m_{ij}\ |\ send(m_{ij})\in LS_i \}\ -\ \{ m_{ij}\ |\ rec(m_{ij})\in LS_j \} \\ \end{aligned} SCij=pi 在 Cij 上发送的白消息 − pj 在 Cij 上接收的白消息={mij ∣ send(mij)∈LSi} − {mij ∣ rec(mij)∈LSj}
proof:
“消息守恒”条件成立:白色消息要么被接收,要么在通道状态中。
“因果”条件成立:因为红色消息既不在通道状态中,也不在本地状态中。
基于向量时钟,通过向量时间戳来判断,在非FIFO模型中,一个发送消息是在快照之前还是之后。(不可比呢??好像是只比较启动者的那一维时间,所以相当于一个tag标记是否已经快照,换句话说一定可比。。。)
CO 的性质非常强: s e n d ( m i j ) → s e n d ( m k j ) ⇒ r e c ( m i j ) → r e c ( m k j ) send(m_{ij})\to send(m_{kj})\ \Rightarrow\ rec(m_{ij})\to rec(m_{kj}) send(mij)→send(mkj) ⇒ rec(mij)→rec(mkj)
这两个算法用相同的原理记录本地状态:启动者广播一个标志 t o k e n token token(包括自己),进程 p i p_i pi 接收到 t o k e n token token 的副本 t o k e n i token_i tokeni 时,记录本地快照 L S i LS_i LSi,并发送给启动者。当启动者收到每个进程的快照时,算法终止。
每个进程 p i p_i pi 维护:
当进程 p i p_i pi 收到 t o k e n i token_i tokeni 记录本地快照时,将数组 S E N T i , R E C D i SENT_i, RECD_i SENTi,RECDi 和本地快照一并发给启动者。当算法终止时,启动者按下述方法确定通道状态:
(1) 从启动者到每个进程的通道状态为空
(2) S C i j SC_{ij} SCij 是由 S E N T i [ j ] − R E C D j [ i ] SENT_i[j] - RECD_j[i] SENTi[j]−RECDj[i] 给出的消息集合(注意:并不一定有 R E C D j [ i ] ⊆ S E N T i [ j ] RECD_j[i] \subseteq SENT_i[j] RECDj[i]⊆SENTi[j])
proof:
对于“消息守恒”条件:
对于一个 s e n d ( m i j ) ∈ L S i send(m_{ij})\in LS_i send(mij)∈LSi
对于“因果”条件:
对于一个 s e n d ( m i j ) send(m_{ij}) send(mij) 事件,使得 s e n d ( t o k e n ) → r e c ( t o k e n i ) → s e n d ( m i j ) send(token) \to rec(token_i) \to send(m_{ij}) send(token)→rec(tokeni)→send(mij)
这里应该是要假设 “所有 s e n d ( t o k e n x ) send(token_x) send(tokenx) 在同一瞬间完成”
定义 旧: s e n d ( m i j ) → s e n d ( t o k e n x ) send(m_{ij}) \to send(token_x) send(mij)→send(tokenx);否则称为新。
按下述方法记录通道状态:
(1) 当进程接收 t o k e n token token 时,进行本地快照,返回 D o n e Done Done 消息给启动者。并开始记录接收通道上的旧消息
(2) 启动者收到所有 D o n e Done Done 消息后,广播一个终止消息( s e n d ( s t o p x ) send(stop_x) send(stopx))
(3) 当收到终止消息,进程停止记录通道状态
proof:
首先注意到一个事实: s e n d ( t o k e n x ) → r e c ( t o k e n i ) → s e n d ( D o n e i ) → s e n d ( s t o p x ) → r e c ( s t o p i ) send(token_x) \to rec(token_i) \to send(Done_i) \to send(stop_x) \to rec(stop_i) send(tokenx)→rec(tokeni)→send(Donei)→send(stopx)→rec(stopi)
对于“消息守恒”条件:
对于一个 s e n d ( m i j ) ∈ L S i send(m_{ij}) \in LS_i send(mij)∈LSi,有 s e n d ( m i j ) → s e n d ( s t o p j ) ⇒ r e c ( m i j ) → r e c ( s t o p j ) send(m_{ij}) \to send(stop_j) \Rightarrow rec(m_{ij}) \to rec(stop_j) send(mij)→send(stopj)⇒rec(mij)→rec(stopj),于是要么 r e c ( m i j ) ∈ L S j rec(m_{ij}) \in LS_j rec(mij)∈LSj,要么 m i j ∈ S C i j m_{ij} \in SC_{ij} mij∈SCij。
对于“因果”条件:
对于一个 s e n d ( m i j ) send(m_{ij}) send(mij) 事件,使得 r e c ( t o k e n i ) → s e n d ( m i j ) rec(token_i) \to send(m_{ij}) rec(tokeni)→send(mij)
快照算法的比较:
检查点:进程的中间状态,可以认为没有事件。用 C p , i C_{p,i} Cp,i 表示进程 p p p_p pp 的第 i i i 个检查点。 C p , i C_{p,i} Cp,i 所代表的区间由 C p , i − 1 C_{p,i-1} Cp,i−1 到 C p , i C_{p,i} Cp,i 的所有事件组成。
全局快照就是每个进程上一个检查点的集合。如果全局快照中任何两个检查点之间没有 happens-before relation,则这个全局快照是一致性的。
定义1:从检查点 C x , i C_{x,i} Cx,i 到 C y , j C_{y,j} Cy,j 存在一条 z i g z a g zigzag zigzag 路径,当且仅当存在消息 m 1 , . . . , m q m_1,...,m_q m1,...,mq,使得
(1) m 1 m_1 m1 被进程 p x p_x px 在 C x , i C_{x,i} Cx,i 之后发送
(2) 如果 m k ( k ∈ [ 1 , q − 1 ] ) m_k\ (k\in[1,q-1]) mk (k∈[1,q−1]) 被 p z p_z pz 接收,那么 m k + 1 m_{k+1} mk+1 在相同的(或之后)检查点区间被 p z p_z pz 发送。( s e n d ( m k + 1 ) send(m_{k+1}) send(mk+1) 和 r e c ( m k ) rec(m_k) rec(mk) 先后关系未指定!!换句话说,如果 s e n d ( m k + 1 ) send(m_{k+1}) send(mk+1) 在前,那么一定在同一个检查点区间内;如果 s e n d ( m k + 1 ) send(m_{k+1}) send(mk+1) 在后,则没有要求,自然同步)
(3) m q m_q mq 被进程 p y p_y py 在 C y , j C_{y,j} Cy,j 之前接收
例如: C 1 , 1 C_{1,1} C1,1 到 C 3 , 2 C_{3,2} C3,2 的 z i g z a g zigzag zigzag 路径经过 m 3 m_3 m3 和 m 4 m_4 m4。(进程 p 2 p_2 p2 在同一检查点区间内, r e c ( m 3 ) rec(m_3) rec(m3) 并且 s e n d ( m 4 ) send(m_4) send(m4),另外 s e n d ( m 4 ) send(m_4) send(m4) 先于 r e c ( m 3 ) rec(m_3) rec(m3))
定义2:一个检查点 C C C 被包含在一个 z i g z a g zigzag zigzag 环路中,当且仅当存在一条从 C C C 到本身的 z i g z a g zigzag zigzag 路径。
例如: C 2 , 1 C_{2,1} C2,1 是在由消息 m 1 m_1 m1 和 m 2 m_2 m2 形成的一条 z i g z a g zigzag zigzag 环路中。(消息序列为 m 2 , m 1 m_2, m_1 m2,m1;在进程 p 2 p_2 p2 中, m 2 m_2 m2 在检查点 C 2 , 1 C_{2,1} C2,1 之后发送;在进程 p 1 p_1 p1 中,同一检查点内 s e n d ( m 1 ) send(m_1) send(m1) 先于 r e c ( m 2 ) rec(m_2) rec(m2);最后在进程 p 2 p_2 p2 中, m 1 m_1 m1 在 C 2 , 1 C_{2,1} C2,1 之前被接收)
因果路径:存在一条从检查点 A A A 到检查点 B B B 的因果路径,当且仅当存在一条在 A A A 之后开始,在 B B B 之前结束的消息链,该链中前一个消息被接收后,后一个消息才被发送。因果路径是 z i g z a g zigzag zigzag 路径。
在 z i g z a g zigzag zigzag 路径中,消息链的前一个消息接收之前,后一个消息允许已经被发送,只要在同一个检查点区间内。
z i g z a g zigzag zigzag 路径的性质:任何包含这两个检查点的快照都是不一致性的。
Netzer 和 Xu 证明:如果检查点集合 S S S 是一个一致性全局快照,当且仅当 S S S 中任意两点不存在 z i g z a g zigzag zigzag 路径。
总结:
定义: A A A 和 B B B 是检查点, R R R 和 S S S 是检查点集合,令 ⇝ \leadsto ⇝ 是检查点和检查点集合间的关系
(1) A ⇝ B A\leadsto B A⇝B,当且仅当存在从 A A A 到 B B B 的 Z 路径
(2) A ⇝ S A\leadsto S A⇝S,当且仅当存在从 A A A 到 S S S 的某个成员的 Z 路径
(3) S ⇝ A S\leadsto A S⇝A,当且仅当存在一条从 S S S 的某个成员到 A A A 的 Z 路径
(4) R ⇝ S R\leadsto S R⇝S,当且仅当存在一条从 R R R 的某个成员到 S S S 的某个成员的 Z 路径
定理:检查点集合 S S S 能被扩展成一个一致性全局快照,当且仅当 S ̸ ⇝ S S\not\leadsto S S̸⇝S
令 S S S 是使得 S ̸ ⇝ S S\not\leadsto S S̸⇝S 的检查点的一个集合,那么对进程 p q p_q pq,集合 S u s e f u l q S_{useful}^q Susefulq 被定义为
S u s e f u l q = { C q , i ∣ ( S ̸ ⇝ C q , i ) ∧ ( C q , i ̸ ⇝ S ) ∧ ( C q , i ̸ ⇝ C q , i ) } S_{useful}^q = \{ C_{q,i}\ |\ (S\not\leadsto C_{q,i})\ \land\ (C_{q,i}\not\leadsto S)\ \land\ (C_{q,i}\not\leadsto C_{q,i}) \} Susefulq={Cq,i ∣ (S̸⇝Cq,i) ∧ (Cq,i̸⇝S) ∧ (Cq,i̸⇝Cq,i)}
之后定义 $S_{useful} = \bigcup_q S_{useful}^q $
引理:令 S S S 是使得 S ̸ ⇝ S S\not\leadsto S S̸⇝S 的检查点的一个集合,并且 C q , i ∉ S C_{q,i}\notin S Cq,i∈/S。那么 S ∪ { C q i } S\cup \{C_{q_i}\} S∪{Cqi} 能扩展成一个一致性快照,当且仅当 C q , i ∈ S u s e f u l C_{q,i}\in S_{useful} Cq,i∈Suseful。
定理:令 S S S 是使得 S ̸ ⇝ S S\not\leadsto S S̸⇝S 的检查点的一个集合,且 T T T 是任意检查点集合满足 T ∩ S = ϕ T \cap S = \phi T∩S=ϕ。那么 S ∪ T S\cup T S∪T 是一个一致性全局快照,当且仅当
C o m p u t e A l l C g s ( S ) { let G = ϕ if S ̸ ⇝ S then 设 A l l P r o s 表 示 未 在 集 合 S 中 出 现 的 进 程 C o m p u t e A l l C g s F r o m ( S , A l l P r o s ) return G } C o m p u t e A l l C g s F r o m ( S , P r o c S e t ) { if ( P r o c S e t = ϕ ) then G = G ∪ { S } else 设 P q 表 示 集 合 P r o c S e t 中 的 任 一 进 程 for ∀ C ∈ S u s e f u l q do C o m p u t e A l l C g s F r o m ( S ∪ { C } , P r o c S e t − { P q } ) } \begin{aligned} & ComputeAllCgs(S)\ \{ \\ & \qquad \textbf{let}\ G = \phi \\ & \qquad \textbf{if}\ S\not\leadsto S\ \textbf{then} \\ & \qquad \qquad 设\ AllPros\ 表示未在集合\ S\ 中出现的进程 \\ & \qquad \qquad ComputeAllCgsFrom(S,\ AllPros) \\ & \qquad \textbf{return}\ G \\ & \} \\ & \\ & ComputeAllCgsFrom(S, ProcSet)\ \{ \\ & \qquad \textbf{if}\ (ProcSet = \phi)\ \textbf{then} \\ & \qquad \qquad G = G\cup \{S\} \\ & \qquad \textbf{else}\ \\ & \qquad \qquad 设\ P_q\ 表示集合\ ProcSet\ 中的任一进程 \\ & \qquad \qquad \textbf{for}\ \forall\ C\in S_{useful}^q\ \textbf{do}\ \\ & \qquad \qquad \qquad ComputeAllCgsFrom(S\cup\{C\},\ ProcSet - \{P_q\}) \\ & \} \\ \end{aligned} ComputeAllCgs(S) {let G=ϕif S̸⇝S then设 AllPros 表示未在集合 S 中出现的进程ComputeAllCgsFrom(S, AllPros)return G}ComputeAllCgsFrom(S,ProcSet) {if (ProcSet=ϕ) thenG=G∪{S}else 设 Pq 表示集合 ProcSet 中的任一进程for ∀ C∈Susefulq do ComputeAllCgsFrom(S∪{C}, ProcSet−{Pq})}
C o m p u t e A l l C g s ( S ) ComputeAllCgs(S) ComputeAllCgs(S) 返回包含 S S S 的所有一致性快照的集合。(即 G G G 中的每一个元素都是包含 S S S 的一致性快照)
C o m p u t e A l l C g s F r o m ( S , P r o c S e t ) ComputeAllCgsFrom(S, ProcSet) ComputeAllCgsFrom(S,ProcSet) 通过递归,试图从每一个可扩展的检查点向下遍历。
proof:略。
定义:分布式计算的回滚依赖图(R图)是一个有向图 G = ( V , E ) G=(V,E) G=(V,E),其中 V V V 是检查点集合,边 ( C p , i , C q , j ) ∈ E (C_{p,i}, C_{q,j})\in E (Cp,i,Cq,j)∈E 如果
(1) p = q ∧ j = i + 1 p=q\ \land\ j=i+1 p=q ∧ j=i+1 或
(2) p ≠ q p\neq q p̸=q 且 C p , i C_{p,i} Cp,i 区间发送的消息被 C q , j C_{q,j} Cq,j 区间接收
构建一个R图:
定理:令 G = ( V , E ) G=(V,E) G=(V,E) 是R图,那么对任意两个检查点 C p , i C_{p,i} Cp,i 和 C q , j C_{q,j} Cq,j,有 C p , i ⇝ rd C q , j C_{p,i} \overset{\text{rd}}{\leadsto} C_{q,j} Cp,i⇝rdCq,j 当且仅当
(1) p = q ∧ i , j p=q\ \land\ i,j p=q ∧ i,j 或
(2) C p , i + 1 ⇝ rd C q , j C_{p,i+1}\overset{\text{rd}}{\leadsto} C_{q,j} Cp,i+1⇝rdCq,j(可能有 p = q p=q p=q)
记录全局快照的一个重点在于区分本地快照前后发送的消息,在不同通信模型下会有不一样的处理。