宝典目录
- CRDT宝典(一): 引言
- CRDT宝典(二): 基本概念
- CRDT宝典(三): GCounter
- CRDT宝典(四): PNCounter
- CRDT宝典(五): GSet
- CRDT宝典(六): PNSet
- CRDT宝典(七): VClock
- CRDT宝典(八): LLW-Register
- CRDT宝典(九): ORSet
- CRDT宝典(十): AWORSet
背景
在分布式系统中,有一个值、以及一个时间戳,每个节点都能更新这个值,同时更新这个时间戳。
请设计一个CRDT(Conflict-free Replicated Data Type)来实现一个满足最终一致性的分布式系统。
为了减少要理解的概念,下文描述的CRDT同时有两层意思
- 无冲突的数据类型,即类型
- 一个CRDT实例,即实例
思维链
flowchart
A["1. CRDT可以应用在不同的节点中"]
A --> B["2. CRDT可以表示该值的最终值、以及时间戳的最终值"]
B --> C["3. CRDT可以让任意节点更新这个值和时间戳"]
C --> D["4. 可以合并多个节点的CRDT,且无论合并的顺序是怎么样的,其结果都一致,\n即Merge(A.CRDT, B.CRDT, C.CRDT) = Merge(B.CRDT, C.CRDT, A.CRDT) = a new CRDT"]
D --> E["5. 多次合并同一个节点的CRDT,其结果都等于合并一次,\n即Merge(A.CRDT, B.CRDT, B.CRDT) = Merge(A.CRDT, B.CRDT) = a new CRDT"]
特别说明:第二点的最终值代表应用完所有节点对该值的更改后的最终值,时间戳同样。
实现
这个CRDT如下,我们暂时给它起名LWWRegister(稍后解释其为什么叫LWWRegister)
type LWWRegister<T> = {
value: T;
timestamp: number;
};
因为是分布式系统,且支持弱网环境,所以这个LWWRegister在不同节点里,都不一样。
比如
// 节点A的LWWRegister
A.LWWRegister = {
value: "value1",
timestamp: 1000,
}
// 节点B的LWWRegister
B.LWWRegister = {
value: "value2",
timestamp: 1500,
}
// 节点C的LWWRegister
C.LWWRegister = {
value: "value3",
timestamp: 1200,
}
各自节点可以随时更新自己的值和时间戳,比如在节点A中,更新LWWRegister:
const update = (node, newValue: string, newTimestamp: number) => {
node.LWWRegister = {
value: newValue,
timestamp: newTimestamp,
};
};
合并不同节点的副本,比如merge(A.LWWRegister, B.LWWRegister),需要根据时间戳选择最新的值,步骤如下
flowchart LR
B["1.创建一个LWWRegister\n 2. 比较A.LWWRegister\n和B.LWWRegister的timestamp"]
B1["2.1 如果A.LWWRegister.timestamp > B.LWWRegister.timestamp,\n则选A.LWWRegister的值和timestamp"]
B2["2.2 如果B.LWWRegister.timestamp > A.LWWRegister.timestamp,\n则选B.LWWRegister的值和timestamp"]
B3["2.3 如果A.LWWRegister.timestamp === B.LWWRegister.timestamp,\n则按照预定义的规则(例如节点ID大小)选择值"]
C["3. 返回选择后的LWWRegister"]
B --> B1
B --> B2
B --> B3
B1 --> C
B2 --> C
B3 --> C
代码如下:
const merge = (regA: LWWRegister<string>, regB: LWWRegister<string>): LWWRegister<string> => {
if (regA.timestamp > regB.timestamp) {
return regA;
} else if (regB.timestamp > regA.timestamp) {
return regB;
} else {
// 当时间戳相同时,使用节点ID进行比较,这里假设A的ID小于B
return /* 根据节点ID决定返回哪个 */;
}
}
const mergedRegister = merge(A.LWWRegister, B.LWWRegister)
你按随意的顺序合并A.LWWRegister,B.LWWRegister,C.LWWRegister,其结果都一致,即最终一致性。
总结
因为这个CRDT通过最后写入优先来解决冲突,,所以叫LWWRegister(Last-Write-Wins Register)