Convergent CRDTs
Convergent CRDTs (or CvRDTs) are replicated data structures that, when merged, converge towards a value.
CvRDTs 有两个基本的组成部分:
- state
- merge function
state 就是 join semi-lattice 中的元素,不同的 state 之间可能存在着 binary relation,并且不同的 state 可以进行 merge 操作,这个 merge 其实就是 join semi-lattice 中,针对其内部元素所符合的 Order 的 join,其结果是两个 state 的 least uppper bound。
System
我们将当前状态组成的集合称为 system,注意,system 不一定满足 join semi-lattice 的条件,我们将所有可能状态的集合记为 background set,这个集合是一个虚设的,并不一定实际存在着,而 system 是目前所得到的状态的集合,system 是 background set 的子集。
Value
value 表示一个 join semi-lattice 中,将所有元素都进行 join 操作后所得到的值。也就是这个 join semi-lattice 的 upper bound。
如果我们从 system 中随机挑选两个元素进行join操作,然后将得到的结果放入 set 中,并不断重复上述操作,那么,我们最终会得到 value 值并将其放入 system 中。这时 system 也就成为了 join semi-lattice。
如上图所示,通过不断地合并 system 中的元素,最终得到了 Earth。
The key point is that the states in a system converge toward the value of the system as we merge them. Imagine choosing pairs of states at random from a system and merging them, each time adding the result of the merge to the system. This process should eventually add the value to the system. Now, each merge acts as a join.
Join 的特性保证了最终结果的收敛一致性:
- 结合律、交换律保证了顺序无关
- 幂等性保证了无论一个状态被重复join多少次,其结果都是一样的
Implement
CvRDTs的作用就是将不同节点的状态进行同步,并让每个节点都得到一致的最终状态,如下图所示,三个不同节点通过同步信息,最终都得到了 system 的 value
下面让我们具体实现一个CRDT-Counter,它主要有两个接口:
- increment 用于+1的方法
- value 用于获取 system 的 value,这个 value 代表着所有节点调用的 increment 的次数的总和
假设系统中有三个节点 x, y, z,分别调用了 increment 1、2、3 次,那么 value 值应当为 6。
分析上述需求,求取value值需要知道三个节点各自本地数据的大小,因此在完成同步后,每个节点上都应当保存有所有节点各自调用 increment 的次数。因此,system 中的元素,也就是状态可以表示为一个向量 [x,y,z] ,状态之间的 join 操作可以设定为对每个向量进行合并,如下图所示:
节点之间的同步过程如下:
每个节点在获取到其它节点的向量后,分别对比向量的每一个分量并取较大的值组成新的向量作为自己的状态,在经过有限次的同步后,最终三个节点达到一致的状态。
使用python代码可以实现如下:
class GCounter:
def __init__(self, nodeId, state_list):
self.nodeId = nodeId
self.state_list = state_list
def value(self):
return sum(self.state_list)
def increment(self):
self.state_list[self.nodeId] += 1
def merge(self, incoming):
for idx in range(0, len(self.state_list)):
self.state_list[idx] = max(self.state_list[idx],
incoming.state_list[idx])