ARC 2选择正确的一致性语义第 5–9 章
第 8 章 —— CRDT:多节点本地更新与合并
本章目标: 本章完成后,开发者能够用 PN Counter 表达多节点本地增减,并说明合并函数需要满足的代数性质。
学习目标
- 解释 state、update、version vector 和 delta。
- 区分 CRDT 合并与 Register 选 winner。
- 说明交换律、结合律和幂等为何必要。
- 判断哪些业务量不能只靠计数器表达。
前置条件
- 已理解第 6 章的并发候选值。
- 知道请求计数允许短暂差异,但需要避免重复合并导致翻倍。
案例进度
east 节点本地记录 3 次请求,west 节点记录 2 次。网络恢复后两端都应得到 5,而不需要为每次请求争夺中心锁。
合并状态,不挑一个 winner
DsmCrdtCollection 的核心操作是
apply(entity) 与
state()。versionVector()
描述已观察的节点进度,deltaSince(vector) 生成给落后 peer
的状态增量。
counter.apply(new PnCounterUpdate("east-1", metadata, "east", 3, 0));
counter.apply(new PnCounterUpdate("west-1", metadata, "west", 2, 0));
long total = counter.state().value();具体构造参数应以固定源码为准;这里的重点是每个节点贡献独立分量,merge 对每个分量取单调信息,再计算总值。重复收到同一状态不会再次加 3。
三条不可协商的性质
- 交换律:
merge(A, B) = merge(B, A),消息顺序不影响结果。 - 结合律:
merge(merge(A, B), C) = merge(A, merge(B, C)),分批修复不影响结果。 - 幂等:
merge(A, A) = A,重试和重复投递不放大结果。
这三条描述 merge,不等于所有业务操作都可交换。余额扣减、库存约束和「不超过一次」的外部效果不能因为用了 CRDT 就自动成立。
反例与故障注入
实现 merge(left, right) = left.total + right.total
并把总数当作新状态。相同快照被重复发送时,总数不断翻倍。正确 PN Counter
保存每个节点的正/负分量或等价单调结构,而不是反复相加已经聚合的总数。
实验
cd submodule/dsm
mvn -q -pl dsm-integration-test -am \
-Dtest=RuntimeIntegrationTest#oneRuntimeMergesCrdtCounterUpdatesThroughTheRuntimeFacade \
-Dsurefire.failIfNoSpecifiedTests=false test再用 crdt-merge-cases.json
手算顺序交换、分组交换和重复输入三组结果。
实验验收卡
| 字段 | 内容 |
|---|---|
| 运行命令 | 上述 Maven
命令;node --test tests/chapter-assets.test.mjs |
| 输入或故障 | 两个节点分量;顺序变化和重复输入 |
| 可观察结果 | Runtime facade 得到合并计数;三条性质的期望结果完整 |
| 证据等级 | E2:Runtime 内合并测试 + 书籍代数样例 |
| 本实验未证明 | 双节点网络传播、业务去重、精确一次计费或生产容量 |
回顾
- CRDT 合并多方状态,不从候选值中只留一个。
- version vector 描述已观察进度,delta 可针对落后 peer。
- merge 需要可交换、可结合、幂等。
- 技术可合并不代表业务约束可合并。
下一步
第 9 章把 Register、Lease、CRDT 放进同一张业务不变量决策树,完成第一次设计评审。