瞬时同步的主要问题
解决方案:
引入多CPU,每个CPU都有自己的时钟,则会产生时钟偏移(clock skew)。
假设每台机器都有一个每秒产生\(H\)次中断的计时器。当计时器产生中断时,中断处理程序将软件时钟加1,软件时钟记录从过去某一约定时间开始的滴答/中断数,记为\(C\)。当UTC时间为\(t\)时,\(P\)上时钟值为\(C_p(t)\)。最理想情况是对于所有的\(p\)和\(t\)都有\(C_p(t)=t\),即\(\mathrm{d}C/\mathrm{d}t\)理想值为1。
若存在某一常数\(\rho\)使得
\[1-\rho\leq\frac{\mathrm{d} C}{\mathrm{d} t}\leq 1+\rho\]则可以说计时器在规定范围内工作,\(\rho\)由厂商规定,称为最大偏移率(maximum drift rate)。
若两个时钟以相反方向偏离UTC,则在其同步后的\(\Delta t\)绝对时间内,最大可能偏差可能达到\(\delta=2\rho\Delta t\)。
让客户A与时间服务器B联系
B | 接收\(T_2\) | 响应\(T_3\) | ||
A | 请求\(T_1\) | 到达\(T_4\) |
进而A可以计算出与B的时间偏差(B的当前时间-A的当前时间)
\[\theta=\left(T_3+\frac{(T_2-T_1)+(T_4-T_3)}{2}\right)-T_4=\frac{(T_2-T_1)+(T_3-T_4)}{2}\]但时间是不能往后退的,只能逐步来调整。假设计时器每秒产生100个中断,正常情况每个中断添加10毫秒,通过每个中断只添加9毫秒可以将时间往后调,每个中断添加11毫秒则往前调。
网络时间协议(Network Time Protocol, NTP)在服务器之间创建了两条连接,延迟计算如下
\[\delta=\frac{(T_2-T_1)+(T_4-T_3)}{2}\]可以将每个\((\theta,\delta)\)对缓存起来,最后以\(\delta\)的最小值作为两个服务器之间的延时,从而相应的\(\delta\)是最可靠的偏差。
在NTP中,时间服务器是被动的;而Berkeley UNIX则采用主动的时间服务器(时钟守护程序),计算时间均值,并告诉其他机器应该如何根据当前时间进行调整。
(a)时间守护程序询问其他机器时钟值,(b)其他机器做出应答,(c)时间守护程序告诉每台机器应该如何调整时钟。
注意在Berkeley算法中并没有采用UTC全局时钟。
参考广播同步化(Reference Broadcast Synchronization, RBS)同样没有假设具有精确时间的结点,不为所有结点提供UTC时间,而只是在内部实现时钟同步化。
前面的协议都是遵循双向协议(发送器和接收器实现同步),而RBS只是使接收器同步化,发送器则位于该循环外。
当一个结点广播一个参考消息\(m\)时,每个结点p只是记录它收到\(m\)的时间\(T_{p,m}\),这里的时间是从\(p\)的本地时钟读取的。忽略时钟偏差,两个结点p,q就可以交换各自的传送时间。
\[deviation[p,q]=\frac{\sum_{k=1}^{M}(T_{p,k}-T_{q,k})}{M}\]其中\(M\)为发送的参考消息总数。(实际上就是蒙特卡洛) 改进的算法则用标准线性回归
\[deviation[p,q](t)=\alpha t+\beta\]来计算偏差。
前面都假设时间同步化与实际时间相关,但实际上在很多应用中,只要所有机器具有相同时间就足够了,这个时间不一定要与实际时间相同。
定义“先发生”(happens-before)的关系\(a\to b\),如果\(x\to y\)不为真,\(y\to x\)也不为真,则这两个事件称为并发的。这样就可以给每一个事件\(a\),分配一个所有进程都认可的时间值\(C(a)\)
由于“先发生”关系,故时钟一定不会往后流,因此当从P3发消息回P2和P1时会促使它们的时钟至少往前进1格。
具体实施则每个时钟\(P_i\)维护一个局部计数器\(C_i\),按照以下步骤更新:
在具体实施中则采用中间件调整本地时钟
全序多播(totally ordered multicast)即一次将所有的消息以同样的顺序传送给每个接收者的多播操作。
因为每个进程都有相同的队列,所以在任何地方,所有消息都以同样的顺序交付。
利用Lamport逻辑时钟解决
Lamport时间戳不能捕获因果关系(causality),因果关系要通过向量时钟来捕获。
如果对于事件 \(b\),有 \(VC(a)<VC(b)\) 那么认为事件 \(a\) 为因, \(b\) 为果。
向量时钟通过令每个进程 \(P_i\) 维护一个向量 \(VC_i\) 来完成:
实际上就是对于每一个进程i维护一个所有进程状况的数组/向量,每一个元素为一个Lamport逻辑时钟。
具体更新步骤:
称 \(b\) 因果依赖于 \(a\) 若消息时间戳 \(ts(a) < ts(b)\)(严格不等式),且
因果有序多播(causally-ordered)比全序多播更弱,当两个消息互相没有任何关系时,则不用关心以何种顺序传送给应用程序。 并且假设只在发送和接收消息时才调整时钟。
调整:仅当 \(P_i\) 发送消息时才增加 \(VC_i\) , \(P_j\) 接收到消息才调整 \(VC_j\)
现在假设进程 \(P_j\) 从进程 \(P_i\) 接收到一个向量时间戳为 \(ts(m)\) 的消息 \(m\) ,则该消息传送到应用层将会被延时,直到满足以下条件:
第一个条件表示消息 \(m\) 是进程 \(P_j\) 希望从 \(P_i\) 接收的下一条消息,而第二个条件则表示当进程 \(P_i\) 接收发送消息 \(m\) 时, \(P_j\) 已经收到所有来自进程 \(P_i\) 的消息。
基于令牌的算法、基于许可的算法
选举一个进程作为协作者(如运行在最大网络地址号机器上的进程),然后访问共享资源的方式即
缺点:单点失效
利用分布式哈希表(DHT),假定每种资源有 \(N\) 个副本,每一个副本都有自己的协作者用于控制访问。进程只需获得 \(m>N/2\) 个协作者的大多数投票就可以访问资源。
Ricart和Agrawala(1981)对Lamport(1978)的算法进行了改进,要求系统中所有事件都完全排序。
如果令牌丢失了,则需要重新生成令牌,但很难确定是丢失了还是仍在使用。
算法 | 每次进/出需要消息数 | 进入前延迟(按消息数) | 问题 |
集中式 | 3 | 2 | 协作者崩溃 |
非集中式 | 3mk | 2m | 会发生饥饿,效率低 |
分布式 | 2(n-1) | 2(n-1) | 任何进程崩溃 |
令牌环 | \(1\thicksim\infty\) | \(0\thicksim n-1\) | 令牌丢失,进程崩溃 |
某些算法需要一些进程作为协作者,问题是如何动态选择这个特殊的进程。在很多系统中,协作者手动选取,这会导致集中化的单点失效问题。
基本假设:
当任何进程发现协作者不再响应请求时,就发起一次选举。进程P按如下过程主持一次选举:
直到最大编号可响应进程,向所有进程发送COORDINATOR消息,成为新的协作者。
传统选举算法假设消息传送是可靠的,网络拓扑结构也是不改变的,但在无线网络中就不能这样。
下图展现了选举过程,其中每个结点为资源容量,越大越好(会成为协调者)。
从节点a开始发起选举,然后每个结点向父节点报告具有最佳容量的结点,最后再由a将协调者信息广播出去。