← 揭开黑盒:理解系统的调查课堂

第一章 · 系统的承诺:保存、状态与通信 / 第四单元 · 现代实现与技术路线(拓展)

现代状态:从轮询,到流与协作

两个人看同一张任务表,一个人勾选“完成”,另一个人的画面什么时候变化?第二单元让我们学会拆开对象、时间与显示。现在换到实现者的位置:我们要设计这条更新路线。

预计用时:40 分钟。带上上一节为多人记账画出的系统图,在读取端继续补画。

刷新、轮询与推送,各自在承诺什么

最直接的实现是让用户刷新。若希望画面自己更新,可以每隔几秒请求一次。轮询把问题变成:多久查一次,什么条件下停止,前一次尚未完成怎么办。它容易观察,也可能产生很多没有新内容的请求。

另一条路线是让连接保持可用,服务在有变化时发送事件。SSE 适合从服务到浏览器的事件流;WebSocket 提供双向消息通道。二者提供的是传输能力,页面依然要决定收到事件后更新什么,以及连接断开期间发生的事情如何补上。

实时性由整条链决定:提交发生、事件被生成、排队、传输、页面采用、绘制。换成推送并不自动消除这些间隔。

留住一个能区分解释的实验

有两种解释:“只要通道有序,页面就不会退回旧状态”;“通道顺序之外,还要协调初始读取与后续事件”。用第一章状态实验的旧快照模拟后者:先捕获 v3,之后完成 v4 更新并显示,最后放行 v3。

在一个真实推送应用里,初始 HTTP 读取与事件通道可能是两条路径。即使每条路径内部有序,旧读取仍可能晚到。实验支持的是这种顺序风险;要断言某个产品存在该问题,还要检查它的实际启动协议。

常见解决办法包括给状态带版本、先订阅再取得有边界的快照,或让服务提供“从某一事件位置继续”的机制。关键是定义快照与事件之间有没有缺口、有没有重复,不能只分别证明两个接口都返回成功。

缓存为什么依然存在

每次都读数据库,可以减少某些缓存一致性问题,但数据读取仍然要耗时。系统可能在浏览器、服务内存、代理或 CDN 上保存可复用结果。缓存键回答“同一份结果是什么”;有效期或失效规则回答“什么时候还适用”。

例如按用户与筛选条件统计任务数,若缓存键漏掉用户,刷新得再勤也可能取到错误范围。若键正确但失效晚到,结果可能只是过时。第一章的三条调查线在这里变成设计检查项。

缓存还会把一次更新拆成多个动作:改数据库、使旧缓存失效、让下次读取重建。需要分析动作间中断的情况,并明确允许多长时间的不一致。金融余额与文章阅读数通常不会采用完全相同的容忍标准。

当客户端先画出结果

乐观更新会先把任务画成“已完成”,同时向服务发送请求。它缩短的是用户等待反馈的时间。服务拒绝修改、请求未获确认、别的设备同时修改时,页面要回滚、重读或呈现冲突。

把“本地待确认”和“服务已确认”分别存下来,就能解释用户看到的画面。第一章保存实验中的 pending 在这里再次出现,但需要补上读取版本和冲突策略。

多人协作继续增加一个问题:两个有效修改怎样合并?最后写入者覆盖适合某些设置项,却可能丢掉文章中的编辑。操作转换和 CRDT 为特定的数据类型与操作提供协作路线,各自依赖明确的模型。一个计数器的合并规则不能直接用于任意文档或业务约束。

一张路线表

使用情境可以先考虑的形式必须补充的行为
低频变化的查询页手动刷新或定时轮询请求取消、失败提示、版本采用
服务持续发布进度SSE重连、事件编号、遗漏恢复
双方持续交换动作WebSocket应用消息语义、心跳与重连、流量限制
网络不稳定的编辑器本地队列加后台同步待确认状态、重复、冲突与恢复
多人同时编辑同一对象适合数据模型的协作算法身份、顺序、合并规则及业务校验

继续阅读可查 MDN 的 SSE 使用说明。协议工具如何使用,与业务状态如何恢复,要分别阅读。

交付一条可恢复的更新路线

在你的多人记账方案上写出:首次进入、收到新事件、断开十秒后重连、提交后未得到确认时,各自读取或保存什么。再选一个会迫使你改变方案的条件,例如允许离线编辑。

至少包含一个反例:已经显示 v4,v3 晚到时会怎样。停止条件是你能解释当前用途下的显示承诺;不必在这一节实现所有协作算法。

下一节 现代通信:从收发器到分层网络 将打开这条更新路线所依靠的通道。