第四章 分布式系统一致性(上)

首先说下什么是一致性,在单机中,如果一个数据被缓存在多个地方,比如CPU有自己的CACHE,而主内存是所有CPU 共享的。所以数据再不同的缓存里,需要保持一致,这就是一致性问题。那么底层操作系统会有缓存一致性的协议来保证每个CPU的缓存里的值不是过期的。
在分布式系统中,每台机器都会有自己的数据,如果一个数据再多台机器上了,那么有一CLIENT发起了修改,就要同时修改所有有这个数据的机器。但是这样会很慢。而且有些时候会机器挂了怎么办?
但是如果没有一致性,那么有些程序就会出错。
同时也没有最佳的一致性模型,无非就是在编程的便利性和效率之间做取舍。
在分布式系统中,一致性是困难的。相比单机。因为首先数据肯定是多备份的。同时并行的情况下没有共享的时钟。而且机器和网络都会挂。

我们来看个单机的例子,在这个例子里,可以用如下方式使得最多只有一个CPU能进临界区。


10803273-7d9ac3963e684b10.png
image.png

但是再分布式的情况下,因为网络的延迟,这个写法就不WORK了。如下图


10803273-c731f04dd70f5a86.png
image.png

第一个,也是最强的一致性模型,我们叫它STRICT CONSISTENCY。他是让每个操作严格的按照全局的时钟来排序。那么所有CPU都会按照这个顺序看到每个操作。在这个约束下,我们可以证明,没有2个CPU可以同时进临界区。


10803273-d03d09b1afffd7fd.png
image.png

但是STRICT CONSISTENCY 在分布式情况下 是不现实的。因为时钟不准,没有一个全局的GLOBAL WALL CLOCK。 即使不同机器可以去同步时间,但是网络是有延迟的,在秒级可以同步精确,但是相对于一个CPU的circle,会很不准。那么引入一个新的概念,SEQUENTIAL CONSISTENCY,他是最接近于STRICT CONSISTENCY的。
他的思想是用一个 操作的全序(total order)来代替全局时钟。他能保证所有的机器可以看到所有操作根据相同的TOTAL ORDER来排序的。
下图就不是SEQUENTIAL CONSISTENCY,因为没有一种操作的全序 可以使得下面的情况成立。


10803273-1de07b1488dd2b64.png
image.png

下面我们来看下SEQUENTIAL CONSISTENCY 是如何实现的


10803273-178ff05eec37faeb.png
image.png
10803273-400c02138dcb68d7.png
image.png

IVY 的思想是,提供一个共享MEMORY 的系统。同时每个节点的本地内存会持有一组PAGE的子集。如果他们要读的数据不在LOCAL MEMORY里,他就会向共享MEMORY 的系统申请。每一个PAGE是有一个OWNER,这个OWNER就是最后写这个PAGE的NODE。
下面有3个挑战:


10803273-8ac58458ee447e64.png
image.png

我们来看一个例子,一开始PAGE 1的OWNER 是NODE A,这个时候C也要读PAGE 1,并且发现本地内存没有。他会向NODE-MASTER要。NODE MASTER 看,这个PAGE 是A的,C是读请求,于是把C加到COPY SET里,然后通知A 让他把PAGE 1发给C。


10803273-55dbbfc7134ae77a.png
image.png

这个时候B发起了一个写请求,NODE-M首先会发送INVALIDATE PAGE 1的消息给所有COPY SET里的NODE。随后M清空了PAGE 1 的COPYSET,再告诉A ,你把OWNER权转交给B。


10803273-3917df6196816d47.png
image.png

基于IVY,你就能确保 读能看到最新的写。
下面的例子又成立了。


10803273-5e30a26c803cd65a.png
image.png

SC 的缺点就是慢,任何读写都需要网络传输,需要同步。为了解决这个问题。
就有了一个叫RELEASE CONSISTENCY 的model

扫描二维码关注公众号,回复: 5249569 查看本文章

10803273-448be020fec1d450.png
image.png

对RELEASE CONSISTENCY来说,他引入了锁的概念,打破了每个读写的变量都要同步。只有在拿锁和放锁的时候需要同步。一把锁可以对很多个变量保护起来。也就是很多变量一把锁。


10803273-963d3976c14fc7c6.png
image.png

那么有2种实现方案,一种是在放锁的时候告诉我的值变成了NEW VALUE,一种是在放锁的时候偶告诉别的NODE,你的X过期了,你需要的时候要重新去拿。
那么出于更少的带宽考虑,发送INVALIDATE是更好的。因为他们可能用不到最新的值,你这样传输新的值过去可能会让费。

为了进一步节约网络开销的时间。还有一种LAZY 的模式。也就是说在放锁的时候什么都不做。只有其他节点去拿锁再通知他们。我们来看下比较。


