ARC 1先看见共享状态第 1–4 章

第 1 章 —— DSM 的职责与适用边界

本章目标: 本章完成后,开发者能够根据数据责任和故障恢复方式,判断一个状态是否适合放入 DSM。

学习目标

  1. 用一句话说明 DSM 解决的协同问题。
  2. 区分业务事实、协同状态和事件历史。
  3. 解释为什么「可以从同伴修复」不是「可以替代数据库」。
  4. 对订单履约案例中的四类数据作出初步存储选择。

前置条件

案例进度

履约系统有多个 API 和 worker 实例。它们需要共享服务路由、订单分片所有权和请求计数,但订单、库存和支付已经有各自的事实系统。

本章只确定责任边界。第 2 章才会创建第一个 Runtime。

DSM 解决的协同问题

先看一个常见但危险的需求:

某团队认为 DSM 能把数据复制到多个节点,因此计划把订单状态迁入 DSM 并移除数据库。

问题不在于 DSM 能不能保存一个 Order 对象,而在于订单状态承担什么责任。订单是需要事务约束、审计历史和确定写入结果的业务事实。副本在网络恢复后得到相同可见值,不能自动补回一次丢失的扣款,也不能撤销已经返回给客户的成功响应。

DSM 适合另一类数据:体量较小、服务运行所需、能够从同伴修复或从权威来源重新生成的协同状态。

按数据责任划分存储边界

订单履约中的数据责任边界

图中有三类责任:

同一个应用可以同时使用三类系统。选择 DSM 不等于排斥数据库或消息系统。

从公开契约看 DSM

当前 DsmRuntime 是承载多个类型化集合的公开门面。它公开三种注册入口:

<E extends DsmEntity<E>> DsmRegister<E> register(CollectionSpec<E> spec);

<E extends LeaseEntity<E>> DsmLeaseRegister<E> leaseRegister(
        LeaseCollectionSpec<E> spec);

<E extends DsmEntity<E>, S extends MergeableState<S>>
DsmCrdtCollection<E, S> crdt(
        CrdtCollectionSpec<E, S> spec,
        StateMerger<E, S> merger);

这三个入口代表三种不同问题,而不是三个容量档位:

问题 首选集合 本书案例
一个 key 当前应该看到哪个逻辑值 Register route-hints
当前谁拥有一项有期限的工作权 Lease shard-owners
多个节点的本地更新怎样合并 CRDT request-counter

第 5—9 章会逐一建立这些语义。当前阶段先识别业务不变量,再选择集合。

第一个可验证增量:给状态分类

本章的资产提供了 8 个候选状态。打开 classification-cases.json,在查看参考答案前逐项填写:

运行检查:

npm test -- --test-name-pattern="chapter 01 classification"

这个命令只验证分类资产和参考答案,不运行 DSM。

拆开来看

1. 业务事实的正确性依赖提交历史

订单从 PENDING 变成 PAID,通常还伴随支付记录、库存变更和审计信息。系统需要回答「哪次事务成功」「谁修改了什么」「失败后如何补偿」。DSM 当前集合 API 不是跨资源事务协议。

因此,订单主记录、库存扣减和支付账务留在事务系统。

2. 协同状态服务于运行决策

Route hint 告诉网关当前访问哪个履约实例。它可能短暂过期,也可以由健康检查重新发布。节点漏掉一次在线 delta 时,repair 可以让副本追上当前状态。

这种状态适合 DSM,但仍要写清陈旧窗口和失败后的业务行为。

3. 事件历史需要传递保证

ChangeStream 可以让工具或应用观察集合变化,但它不是持久消息日志。需要重放全部 OrderPaid 事件时,应使用消息系统、outbox 或事件存储。

4. 大规模分析需要不同的数据模型

DSM 集合以 key 和协同语义为中心,不提供面向大量数据的 join、二级索引和分析查询。高容量日志和指标应该进入相应的数据管道。

反例与故障注入

假设把库存余量放入 Register:

  1. 节点 A 读取余量为 1,并返回扣减成功。
  2. 网络分区期间,节点 B 也读取余量为 1,并返回扣减成功。
  3. 分区恢复后,Register 通过确定性规则只保留一个可见值。

副本最后可以收敛,但两个客户已经收到成功响应。收敛没有撤销第二次销售,也没有产生一条可审计的库存事务。

这个反例说明,DSM 适用性取决于业务响应能否撤销、是否需要事务历史;对象能否序列化只是技术条件之一。

为什么会这样

Register 的责任是让候选状态得到确定的可见结果。它不协调多个业务资源,也不替调用方撤回已经产生的外部效果。

后续章节会解释本地提交、在线 delta 和 repair。现在先保留一句边界:

repair 修复副本状态,不修复已经对外发生的业务承诺。

调整后的状态模型

把「库存余量」改成「库存服务当前首选路由」。重新判断:

  1. 丢失后能否从健康检查重新生成?
  2. 短时间读到旧值时,调用方能否重试或切换?
  3. 是否需要查询完整修改历史?
  4. 多节点并发发布时,能否接受确定性 winner?

如果答案分别是「能、能、不需要、能」,它比库存余量更接近 DSM Register 的适用条件。

常见误区

把「内存」理解成单机缓存

DSM 的共享状态由 Runtime 管理,并可通过 sync 和 repair 在节点间传播。它不是把一个 Map 暴露给所有 JVM,也不是简单的本地缓存封装。

把「多个副本」理解成数据永不丢失

数据安全仍取决于持久化配置、节点拓扑、故障相关性、备份和恢复流程。多个内存副本不能替代数据库备份。

先选集合,再找业务理由

「CRDT 看起来最先进」不是选择依据。没有并发本地更新和可合并状态时,CRDT 只会增加模型与验证成本。

理解检查

  1. 服务发现地址适合 Register 的关键条件是什么?
  2. 为什么一个最终收敛的库存值仍可能造成超卖?
  3. 如果数据需要支持按客户、时间和状态组合查询,DSM 是否应该是主存储?为什么?
  4. 一个 worker 的当前心跳适合 DSM,不代表 worker 完成过的全部任务历史也适合 DSM。两者的责任差异是什么?

实验

将以下状态分成业务事实、协同状态、事件历史和分析数据:

完成后与 参考答案 比较。参考答案不是机械分类:临时功能开关是否适合 DSM,还取决于它是否有外部事实源、陈旧窗口和回滚要求。

实验验收卡

字段 内容
运行命令 npm test -- --test-name-pattern="chapter 01 classification"
输入或故障 8 个候选状态及其责任说明
可观察结果 分类资产结构有效;参考答案覆盖全部候选状态
证据等级 E1:只验证书籍资产与静态责任边界
本实验未证明 DSM Runtime 可以启动、节点可以复制、任何生产容量或可用性结论

回顾

下一步

第 2 章将启动一个最小 DsmRuntime,创建 route-hints Register,并第一次写入和读取路由提示。

证据链接