文章目录
简介
分布式一致性问题包括数据一致性问题
和事务一致性问题
。在此仅关注数据一致性问题
,数据一致性问题
是因为分布式系统下数据需要复制而导致的。
而数据一致性模型
就是规定在并发的进行数据复制时,每个节点应该看到的视图是怎样的。
不同的 数据一致性模型
对于视图的规定不一样。
假设:
有两个进程 p1
和 p2
,各自发生的事件为 p1_1、p1_2
和 p2_1、p2_2
。
如果不做任何限制,那么这四个事件在全局所有节点看来有 4!
种可能(由于网络波动或者任何原因,这些事件会完全的乱序由别的节点感知到)。
而一致性模型
就是对事件的排列做出限制,让所有节点只可以感知到受限集合内的事件发生顺序。而限制集合内的事件发生顺序(视图)
符合的特性就是 一致性模型
的特性。
在此只介绍几种常见的 一致性模型
,包括:线性一致性
、顺序一致性
、因果一致性
、单调读
、单调写
、读己所写
、读后写
一、线性一致性
最强的一致性,要求分布式系统需要有一个全局时钟,在全局时钟下按顺序发生的事件,在各个节点处理时也必须按全局时钟顺序发生。即各个进程看到的操作顺序和全局时钟下的顺序一致。
简介中的例子,假设全局时钟下发生的顺序为 p1_1、p1_2 、p2_1、p2_2
,则集群内的所有节点看到的顺序都是这个顺序。
如何实现线性一致性? 可以使用 raft 协议,但需要保证读、写操作都是通过 leader 进行执行,并通过决议。如果读操作可以读取从节点,则无法保证每个进程的操作执行顺序和全局时钟下的顺序一致,即读操作可能会读到旧的数据。
二、顺序一致性
满足顺序一致性有两个条件:
- 每个节点对于某节点事件的执行顺序和发生该事件的节点一致
- 对变量的读写要表现的像 FIFO 一样先入先出,即每次读到的都是最近写入的数据,不同节点对于共享变量的修改必须以相同的顺序被所有进程看到
简介中的例子,P1 的顺序为 p1_1 (write x = 1)、p1_2 (read x)
,P2 的顺序为 p2_1 (write x = 2)、p2_2 (read x)
,则集群中节点感知到的事件顺序可以为(但只能为一种,即有四种可能,但只有一种是集群中节点真实看到的序列):
p1_1、p1_2、p2_1、p2_2
,此时 p1 读到 x = 1,p2 读到 x = 2p1_1、p2_1、p1_2、p2_2
,此时 p1、p2 读到的 x = 2p2_1、p1_1、p1_2、p2_2
,此时 p1、p2 读到的 x = 1p2_1、p2_2、p1_1、p1_2
,此时 p1 读到 x = 1,p2 读到 x = 2
如何实现顺序一致性? 可以使用 raft 算法,写操作必须由 leader 发起且经过 raft 状态机,读请求也是访问 leader 但可以不经过 raft 状态机,同一个节点的读请求必须在该节点的写请求执行完毕之后才能得到处理。例如,为每个节点维护一个任务队列,每个队列同一时刻可以取一个任务执行,如果是写写请求,串行执行,如果是读写请求,并行执行。
此时最新的更新,可能其他节点不能立马感知到,比如 p1_1、p1_2、p2_1、p2_2
这个序列:
- 先取
p1_1
和p2_1
,是写写,串行执行。 - 假定先执行
p1_1
,p1_1
执行完毕后,从 p1 的请求队列中取p1_2
, 此时待执行的请求为p2_1
和p1_2
,是读写,并行执行 p1_2
获取到 x = 1 (此时 p1 没有感知到最新的更新,但是它可以看到自己的最新更新)p2_1
执行完毕,更新集群 x = 2- 再取 p2 请求队列中的
p2_2
,读取到 x = 2
此时,每个节点可以看到自己的最新更新,但是可能看不到整个集群的最新更新,但是写写是没有冲突的,且每个节点对于请求的视图是一致的。
三、因果一致性
前面两种一致性都保证了集群中所有节点看到的视图是一致的,而因果一致性无需所有节点看到的视图一致。
依然为简介中的假设,P1 的顺序为 p1_1 (write x = 1)、p1_2 (read x)
,P2 的顺序为 p2_1 (write x = 2)、p2_2 (read x)
,则集群中节点感知到的事件顺序可以为以下任意一种(不再限制所有节点看到的视图一致):
p1_1、p1_2、p2_1、p2_2
,此时 p1 读到 x = 1,p2 读到 x = 2p1_1、p2_1、p1_2、p2_2
,此时 p1、p2 读到的 x = 2p2_1、p1_1、p1_2、p2_2
,此时 p1、p2 读到的 x = 1p2_1、p2_2、p1_1、p1_2
,此时 p1 读到 x = 1,p2 读到 x = 2
线性一致的情况,所有节点只能看到上述四种视图中的一种
,而在因果一致的情况下,由于 p1 和 p2 没有任何因果关系,所有节点可以看到上述四种视图中的任意一种
。因此我们也可以发现,如果不做一些同步的话,p1 和 p2 对于 x 的修改,对于对方都是不可见的。因此要想使用因果一致性保证对于冲突数据的修改不会出错,就必须在每次修改的时候都通知其他节点,从而创造因果关系。
即便每次修改都通知来创造因果,也不能绝对的保证不会出现写冲突,就比如上面的示例,p1 和 p2 同时修改,同时给对方发消息,即便都收到了请求,还是会造成 x 的写冲突。因为对方都表示自己是因,所以还需要额外的添加节点的优先级,比如数字越小,权重越高。这时候,p1和p2互发了消息,但是p1权重高,p2自动认为p1是因,因此会在全局形成一种可能的排序:p1_1、p2_1
,同时还有 p1_1、p1_2
和 p2_1、 p2_2
两种因果关系,所以节点可以看到视图就只有(需要同时满足上述三种因果关系的视图):
p1_1、p1_2、p2_1、p2_2
p1_1、p2_1、p1_2、p2_2
因此我们可以看到改造后的因果一致性虽然不需要保证每个节点的视图一致,但是更加受限,因此也是能保证写写无冲突,只有读可能会读不到最新的数据。
因果一致性的实现可以基于逻辑时钟或者向量时钟来实现。
四、以客户端为中心的一致性
单调读、单调写、读后写、读已之所写,这些都是站在客户端的角度来说的,属于后端集群一致性模型应该对外表现出来的视图应该符合的特性。
因此,这些都是后端集群的一致性模型需要实现的特性,比如如何让采用线性一致性模型的集群对外具有单调读、单调写、读后写等特性。
个人理解:就是在实现后端集群一致性模型的基础上,再对请求做一些限制或者对视图再做一些限制就可以实现上述特性。