假设有如下的应用场景:
我们有多台服务器节点,客户端每次请求时候可能会改变服务器上数据,但这些请求可能由不同的服务器节点进行处理,这样在每台服务器上都有持有自己的数据修改。
为了使最后所有服务器的数据保持一致性,我们需要对每台服务器上的数据保持追踪和版本管理,这在dynamo中使用vector clock来进行处理。
版本信息,我们用这样的结构进行表示(Node, Version), 例如A节点,版本1数据:(A, 1),
对于每个数据,我们都额外附加一个版本信息列表来进行跟踪。
我们首先要明确统一数据不同版本信息间的descendant的概念,
我们大概可以用这样的伪码来判断descendant关系。
//A, B列表中的每一个元素都为(Node, Tag)此种结构 A = [...] B = [...] //判断A是否是B的后代 bool descends(A, B) //若B为空列表,则表示A肯定是B的后代。 if B == [] begin return true end else begin //从B列表中取出第一个元素,元素结构为(Node, Tag) C = dequeue(B) //从A列表中查看是否包含取出的B头元素中与Node相同的元素 //find函数若发现同样Node的元素存在,则进行返回,否则返回null D = find(A, C) //如果A中不包含此元素,则A不为B后代 if D == null return false else //如果B中包含同Node的元素的tag值>= A取出的头元素tag值,则进行递归判断 //tag函数是取出(Node, Tag)中的Tag值 return tag(A) >= tag(C) && descends(B) endif end endif
比如我们有两个版本信息列表
版本一:[(A,1)] 版本二:[(A,1), (B,1)]
我们可以说版本二是版本一的descendant。
而以下两个范例则不是:
版本一:[(C,1)] 版本二:[(A,1), (B,1)]
版本一:[(A,1)] 版本二:[(A,2), (B,1)]
阐述完上面的概念,现在我们假设有三台服务器节点 A, B, C,初始时,每台服务器节点上都没有任何数据。
步骤一:有客户端向A节点发出请求初始化某条数据为100, A将数据同步到B, C节点后,则有:
A节点 data: 100 version: [(A, 1)]
B节点 data: 100 version: [(A, 1)]
C节点 data: 100 version: [(A, 1)]
步骤二:有客户端向B节点发出请求更新数据为200,尚未来的及同步到其他两个节点:
A节点 data: 100 version: [(A, 1)]
B节点 data: 200 version: [(A, 1), (B, 1)]
C节点 data: 100 version: [(A, 1)]
步骤三:有客户端向C节点发出请求更新数据为300,尚未来的及同步到其他两个节点:
A节点 data: 100 version: [(A, 1)]
B节点 data: 200 version: [(A, 1), (B, 1)]
C节点 data: 300 version: [(A, 1), (C, 1)]
这时候,我们可以看到C节点的数据版本是A节点的descendant,可以进行merge,但是B节点却不是,最后经过一段时间服务间同步之后,最后的情况是:
A节点 data: 300 version: [(A, 1), (C, 1)]
B节点 data: 200 version: [(A, 1), (B, 1)]
C节点 data: 300 version: [(A, 1), (C, 1)]
我们可以看到三个节点此时有两个冲突而无法merge的版本[(A,1), (B,1)], [(A,1), (C,1)]。
接下来我们要对这些冲突进行解决,我们可以采用两个方式,一种在服务器间同步时,按照一定机制自动来决定版本选择,另外一种,把这个主动权交给客户端,当客户端读取数据时,来进行处理。
具体方式下一篇来说= =