第 6 章 —— Register 的并发写入与确定性收敛
本章目标: 本章完成后,开发者能够解释并发候选值怎样确定性收敛,并验证自定义 resolver 是否满足交换律。
学习目标
- 区分墙上时钟、HLC 与 lineage total order。
- 说明默认 last-write-wins 的确定性来源。
- 写出 resolver 的交换律检查。
- 识别「最终同值」不能修复的外部副作用。
前置条件
- 已掌握 Register 本地视角和双节点传播。
- 知道网络分区期间两端可能各自接受本地写入。
案例进度
节点 A 把 fulfillment-primary 指向 east,节点 B 同时指向
west。恢复后两端需要选择同一个可见 hint,否则请求会永久分裂。
确定性比“谁更快”重要
默认 ConflictResolver.lastWriteWins() 使用
RegisterLineageOrder 比较 metadata。排序包含 lineage/HLC
信息,并以稳定节点标识打破平局。正确性取决于所有节点对同一对候选值执行相同
total order,不能依赖某台机器对本地时间的判断。
数学上,至少要满足:
resolve(A, B).winner == resolve(B, A).winner
如果 resolver 按第一个参数优先,A 节点可能保留 east,B 节点可能保留 west。消息重复、顺序交换后仍无法收敛。
自定义 resolver 的安全入口
ConflictResolver.verified(delegate) 会分别用
(local, remote) 与 (remote, local) 调用
resolver,并比较获胜实体及 outcome family。不满足交换律时抛出
CONFLICT_RESOLVER_NOT_COMMUTATIVE,适合测试、预发或受控上线。
这项检查只覆盖实际输入对,也会让 resolver 执行两次,因此不能视为形式化证明。resolver 仍需保持纯、确定且无外部副作用。
反例与故障注入
定义
resolve(local, remote) -> localWins(local)。在两个节点分别以自己值为
local 的情况下,结果永久分裂。再定义一个读取当前时间或随机数的
resolver,即使偶尔交换后同值,也无法对重放保持确定。
并发库存扣减即使最终选出同一个 winner,也已经可能向两个客户返回成功。这再次证明冲突裁决只解决副本可见值,不撤销外部承诺。
实验
运行自定义 resolver 双节点测试:
cd submodule/dsm
mvn -q -pl dsm-integration-test -am \
-Dtest=TwoNodeIntegrationTest#customConflictResolversConvergeMergedRegisterValuesAcrossTwoNodes \
-Dsurefire.failIfNoSpecifiedTests=false test然后阅读本章
resolver-cases.json,对四个候选策略判断是否纯、交换、确定。
实验验收卡
| 字段 | 内容 |
|---|---|
| 运行命令 | 上述 Maven
命令;node --test tests/chapter-assets.test.mjs |
| 输入或故障 | 两端同 key 并发候选值;输入顺序反转 |
| 可观察结果 | 合并结果在两个节点相同;错误策略在资产检查中被标注 |
| 证据等级 | E3:受控双节点冲突测试 |
| 本实验未证明 | 所有输入域上的形式化正确性、跨资源事务或外部副作用撤销 |
回顾
- 并发写要求所有节点使用同一个 total order 或可交换合并规则。
- HLC 提供逻辑顺序,不是全局同步时钟。
- 自定义 resolver 需要纯、确定且满足交换律。
- 收敛修复副本,不修复已经对外发生的业务效果。
下一步
第 7 章转向有限期所有权:分片处理关注谁在限定时间内有权操作,而非哪个候选值更新。