10803273-a24a7a36d8599fd7.png
image.png

但是这种基于页的缓存,都会发生缓存伪共享的问题。2个THEAD 对同一个PAGE的不同变量在写,希望在交互的时候不要把整个页换来换去。解决方案类似于打PATCH.旧版本和新版本只发送DIFF的量,可以减少网络的开销。


10803273-302c38a0b2b18cb7.png
image.png

我们看看怎么来实现,一个PAGE里放了X 的 ARRAY 和一个Y 的ARRAY。NODE 0 只修改X, NODE 1 只修改Y。
然后NODE 0,修改的时候会记一个DIFF(通过和TWIN比)。进行同步的时候,把DIFF拉过来,再一个个UPDATE上去。


10803273-7658d8c9ad28efa3.png
image.png

INTERVAL 对于SEQUENCY CONSISTENCY来说,每一个操作之间都有ORDER。
而RELEASE CONSISTENCY,只有锁和锁之间有ORDER。
INTERVAL A 在 INTERVAL B 之前,代表A里的所有操作 都在 B的所有操作之前。

如何来构建INTERVAL顺序呢?
能不能设个时间,每个人都有个时间,交换的时候把时间告诉你,你就知道谁先谁后了。
方法1.


10803273-edc1a39ccf8f8835.png
image.png

Lamport's Logical Clock
单机系统容易给发生的所有事件定义一个全局顺序(total order),但是分布式系统没有全局时钟,很难给所有事件定义一个全局顺序。所以,Lamport定义了一种偏序关系,happens-before.记作 ->

a->b意味着所有的进程都agree事件a发生在事件b之前。

在三种情况下,可以很容易的得到这个关系:

如果事件a和事件b是同一个进程中的并且事件a发生在事件b前面,那么a->b

如果进程A发送一条消息m给进程B,a代表进程A发送消息m的事件,b代表进程B接收消息m的事件,那么a->b(由于消息的传递需要时间)
->满足传递性,如果a->b AND b->c => a->c

Lamport's Logical Clock算法如下:

每个机器本地维护一个logical clock LCi
每个机器本地每发生一个事件设置LCi = LCi + 1,并且把结果作为这个事件的logical clock。
当机器i给机器j发送消息m时,把LCi存在消息里。
当机器j收到消息m时候,LCj = max(LCj, m timestamp)+1,结果值作为收到消息m这个事件的时间戳。
这个算法能够保证a->b,那么a事件的logical clock比b事件的logical clock小。反过来,通过只比较两个事件的logical clock不能得到a和b的先后。

每个NODE 都有个COUNTER。每做一个操作COUNTER+1,自己这里就保证了顺序。当需要同步,A把COUNTER发过去,然后对面NODE B拿自己的COUNTER 和收到的A 的COUNTER 取MAX后 + 1,。这样就能确保B同步后的操作都会在A同步前的操作的后面。

这个的限制就是2个事件拿过来,用COUNTER 比,一个大一个小 并不知道这2个事件谁发生在前,谁发生在后。只有反过来是知道的。如果A发生在B后,他的COUNTER一定比B大。

最大的问题是counter只有1个。

方法2.
Vector Clock
每个机器维护一个向量VC,也就是Vector Clock,这个向量VC有如下属性:

VCi[i] 是到目前为止机器i上发生的事件的个数

VCi[k] 是机器i知道的机器k发生的事件的个数(即机器i对机器j的知识)

每个机器都有一个向量(Vector),每个向量中的元素都是一个logical clock,所以取名为Vector Clock。

通过如下算法更新Vector Clock

机器i本地发生一个事件时将VCi[i]加1
机器i给机器j发送消息m时,将整个VCi存在消息内
机器j收到消息m时,VCj[k]=max(VCj[k],VCi[k]),同时,VCj[j]+1
可以看出,Vector Clock是一种maintain因果关系(causality)的一种手段,Vector Clock在机器之间传递达到给对方传递自己已有的关于其他机器知识的目的。

vector timestamp 维护很多个LAMPORT TIMESTAMP。用了一个数组。然后改了哪个变量,就把对应的数组加一下。然后同步的时候就把对面的整个数组取过来,每个变量的COUNTER 都取MAX。这样正着也是可以比较的,如果一个VECTOR TIMESTAMP里的每个变量都比后一个大,那么这个操作一定发生在另外一个的后面。

10803273-7ffd5ee036738178.png
image.png

有了TIMESTAMP之后再回过来重新看WRITE NOTICE。这个时候可以根据对面发过来的TIME STAMP,知道对方的这个数据更新到哪个位置。你只要这个时间后面的WRITE NOTICE发过去就好。不用全部的W N都发了。


10803273-c406a852144ae18b.png
image.png

猜你喜欢

转载自blog.csdn.net/weixin_33968104/article/details/86892196