08.04 一致性模型:刚发的消息何时可见
返回第八卷 · 部分失败前置:08.01 · 时间前置:08.02 · 复制前置:08.03 · 事务隔离前置:06.07
08.04 一致性模型:刚发的消息何时可见
DeepTutor 八节初稿经技术与教学审阅后的静态课程。P/R1/R2、t=10/40/70、
W9和所有观察历史均为虚构纸上模型;没有运行 Go、数据库、复制、客户端或站点,不声称 OpenIM 提供这里列出的具体一致性级别。当前 S2200 accepted_in_memory与未来 S3 v2stored_in_teaching_db提议仍分开;本章讨论的是假定 S3 写入已知提交后的读取承诺。
一、同一条 seq=9,为什么有人看见、有人看不见?
沿用 08.03:会话 c-a 原有 seq=1..8,P 在 t=10 将 m-9/seq=9 已知提交;R1 到 t=70 才回放并使新查询快照可见 9,R2 仍停在 8。我们只讨论相同会话、相同“最新历史”查询、没有删除或新的写入、读者始终有权的固定模型。否则“后来读到 8”也可能是撤回、权限变化、过滤条件改变或旧事务快照,不宜直接称复制倒退。
一致性模型是系统对调用者“不同时间、不同节点的操作结果可以怎样排列和观察”的承诺。它不是一句笼统的“强/弱”;要标清对象、客户端/会话范围、写入确认点、读路由和失败时返回什么。前几章已经把 S 的本机提交、R 的回放与 A 的 HTTP 回答分开,本章把这些机械事实翻译成用户可验收的句子。Terry 等《Session Guarantees》
| 第一遍 | 第二遍 | 交付物 |
|---|---|---|
| 最终追平、读己之写、单调读与会话范围 | 因果、顺序一致、线性化、事务隔离两轴 | 五条观察历史、每条承诺的违例判断与实现/失败策略 |
先设一个抽象提交水位 W9,表示包含 m-9 的源提交位置;它不是 c-a.seq=9,也不是 PostgreSQL LSN 或 MongoDB resume token 的实际值。把水位与会话 seq 区分开,是为了后续讨论多会话和不同引擎时不误把业务序号当复制协议位置。
二、最终追平与读己之写:谁有资格说“马上能看到”?
最终收敛的教学条件是:若源不再继续写入、复制链健康且有足够时间,R2 最终追到包括 W9 的状态。这没有固定毫秒承诺,更不表示 t=40 的每次读都要有 9。若网络永久断开或复制日志已断链,也不能无条件说“终会收敛”。PostgreSQL 热备可见性
读己之写(Read Your Writes,RYW)要求同一约定的会话里,A 已确认的写入在后续读取中可见。A 在 t=10 收到写 m-9 的未来 S3 持久版确认,t=40 被路由到 R2,最新历史只返回 8,7,6:若产品承诺 A 发后立即能从历史看到自己发的 9,这一观察就违反 RYW。若产品只承诺 S2 的进程内受理,根本不能把这个 S3 写入前提偷套回 S2 数据库历史。会话保证原论文
实现方向有两类:把 A 的后续关键读路由到权威 P;或在写答复里携带可验证 W9,指定 R1/R2 读前等到它回放并对新快照可见。R2 若长期落后,应按合同等待、改读 P 或返回明确的未满足/超时状态,不要悄悄给 8,7,6 却对 A 说“已保证刚发可见”。这要付出路由、等待、元数据和可用性成本。08.03 复制目标与代价
三、单调读与四种会话保证:跨副本不能越读越旧
单调读(Monotonic Reads,MR)要求同一会话的后续读取不倒退到比先前所见更旧的状态。A 先在 P 或 t=70 的 R1 读到 9、8、7,随后路由到仍只回放到 8 的 R2 读得 8、7、6。在固定无删除、同查询条件下,A 已见过的 9 消失,违反 MR。若客户端换设备,是否还是“同一会话”取决于产品是否把最低已见水位跨设备安全传递;光用同一账号登录不自动产生共享会话保证。
Terry 等提出的四种可分别要求的会话保证可用 IM 操作解释,仍须具体协议实现,不是四个数据库开关:原论文
| 保证 | 初学者可以问 | 本案例中的反例或用途 |
|---|---|---|
| Read Your Writes(读己之写) | 我发完后自己读得到吗? | A 写 9 后从 R2 读到 8 违反 |
| Monotonic Reads(单调读) | 我看过 9,下一次会退回 8 吗? | R1→R2 切读可能违反 |
| Monotonic Writes(单调写) | 我先发 9 后撤回 9,后一个写会否先于前一个写生效? | 需要同一会话写入依赖顺序 |
| Writes Follow Reads(写跟随读) | 我读过某条消息后回复/标记,写是否基于至少包含该读结果的状态? | B 读到 9 后的回执不应在没见 9 的状态上被误解释 |
会话是操作历史的约定范围,不必等于一个 TCP 连接、一个 Go context 或一次数据库事务。跨设备同步会话最低水位可能需身份、版本与设备状态;这些元数据本身也会延迟。能定义保证不等于当前系统已验证保证。
四、因果可见:收到通知后查不到消息算不算违例?
08.02 的事件图有 S 提交 m-9 → G 收到确认 → G 发通知 → B 收到通知。B 接着去 R2 拉历史,却只见到 8。若产品合同明确说“收到这条通知后,紧接着的补拉必须读到它指向的 m-9”,这个结果违反了该通知与历史读取之间的因果可见承诺。可让通知附抽象依赖水位 W9,B 的补拉选择已回放到 W9 的读路径,或明确返回“尚未追平、稍后重试”。
但若通知只是“可能有新消息”的提示,产品允许 B 看到加载中并在后续补拉才得到 9,那么 t=40 的暂缺本身不自动违反这种较弱合同。先写出用户可见承诺,再把因果模型与实现对应;不能见到一次旧读就无条件喊“因果一致性坏了”。独立设备 C 在 08.02 的离线编辑与 m-9 提交没有因果路径,并发事件也不必由所有节点以同样物理时间顺序观察。Lamport 发生在前
这也提醒:会话 seq=9 是 c-a 内排序键,跨会话依赖、会员变更或通知的完整因果不一定能由一个 seq 表达;客户端墙钟或单个 Lamport 数值也不能直接替代源提交位置与依赖传播。
五、顺序一致与线性化:真实时间是否必须被保留?
现在只看一个抽象对象“c-a 最新已提交消息头”,初始值为 8。A 写入 9 并在 t=10 完成并收到成功;没有从 A 向 C 的消息或其他因果通信,独立客户端 C 到 t=20 才开始读取这个对象,却从落后 R2 读出 8。
顺序一致性(Sequential Consistency,SC)要求存在一个合法的单一顺序,保留每个客户端自己的程序顺序,但不必保留不同客户端操作的现实完成先后。A 只有一次写,C 只有一次读,所以可以在抽象顺序里排成“C 读 8 → A 写 9”;对这两条操作而言,仍可能符合 SC。这不表示真实物理读发生在 t=10 前,只是 SC 容许用那种抽象顺序解释结果。
线性化(Linearizability)还要求:若一个操作的响应先于另一个操作的调用,抽象顺序也必须保留这段非重叠的真实时间先后。A 写已在 t=10 完成,C 的读直到 t=20 才开始,故不能把 C 读排在写之前;若对象的语义是“读最新头”,读 8 违反线性化。Herlihy–Wing 线性化原论文
反过来,若是 A 自己先收到写 9 的成功,再发一个新读却得到 8,既违反读己之写,也不能用“把读排在自己的写之前”满足该客户端的程序顺序。若 A 写与 C 读重叠,或 C 收到 A 的通知形成因果边,判断又要看不同前提;本题的 t=10 完成、t=20 开始和无通信是故意固定的判别条件。线性化是对指定对象/操作合同的性质,不等于所有消息的设备交付、事务原子性或业务授权都自动成立。
六、数据库事务隔离与副本观察是两条不同轴
06.07–06.08 讲 PostgreSQL Read Committed、Repeatable Read、Serializable,主要分析一笔或多笔数据库事务对行版本、冲突和写偏斜的处理。Serializable 成功事务可等效某个串行事务顺序,冲突时可能失败重试;但这句不自动规定一个被路由到仍停在 8 的 R2 的后续请求必须看见 9,也没有单独定义 A/C 非重叠操作的线性化合同。PostgreSQL 事务隔离
把两轴写在评审表中:
| 轴 | 典型问题 | 要看的证据 |
|---|---|---|
| 事务隔离 | 退群与发送交错后,能否留下业务不允许的消息? | 同库事务、锁/版本/约束、冲突重试 |
| 副本/会话观察 | A 写 9 后读 R2 是否仍只见 8? | 写确认范围、读路由、R2 回放位置、会话水位 |
| 外部端到端 | B 是否收到通知并展示/已读? | 推送、设备确认和用户行为证据 |
同样,MongoDB 选择 w:"majority" 或 PostgreSQL 要求某个备库远端 flush,不能由此直接推断任意副本读都已应用 9。写入/读取关注、选定副本、会话因果选项与故障切换要一起核对;08.03 已给基本水位表。
七、把一致性合同落成路由、水位和失败策略
一个可评审的教学候选:S3 写成功后为 A 保留抽象提交位置 W9;A 的后续关键读要么读权威 P,要么只在指定副本回放并为新快照提供至少 W9 的状态后返回。若 A 已看见 W9,再发起历史读,携带的最低已见水位不得下降;路由器不能把它无声送到仍停在 8 的 R2。B 的通知若承诺立即可补拉,可携相同依赖水位;若它只是提示,就明确“可能需要等待/重试”。
| 需求 | 路由/水位候选 | 达不到时怎样答 |
|---|---|---|
| A 发后立刻见 9 | P 读或等指定 R 回放到 W9 | 有界等待、回主或明确未满足,不伪造旧结果 |
| A 已见 9 后不倒退 | 会话最低已见水位 max(old,W9) | 换节点/设备时无法承诺要显式说明 |
| B 通知后立即可拉 | 通知携依赖 W9,补拉等可见或改主读 | 返回待追平,不把空页当消息不存在 |
| 普通旧历史允许稍后追平 | 可读副本但标可接受陈旧范围 | 若超过期限,依合同失败/降级 |
水位有来源、保留期和作用域:W9 不等于 seq=9,切主或迁移后还需映射/失效政策;权限变化可能要求比消息水位更强的判断,不能由消息已回放推成成员授权也新。路由到主、等待副本、携 token 和拒绝旧读各要付延迟、负载、元数据或可用性成本。若不满足保证,明确失败优于悄悄返回违反合同的旧值。
MongoDB 的因果一致会话也有具体读写关注前提;官方文档说明在需要兼顾数据耐久的范围内,以 readConcern:"majority" 和 writeConcern:"majority" 配合因果会话可提供读己之写、单调读、单调写与写跟随读。本课程不是在启动一个 MongoDB 会话,不能只写“用了 MongoDB”便声称具备这四项。MongoDB 因果一致会话文档
八、用观察历史验收,而不是只看配置名
把五条纸上观察历史逐一判定:①无新写且复制健康后 R2 最终追到 W9;②A 写 9 成功后从 R2 读 8;③A 先读 9 再读 8;④B 收通知后立即拉到 8;⑤A 写完成 t=10、独立 C t=20 才读 8。每条先标对象、会话、写确认和读开始/结束,再判断它违反哪一个已声明的合同,而不是看到“旧”字就贴标签。
分层练习与反馈
1–8 先认模型,9–16 判纸上历史,17–22 评审事务、路由与业务代价。先写预测,再展开答案。
1. 本章的 `W9` 与 `c-a.seq=9` 是同一个字段吗?
不是。W9 是抽象源提交位置;seq9 是会话内业务排序值。
2. “最终追平”是否保证 R2 在 t=40 已看见 9?
不保证。它需要无新写、健康传播等前提,也没有本题给定的固定追平时限。
3. A 写 9 后从 R2 读 8,若承诺读己之写,结论是什么?
违反该承诺;必须调整路由、等待可见水位或明确失败。
4. 先见 9 后见 8,首先检查哪些等价前提?
同一会话/用户范围、相同查询和授权、无删除/新写、是否有旧事务快照;固定本题后才判为单调读倒退。
5. TCP 连接换了,是否自动意味着新会话?
不一定。会话是产品/存储定义的操作历史范围,不等于一次网络连接。
6. 会话保证必须四项一起启用吗?
不必须。可分别选择,但各自需要范围、状态传递与失败策略。
7. 事务 Serializable 能自动让任意 R2 读到最新 9 吗?
不能。事务隔离与跨副本读路由/回放是不同问题。
8. B 已收到通知能证明 B 设备已展示、用户已读吗?
不能。通知、补拉、展示和阅读各有不同证据。
9. A t=40 从 R2 读 8,是最终收敛模型必然违例吗?
不必然。最终收敛没有要求这次立即读到 9;若另承诺 RYW 才是违例。
10. A t=70 在 R1 读 9,之后在 R2 读 8,违反什么?
在固定本题同会话、同查询、无删除的条件下违反 Monotonic Reads。
11. A 写 9 后撤回 9 的操作先被副本应用,可能违反哪项会话保证?
Monotonic Writes:后一项写依赖前一项,不能被解释为在原消息之前生效。
12. B 读到 9 后写回执,但回执落在尚没见 9 的状态,涉及什么?
Writes Follow Reads 与因果依赖:该写应建立在包含先前读结果的可见状态上。
13. B 通知后读 R2 只到 8,是否无条件违反因果一致?
不是。若合同承诺通知后立刻可补拉到 9,则违约;若通知只是提示并允许稍后追平,则需按较弱合同判断。
14. A 写 9 完成 t=10,独立 C t=20 才读 8,为什么可能仍顺序一致?
A 只有写、C 只有读且两者无通信,可在抽象总序里排“C 读 8→A 写 9”,保留各自程序顺序。
15. 同一条历史为什么违反线性化?
A 写的响应先于 C 读的调用;线性化须保留这段真实时间先后,读最新头不能再返回旧 8。
16. 若 A 自己写后读到 8,还能用把读排写前满足顺序一致吗?
不能。同一客户端程序顺序要求写先于后续读。
17. 若 C 在 t=5 就开始读、到 t=25 才返回,仍能直接用本题结论吗?
不能。C 的读与 A 的写可能重叠,线性化判定要重新画调用/响应区间。
18. “P/R1 有两份日志”就证明每次 R2 查询线性化吗?
不能。R2 可能仍未回放,且确认/选举安全与读路由都要单独设计。
19. A 需要读己之写,除了读 P 还有哪条候选路径?
携 W9 等指定副本回放到该位置并用新快照读取;达不到就按合同等待、回主或明确失败。
20. 把 W9 直接当永久跨迁移 token 有什么风险?
源位置可能变更或失效,需定义作用域、映射、保留/过期和切换后的失败政策。
21. 为什么“强一点的读”可能降低可用性或增加延迟?
可选副本更少,落后时要等待/回主;还需保存水位和处理故障,不能无条件立刻答复。
22. MongoDB 因果会话是否只要创建 Session 就自动有耐久的四种保证?
不能。需按官方要求核对因果会话与 readConcern/writeConcern 的组合、操作范围和故障条件。
本章完成标准与下一步
能不看答案判定 A 写 9 后读 8、先见 9 后见 8、B 通知后读旧、独立 C 跨真实时间读旧分别与哪一份已声明合同冲突,并说明事务隔离与副本观察的区别,才算完成第一轮。第二轮在学习者隔离环境保存操作调用/响应、路由、水位和原始读取结果,本章没有代替运行。
按学习路线,08.01–08.04 已建立第七卷所需的部分失败、时间、复制与一致性前置;下一步进入07.01 访问模式与状态角色,再逐步学习缓存和消息队列。