两个人看同一张任务表,一个人勾选“完成”,另一个人的画面什么时候变化?第二单元让我们学会拆开对象、时间与显示。现在换到实现者的位置:我们要设计这条更新路线。
预计用时:40 分钟。带上上一节为多人记账画出的系统图,在读取端继续补画。
最直接的实现是让用户刷新。若希望画面自己更新,可以每隔几秒请求一次。轮询把问题变成:多久查一次,什么条件下停止,前一次尚未完成怎么办。它容易观察,也可能产生很多没有新内容的请求。
另一条路线是让连接保持可用,服务在有变化时发送事件。SSE 适合从服务到浏览器的事件流;WebSocket 提供双向消息通道。二者提供的是传输能力,页面依然要决定收到事件后更新什么,以及连接断开期间发生的事情如何补上。
实时性由整条链决定:提交发生、事件被生成、排队、传输、页面采用、绘制。换成推送并不自动消除这些间隔。
有两种解释:“只要通道有序,页面就不会退回旧状态”;“通道顺序之外,还要协调初始读取与后续事件”。用第一章状态实验的旧快照模拟后者:先捕获 v3,之后完成 v4 更新并显示,最后放行 v3。
在一个真实推送应用里,初始 HTTP 读取与事件通道可能是两条路径。即使每条路径内部有序,旧读取仍可能晚到。实验支持的是这种顺序风险;要断言某个产品存在该问题,还要检查它的实际启动协议。
常见解决办法包括给状态带版本、先订阅再取得有边界的快照,或让服务提供“从某一事件位置继续”的机制。关键是定义快照与事件之间有没有缺口、有没有重复,不能只分别证明两个接口都返回成功。
每次都读数据库,可以减少某些缓存一致性问题,但数据读取仍然要耗时。系统可能在浏览器、服务内存、代理或 CDN 上保存可复用结果。缓存键回答“同一份结果是什么”;有效期或失效规则回答“什么时候还适用”。
例如按用户与筛选条件统计任务数,若缓存键漏掉用户,刷新得再勤也可能取到错误范围。若键正确但失效晚到,结果可能只是过时。第一章的三条调查线在这里变成设计检查项。
缓存还会把一次更新拆成多个动作:改数据库、使旧缓存失效、让下次读取重建。需要分析动作间中断的情况,并明确允许多长时间的不一致。金融余额与文章阅读数通常不会采用完全相同的容忍标准。
乐观更新会先把任务画成“已完成”,同时向服务发送请求。它缩短的是用户等待反馈的时间。服务拒绝修改、请求未获确认、别的设备同时修改时,页面要回滚、重读或呈现冲突。
把“本地待确认”和“服务已确认”分别存下来,就能解释用户看到的画面。第一章保存实验中的 pending 在这里再次出现,但需要补上读取版本和冲突策略。
多人协作继续增加一个问题:两个有效修改怎样合并?最后写入者覆盖适合某些设置项,却可能丢掉文章中的编辑。操作转换和 CRDT 为特定的数据类型与操作提供协作路线,各自依赖明确的模型。一个计数器的合并规则不能直接用于任意文档或业务约束。
| 使用情境 | 可以先考虑的形式 | 必须补充的行为 |
|---|---|---|
| 低频变化的查询页 | 手动刷新或定时轮询 | 请求取消、失败提示、版本采用 |
| 服务持续发布进度 | SSE | 重连、事件编号、遗漏恢复 |
| 双方持续交换动作 | WebSocket | 应用消息语义、心跳与重连、流量限制 |
| 网络不稳定的编辑器 | 本地队列加后台同步 | 待确认状态、重复、冲突与恢复 |
| 多人同时编辑同一对象 | 适合数据模型的协作算法 | 身份、顺序、合并规则及业务校验 |
继续阅读可查 MDN 的 SSE 使用说明。协议工具如何使用,与业务状态如何恢复,要分别阅读。
在你的多人记账方案上写出:首次进入、收到新事件、断开十秒后重连、提交后未得到确认时,各自读取或保存什么。再选一个会迫使你改变方案的条件,例如允许离线编辑。
至少包含一个反例:已经显示 v4,v3 晚到时会怎样。停止条件是你能解释当前用途下的显示承诺;不必在这一节实现所有协作算法。
下一节 现代通信:从收发器到分层网络 将打开这条更新路线所依靠的通道。