文章目录
- 分布式系统
- 基本概念
- 设计目标
- 时间
- 基本概念
- 时间同步
- 同步系统
- 异步系统
- 网络时间协议(NTP)
- 逻辑时钟 & 向量时钟
- 发生在先
- 逻辑时钟
- 向量时钟
- 状态 & 快照
- 全局状态 & 割集
- 快照算法
分布式系统
基本概念
分布式计算:是利用分布在网络上的各种软、硬计算机资源进行信息处理的过程,它是通过基于网络的分布式系统实现的。
分布式计算的多种形态:
- 网格计算、集群计算;
- 云计算、边缘计算、雾计算;
- 移动计算、普适计算;
- 对等计算。
分布式系统:是使得基于网络互相连接的若干计算单元进行协同计算的软件。对用户而言,好像是面对一个单一的系统。
分布式系统的基本属性:
-
一个分布式系统拥有一定数目的计算单元和一定数目的进程
-
物理资源:计算单元
-
逻辑资源:进程
-
-
进程之间通过消息传递进行通信
-
进程之间以协作的方式交互
-
通信延迟不可忽略
-
任何单个逻辑或物理的资源故障不会导致整个系统的崩溃
-
在资源故障的情况下系统必须具有重新配置和故障恢复的能力
分布式系统的分类:
- 系统设计的角度
- 时序假设:同步系统、异步系统、部分同步系统
- 故障假设:随机故障、非随机故障
- 进程间的通信机制
- 使用规模的角度:个人用户、企业用户、互联网应用
- 基于网络的角度:有线网、无线网;车联网、物联网
分布式算法与集中式算法的区别:
- 分布式算法是以没有全局时钟为前提的,
- 没有任何一台机器具有系统的完整状态信息,
- 每台机器仅根据本地信息进行决策,
- 一台机器出了故障,不会使整个算法崩溃。
设计目标
-
连接用户和资源:以一种安全、可靠的方式进行资源共享和用户协作
-
透明性(Transparency):
- 100% 的透明性并不总是一个好主意,过分强调透明性可能会带来性能效率上的损失
-
开放性(Openness):
-
它提供的服务的规约应该是完整的和中性的,
- 完整是指实现所需的所有信息都能在服务的接口中指定,
- 接口的中性描述是指接口的规约并不规定实现是什么样的,
完整和中性有助于提高互操作能力和可移植性。
-
系统应该是灵活的、可扩展 (extensible) 的,
- 可以把不同开发人员开发的组件搭配起来,配置起来,可以在不影响其它组件的情况下增加、替换组件。
-
-
可伸缩性(Scalability):
- 在规模上可伸缩 / vertical scalability:可以增加更多的用户和资源,
- 在地理上可伸缩 / horizontal scalability:用户、资源都可以相距很远,
- 在管理上可伸缩:能很容易地管理相互独立的组织。
改善系统的可伸缩性:
- 体系结构层面:
- 对数据、服务进行克隆
- 对数据、服务、客户进行拆分,分布到不同地点
- 通信层面:
- 使用异步通信,避免信道拥挤
- 减少通信
- 容错层面:
- 隔离故障,避免级联模块的设计,避免单点故障的设计
- 故障的恢复
- 数据层面:
- 使用复制、缓存
- 尽量实现无状态的系统
- 如果有状态,尽可能在客户端维护会话,或者使用分布式缓存来存放状态
时间
基本概念
秒(second):铯原子 Cs133 基态的两个超精细能级间跃迁辐射震荡 9192631770 9192631770 9192631770 周所持续的时间为 1 1 1 秒。
时钟漂移(clock drift):时钟的绝对偏差,石英钟的漂移率为 1 0 − 6 s / s 10^{-6} s \big/ s 10−6s/s
时钟偏移(clock skew):两个时钟在读数上的瞬时差值
UTC(Universal Time Coordinated):协调世界时,世界标准时间
-
时间的测量:国家授时中心,使用”小铯钟“。
-
时间的发送和接收:长波、短波;网络时间服务器;卫星。
计算机时钟:
-
硬件时钟: H ( t ) H(t) H(t),晶振
-
软件时钟: C ( t ) = α H ( t ) + β C(t) = \alpha H(t) + \beta C(t)=αH(t)+β,根据软件调整 ( α , β ) (\alpha,\beta) (α,β) 的值
-
分布式系统的每个进程使用各自的本地时间,它们的漂移率各不相同;如果不及时校准,各进程的时钟会偏差很大。
时钟的正确性:设 t < t ′ t<t' t<t′ 是实际时间, H ( t ) , H ( t ′ ) H(t),H(t') H(t),H(t′) 是相应的硬件时钟值, ρ > 0 \rho>0 ρ>0 是漂移率
- 硬件时钟的误差有界性: ( 1 − ρ ) ( t ′ − t ) ≤ H ( t ′ ) − H ( t ) ≤ ( 1 + ρ ) ( t ′ − t ) (1-\rho)(t'-t) \le H(t')-H(t) \le (1+\rho)(t'-t) (1−ρ)(t′−t)≤H(t′)−H(t)≤(1+ρ)(t′−t)
- 软件时钟的单调性: t < t ′ ⇒ C ( t ) < C ( t ′ ) t<t' \Rightarrow C(t) < C(t') t<t′⇒C(t)<C(t′)
- 如果计算机时间慢了:在同步点拨快,追上标准时间
- 如果计算机时间快了:不可拨慢(违反单调性),可以调小 α \alpha α 调大 β \beta β 使得时钟走的慢一些,等候完美时间
时间同步
外部同步:用权威的外部时间源同步进程的时钟
内部同步:指时钟相互同步
- 内部同步的时钟未必是外部同步的
- 如果系统在范围 D D D 内是外部同步的,那么同一系统在范围 2 D 2D 2D 内是内部同步的
同步系统
- 已知时钟漂移率的范围
- 已知最大的消息传输延迟
- 已知进程每一步的执行时间
有 2 2 2 个进程的分布式系统,用 m i n min min 表示最小的消息传输延迟,用 m a x max max 表示最大的消息传输延迟,
- 进程 p p p 发送本地时间 t t t 给另一个进程 q q q
- 进程 q q q 将自己的时钟设置为 t + ( m i n + m a x ) / 2 t+(min+max)/2 t+(min+max)/2
与实际时间 t + T t r a n s t+T_{trans} t+Ttrans 相差至多 ( m a x − m i n ) / 2 (max-min)/2 (max−min)/2
有 N N N 个进程的分布式系统,时间同步算法扩展为:
- 进程 p p p 发送当前的本地时间 t = t p t=t_p t=tp 给其他进程 q ≠ p q \neq p q=p
- 进程
p
p
p 等待其他
N
−
1
N-1
N−1 个进程的消息,设置
s
u
m
=
0
sum=0
sum=0(与自己的时钟偏移)
- 收到来自 q q q 的时间 t q t_q tq
- 计算 δ = t q + ( m i n + m a x ) / 2 − t p \delta = t_q + (min+max)/2 - t_p δ=tq+(min+max)/2−tp(与进程 q q q 的时钟偏移)
- 计算累加值 s u m = s u m + δ sum = sum+\delta sum=sum+δ
- 当前时间为 t p ′ t_p' tp′,将自己的时钟设置为 t p ′ + s u m / N t_p'+sum/N tp′+sum/N
系统的 N N N 个计算机,两两之间的时间差至多为 γ < ( m a x − m i n ) / ( 1 − 1 / N ) \gamma < (max-min)/(1-1/N) γ<(max−min)/(1−1/N)
异步系统
在进程执行时间、消息传递时间和时钟漂移上没有限制的
- 不知道最大的消息传输延迟,可能是无穷大
- 但最小的消息传递延迟是可知的
外部同步方法(Cristian 算法):
- 使用一个时间服务器 S S S,接收标准时间信号
- 进程 p p p 发送给 S S S 一个请求 m r m_r mr
- 服务器 S S S 回应消息 m t m_t mt,包含接收到 m r m_r mr 的时间 t t t
- 进程 p p p 记录发送 m r m_r mr 和接收 m t m_t mt 之间的往返时间 T r o u n d T_{round} Tround
- 进程 p p p 设置时钟为 t + T r o u n d / 2 t+T_{round}/2 t+Tround/2
需要 T r o u n d T_{round} Tround 远小于所要求的时间精度,且时间服务器 S S S 是正确的。由于 t e = t s + T r o u n d t_e = t_s+T_{round} te=ts+Tround 与 t t t 的关系为 t + m i n < t e t+min < t_e t+min<te 和 t > t s + m i n t>t_s+min t>ts+min,因此 t e ∈ [ t + m i n , t + T r o u n d − m i n ] t_e \in [t+min, t+T_{round}-min] te∈[t+min,t+Tround−min],精度为 ± ( T r o u n d / 2 − m i n ) \pm(T_{round}/2 - min) ±(Tround/2−min)
内部同步方法(Berkely 算法):
- 在计算机集群里挑选一个 Master,其他的作为 Slave
- Master 周期性地轮询,Slave 将自己的时钟值发送给 Master
- Master 类似 Cristian 那样观察往返时间,估计 Slave 的本地时间,然后计算所有时间(包括自己)的平均值
- Master 发送各个 Slave 需要的调整量给对应的 Slave
精确度取决于 Master-Slave 之间的最大往返时间。为了更强的容错:
- Master 使用“容错平均值”(差值在一定范围内的子集),过滤掉异常往返时间
- Master 通过选举产生,这允许 Master 崩溃
网络时间协议(NTP)
NTP(Network Time Protocol)定义了时间服务的体系结构和在 Internet 上发送时间信息的协议。
设计目标:
-
提供一个服务,使得 Internet 上的用户能精确地同标准时间同步:用统计方法来过滤时序数据
-
使得客户能经常有效地重新同步以抵消漂移率:该服务具有对大量客户和服务器的可伸缩性
-
在不可靠的通信链接上提供一个可靠服务:通过提供冗余的服务器以及服务器之间冗余的路径
-
能防止对时间服务的干扰,无论是恶意的还是偶然的:使用认证技术
体系结构:
-
主服务器 (在 root 上) 直接连到外部时间源,例如短波时间接收器、GPS接收器之类的设备。层次 (stratum) 2 2 2 的服务器与主服务器同步,层次 3 3 3 的服务器与层次 2 2 2 的服务器同步等等。
-
同步子网是用 Bellman-Ford 路由算法的变种来组织的,构建以主服务器为根的最小权重的支撑树。
故障处理:
-
同步子网可以进行重新配置,以应对服务器不可达或出现故障。
-
如果主服务器的外部时间源出现故障,那么它能变成层次 2 2 2 的二级服务器 。
同步方式:
- 组播模式,用于高速LAN环境。一个或多个服务器周期性地将时间组播到其他服务器,并设置它们的时钟 (假设延迟很小)。
- 过程调用模式,类似 Cristian 算法的操作。一个服务器接收来自其他计算机请求,并用时间戳(本地的当前的时钟读数)应答。这个模式适合精确性要求比组播更高的地方,或不能用硬件支持组播的地方。
- 对称模式,用于在LAN中提供时间信息的服务器和同步子网的较高层(较小的层次数),即要获得最高精确性的地方。一对服务器相互交换有时序信息的消息。
NTP 的消息传递使用 UDP,每个消息携带3个时间戳:
- 发送前一个 NTP 消息的本地时间
- 接收前一个 NTP 消息的本地时间
- 发送当前 NTP 消息的本地时间
服务器 A A A 发送消息 m m m 给服务器 B B B,发送时间 t i − 3 t_{i-3} ti−3( A A A 的),接收时间 t i − 2 t_{i-2} ti−2( B B B 的),传输时间为 T T T
服务器 B B B 发送消息 m ′ m' m′ 给服务器 A A A,发送时间 t i − 1 t_{i-1} ti−1( B B B 的),接收时间 t i − 1 t_{i-1} ti−1( A A A 的),传输时间为 T ′ T' T′
假设服务器
A
,
B
A,B
A,B 的时钟真实偏移为
o
o
o,估计值为
o
i
o_i
oi,令
d
i
=
T
+
T
′
d_i = T+T'
di=T+T′,那么
t
i
−
2
=
t
i
−
3
+
t
+
o
t
i
=
t
i
−
1
+
t
′
−
o
d
i
=
(
t
i
−
2
−
t
i
−
3
)
+
(
t
i
−
t
i
−
1
)
\begin{aligned} t_{i-2} &= t_{i-3}+t+o\\ t_{i} &= t_{i-1}+t'-o\\ d_i &= (t_{i-2}-t_{i-3}) + (t_{i}-t_{i-1}) \end{aligned}
ti−2tidi=ti−3+t+o=ti−1+t′−o=(ti−2−ti−3)+(ti−ti−1)
设置估计值
o
i
=
(
t
i
−
2
−
t
i
−
3
+
t
i
−
t
i
−
1
)
/
2
o_i = (t_{i-2}-t_{i-3} + t_{i}-t_{i-1})/2
oi=(ti−2−ti−3+ti−ti−1)/2,那么
o
=
o
i
+
(
T
′
−
T
)
/
2
o = o_i + (T'-T)/2
o=oi+(T′−T)/2,得到
o
i
−
d
i
/
2
≤
o
≤
o
i
+
d
i
/
2
o_i - d_i/2 \le o \le o_i + d_i/2
oi−di/2≤o≤oi+di/2。因此,各个 NTP 服务器之间应该多次执行程序,使用
d
i
d_i
di 最小的
o
i
o_i
oi 值作为对
o
o
o 的估计。
逻辑时钟 & 向量时钟
发生在先
Lamport 发生在先(happened-before)关系 → \to →,需要满足:
- HB1:如果存在进程 p i p_i pi 中的关系 e → i e ′ e \to_i e' e→ie′,那么 e → e ′ e \to e' e→e′
- HB2:对于任意的消息 m m m,都有 s e n d ( m ) → r e c e i v e ( m ) send(m) \to receive(m) send(m)→receive(m)
- HB3:如果 e → e ′ e \to e' e→e′ 且 e ′ → e ′ ′ e' \to e'' e′→e′′,那么 e → e ′ ′ e \to e'' e→e′′
发生在先关系的性质:
- 不能由 → \to → 排序的两个事件( e → e ′ e \to e' e→e′ 和 e ′ → e e' \to e e′→e 都不成立),它们是并发事件(可能有资源竞争,不是并行),记做 e ∥ e ′ e \| e' e∥e′
- 关系 → \to → 捕获了以消息传递方式表示的数据流(但不能对非消息传递的数据流动建模)
- 关系 → \to → 捕获可能的因果关系,第一个事件不一定会影响第二个事件
逻辑时钟
Lamport 逻辑时钟,是一个单调增长的软件计数器。
每个进程 p i p_i pi 维护自己的逻辑时钟 L i L_i Li,用它给事件打时间戳。事件 e e e,用 L i ( e ) L_i(e) Li(e) 表示 p i p_i pi 的时间戳,用 L ( e ) L(e) L(e) 表示任意进程中的时间戳。
计算规则:
- LC1:进程 p i p_i pi 发出事件 e e e 之前,首先 L i = L i + 1 L_i = L_i+1 Li=Li+1,然后打戳 L i ( e ) L_i(e) Li(e)
- LC2:
- 发送:进程 p i p_i pi 发送消息 m m m 时,先按照 LC1 打戳,然后在 m m m 上捎带 t = L i t=L_i t=Li
- 接收:进程 p i p_i pi 接收到 ( m , t ) (m,t) (m,t),先设置 L i = max ( L i , t ) L_i = \max(L_i,t) Li=max(Li,t),然后按照 LC1 打戳
逻辑时钟无法区分,是由于局部事件引起的时钟前进,还是由于消息交换引起的时钟前进。
与发生在先的必要不充分关系:
- 如果 e → e ′ e \to e' e→e′,那么 L ( e ) < L ( e ′ ) L(e) < L(e') L(e)<L(e′)
- 但是 L ( e ) < L ( e ′ ) L(e) < L(e') L(e)<L(e′) 不能推出 e → e ′ e \to e' e→e′
向量时钟
为弥补逻辑时钟的缺点,对于 N N N 个进程,每个进程 p i p_i pi 持有向量 V i [ 1 ⋯ N ] V_i[1 \cdots N] Vi[1⋯N]
- V i [ i ] V_i[i] Vi[i] 是进程 p i p_i pi 自己的加时间戳的事件个数(可能与当前事件 e e e 有发生在先关系的的进程 p i p_i pi 上事件个数)
- V i [ j ] , j ≠ i V_i[j],j \neq i Vi[j],j=i 是 p j p_j pj 中可能会影响 p i p_i pi 的事件个数(可能与当前事件 e e e 有发生在先关系的进程 p j p_j pj 上事件个数)
计算规则:
- VC1:初始化 V i [ j ] = 0 , ∀ i , j = 1 , ⋯ , N V_i[j]=0,\, \forall i,j=1,\cdots,N Vi[j]=0,∀i,j=1,⋯,N
- VC2:进程 p i p_i pi 发出事件 e e e 之前,首先 V i [ i ] = V i [ i ] + 1 V_i[i] = V_i[i]+1 Vi[i]=Vi[i]+1,然后打戳 V i ( e ) V_i(e) Vi(e)
- VC3:进程 p i p_i pi 发送消息 m m m 时,先按照 VC2 打戳,然后在 m m m 上捎带 t = V i t=V_i t=Vi
- VC4:进程 p i p_i pi 接收到 ( m , t ) (m,t) (m,t),先设置 V i [ j ] = max ( V i [ j ] , t [ j ] ) V_i[j] = \max(V_i[j],t[j]) Vi[j]=max(Vi[j],t[j]),然后按照 VC2 打戳
与发生在先的等价关系:
- V = V ′ ⟺ V [ j ] = V ′ [ j ] , ∀ j = 1 , ⋯ , N V = V' \iff V[j] = V'[j],\forall j=1,\cdots,N V=V′⟺V[j]=V′[j],∀j=1,⋯,N
- V ≤ V ′ ⟺ V [ j ] ≤ V ′ [ j ] , ∀ j = 1 , ⋯ , N V \le V' \iff V[j] \le V'[j],\forall j=1,\cdots,N V≤V′⟺V[j]≤V′[j],∀j=1,⋯,N
- V < V ′ ⟺ ( V ≤ V ′ ) ∧ ( V ≠ V ′ ) V < V' \iff (V \le V') \wedge (V \neq V') V<V′⟺(V≤V′)∧(V=V′)
- 等价关系: e → e ′ ⟺ V ( e ) < V ( e ′ ) e \to e' \iff V(e) < V(e') e→e′⟺V(e)<V(e′)
- 如果 V ( e ) < V ( e ′ ) V(e) < V(e') V(e)<V(e′) 与 V ( e ) > V ( e ′ ) V(e) > V(e') V(e)>V(e′) 都不成立,那么 e ∥ e ′ e \| e' e∥e′
状态 & 快照
全局状态 & 割集
全局状态:分布式系统可以看作是一系列协同工作的进程集合 P = P 1 , P 2 , … , P n P={P_1,P_2,…,P_n} P=P1,P2,…,Pn,进程可以是物理分布的,它们之间通过消息通信实现互操作。分布式系统的全局状态由局部状态集和消息通道状态集组成:
-
局部状态集,是指系统中的一个进程的全部变量的集合
-
消息通道状态集,是指在消息传输中的消息序列的集合
观察全局状态的必要性:分布式垃圾回收、分布式调试、死锁判定、程序终止判定、检查点。
一些描述状态的术语:
- 进程的历史:一系列的内部事件、发送事件、接收事件, h i s t o r y ( p i ) = h i = ⟨ e i 0 , e i 1 , ⋯ ⟩ history(p_i) = h_i = \langle e_i^0,e_i^1,\cdots \rangle history(pi)=hi=⟨ei0,ei1,⋯⟩
- 进程历史的有限前缀: h i k = ⟨ e i 0 , e i 1 , ⋯ , e i k ⟩ h_i^k = \langle e_i^0,e_i^1,\cdots,e_i^k \rangle hik=⟨ei0,ei1,⋯,eik⟩
- 全局历史:进程历史的并集, H = ⋃ i = 1 N h i H = \bigcup_{i=1}^N h_i H=⋃i=1Nhi
- 割集(Cut):全局历史的子集, C = ⋃ i = 1 N h i c i C = \bigcup_{i=1}^N h_i^{c_i} C=⋃i=1Nhici
- 割集的边界:进程 p i p_i pi 处理的最后一个事件的集合, B = { e i c i } B = \{e_i^{c_i}\} B={eici}
一致的割集:它包含的每个事件,它也包含了所有在该事件之前发生的所有事件。如果 e i k ∈ C e_i^k \in C eik∈C,那么 ∀ e j k ′ → e i k , e j k ′ ∈ C \forall e_j^{k'} \to e_i^k,\,e_j^{k'} \in C ∀ejk′→eik,ejk′∈C
一致的全局状态:相对于一致割集的全局状态
快照算法
算法目标:
- 记录进程集的进程状态和通道状态
- 算法所记录的状态组合可能并没有在同一时间发生,但所记录的全局状态是一致的
算法假设:
- 不论是通道还是进程都不出故障
- 通信是可靠的,每个发送的消息最终被完整地接收到恰好一次
- 通道是单向的,提供 FIFO 顺序的消息传递
- 描述进程和通道的图是强连通的 (在任两个进程之间有一条路径)
算法的优点:
- 任一进程可在任一时间开始一个全局快照
- 在照快照时,进程可以继续它们的执行,可以发送和接收正常的消息
Chandy-Lamport Snapshot Algorithm:
- 标记接收规则:
- 进程 p i p_i pi 从接收信道 c c c 上获得标记信息(Marker Message)
- 如果
p
i
p_i
pi 还没记录自己的进程状态
- 记录自己的进程状态
- 将接收信道 c c c 的状态置为空集
- 开始记录其他接收信道 c ′ c' c′ 上的消息
- 否则,把接收信道
c
c
c 的通道状态记录为
step 1.2.3
的所有接收到的消息集合
- 标记发送规则:
- 进程
p
i
p_i
pi 完成了
step 1.2.1
后, - 对每个发送信道 c c c 上,在发送任何其他消息之前,发送标记信息
- 进程
p
i
p_i
pi 完成了
- 算法启动:任一进程 p i p_i pi 从一条不存在的接收信道上接收到了一个标记
对算法的理解:
- 在
step 1.2
,进程 p i p_i pi 启动自己的快照算法。 - 在
step 1.2.2
,信道 c c c 上没有影响当前全局状态的消息了。 - 在
step 1.2.3
,除了 c c c 以外的接收信道上,可能有影响当前全局状态的消息,还没接收到。 - 在
step 2.2
,进程 p i p_i pi 的每个发送信道上,恰好发送一次标记信息。要么启动对方的快照算法,要么结束对方对这个信道的记录。 - 在
step 1.3
,进程 p i p_i pi 的每个接收信道上,也恰好只有一次标记信息。当所有接收信道的状态都已被记录,那么终止自己的算法。
最后,这些记录下的状态,可以在各进程本地记录,也可以发送给某个指定进程来收集。