把这个应用当作。学习行为会写入以及一个只追加的(outbox);重新联网时,设备会,并。冲突用一条全局规则来解决,而是根据来解决。
心智模型:把它想象成 git——每台设备在离线时于本地"commit",而同步是一次合并(merge),而不是覆盖。
Device (offline) Server = source of truth
┌──────────────────┐ on reconnect ┌───────────────────────────┐
│ Local DB (SQLite)│ push ops (delta) │ Sync API /sync │
│ + Op log (outbox)│ ─────────────────▶ │ • idempotent by op.id │
│ cursor = lastSeen│ ◀───────────────── │ • merge by semantics │
└──────────────────┘ pull changes>cursor└───────────────────────────┘
Progress DB (Postgres)
手机时钟不可靠,而且两台设备可以在离线时编辑同一条记录,因此**"最后写入者胜出"(LWW)会悄无声息地丢失数据**。要按语义来解决:
// op 带有一个 id -> 幂等地应用(重试不会重复计数)。clientTs 不可信。
function mergeProgress(server, op) {
return {
// progress 只会增加 -> max:迟到的旧 op 永远不会把它往回拖
progress: Math.max(server.progress ?? 0, op.progress),
// studiedMs 是累加计数器 -> 加上增量,不要覆盖
studiedMs: (server.studiedMs ?? 0) + op.studiedMs,
completedAt: server.completedAt ?? (op.progress >= 1 ? op.clientTs : null),
};
}
在这里,纯 LWW 会让一台迟到同步、携带 progress: 0.3 的过期设备覆盖服务端的 0.9 → 进度丢失。取 max 并对 studiedMs 求和,使同步与顺序无关(order-independent)——当数百万台设备带着有偏差的时钟同步时,这样才安全。
面试官在考察你是否知道客户端时钟不可信(所以不能用纯时间戳 LWW)、你是否让同步具备幂等性(idempotent)(op ids),以及你是否按数据含义来合并。一个弱答案:"用时间戳,最后写入者胜出。"一个强答案:副本 + op log + 幂等性 + 按字段合并 + 服务端作为 source of truth——并且明确指出 LWW 的数据丢失故障。
LWW 便宜但会丢数据;语义合并需要针对每个字段思考,但不会丢失任何东西。只有当你需要真正的协同编辑(笔记、文档)时,才动用完整的 CRDT/OT——它成本很高,所以不要事事都用它。
一个包含详细解答的 IT 面试题库——从初级到高级。
捐赠