06.11 复制、迁移与对账:历史消息怎样安全切换
返回第六卷 · 历史查询前置:06.02 · 提交与恢复前置:06.09 · Go 数据访问前置:06.10
06.11 复制、迁移与对账:历史消息怎样安全切换
DeepTutor 八节初稿经技术与教学审阅后的静态课程。
c-a的序号、主副本和新旧存储均为虚构教学场景;本章不声称 OpenIM 的实际拓扑、配置、写关注或迁移流程。没有运行数据库、SQL、Go、复制、回填或站点。源数据库的提交、副本接收/持久/回放、业务读到、设备交付与客户端知晓是不同证据。
一、消息已经提交,为何另一台机器还没查到?
06.09 在单机故障模型里讨论了 m-a 的 WAL 和本机恢复。现在把历史读移到另一个数据库节点。虚构 c-a 有 seq=1..8;主库已提交序号 8,副本却只回放到序号 7。若读历史被路由到这个副本,返回 1..7 而暂缺 8,不必然表示主库丢了消息。它可能是复制滞后;同时要排除权限、游标条件和路由错误,不能凭“查不到”直接确定根因。
主库/权威写节点接收受控写入,副本接收并应用来自主库的变化。同步与异步描述某个确认点前要等到哪一步,不是“有副本就所有读立即一致”。PostgreSQL 文档区分主库、热备、流复制及同步复制;MongoDB 副本集用主节点 oplog 向次节点异步复制。它们是两个不同引擎的实现例子。PostgreSQL 高可用与复制 · MongoDB Replication
| 第一遍 | 第二遍 | 交付物 |
|---|---|---|
| 主/副本、延迟、读写确认、故障切换 | 一致快照+增量、源位置水位、对账、回滚窗 | 一张副本时间线、一张迁移状态图、一份同水位对账表 |
先定义业务问题:A 发送后立刻打开历史,应否必见刚发的 8?普通浏览过去的历史可否接受短暂旧读?退群权限检查能否用旧成员副本?这些操作的一致性需求不同,不能用同一“读副本”开关解决。
二、复制进度不是一个布尔值:发出、收到、持久、回放
以 PostgreSQL 物理流复制为主例:主库生成 WAL,副本接收,写入其本地存储并按配置持久,随后回放使更改进入副本查询视图。教学上可把进度分为主库已产生/发送、副本已收到/写入、副本已持久、副本已回放。即使副本已收到了描述 seq=8 的日志,热备查询也可能暂时只见 seq<=7,因为读取可见性还取决于回放进度。真实 PostgreSQL 有相应 WAL 位置与复制状态指标,不能把这四点合称“同步完成”。PostgreSQL Streaming Replication
主库:提交 seq8 ──传输──> 副本收到 ──写入/持久──> 副本回放 ──> 热备读见 seq8
(各段都可能有延迟;具体确认点由配置决定)PostgreSQL 的物理复制与逻辑复制也不同:前者发送 WAL 供备库恢复,后者可按发布/订阅的逻辑变更复制部分表数据。逻辑复制并不自动同步所有 schema/DDL 或序列状态;若迁移依赖它,目标表结构与序列续写要单独核对。复制槽可帮助保留未被订阅方消费的 WAL,但消费者长时间落后也可能让主库保留大量日志,占用空间。PostgreSQL Logical Replication · 逻辑复制限制 · 流复制与复制槽
同步配置只说明等待的里程碑。PostgreSQL 可按配置等本地 WAL、同步备库写入、持久或回放;例如 remote_apply 等到所选同步备库回放,使该备库可读到相关提交,代价是提交等待更长。没有配置同步备库时,不能从 synchronous_commit=on 四个字推导“所有副本已回放”。其他异步副本即使存在,也不自动达到同一水位。PostgreSQL WAL 同步模式 · 同步复制
三、历史读可以旧,授权决定不能偷用旧状态
固定小时间线:主库在时刻 T1 提交 c-a,seq=8,热备仍只回放到 seq=7;A 在 T2 从热备读取最近三条,可能得到 7,6,5,从主库读取则按同一条件可能得到 8,7,6。这里假定历史查询无额外游标且两端成员权限相同,目的是看路由和回放水位,不是宣称真实数据库必选哪个索引。若使用 06.02 的“seq<7 更旧三条”,两边都只会取 6、5、4;因此排查时必须保存具体查询参数,不能只问“副本有没有 8”。
| 用户操作 | 可能的要求 | 一种教学处理方向 |
|---|---|---|
| 发送后立即刷新自己历史 | 读到刚刚已确认的消息 | 读权威节点,或等指定副本到达可见水位再读 |
| 浏览旧历史 | 可否接受短暂陈旧由产品合同定义 | 可评估副本读取,同时标注延迟和失败回退 |
| 退群后决定是否允许再发 | 必须按最新受控资格和 06.07–06.08 顺序判断 | 在权威写入/冲突边界完成,不能凭旧副本状态放行 |
“等副本水位”必须等到业务所需的可见里程碑,不能只看到 TCP 收到日志就报告可读。具体数据库可能提供因果会话、读关注或等待回放位置的机制,但要按版本与操作范围核对。MongoDB 官方明确非 primary 读偏好可能返回陈旧数据;读写关注与会话保证不能被一句“读副本”概括。MongoDB Read Preference
四、故障切换:先确保只有一个权威写入口
若旧主库突然失联,候选副本能否提升为新主,取决于它已有多少日志、可否恢复、业务 RPO/RTO 和故障模式。异步副本可能尚未收到最近在旧主本机提交的 seq=8;若此时提升只拥有 1..7 的副本,seq=8 对新权威可能缺失。A 原先可能收到本机成功回答,也可能只见提交未知。不能由“新主已启动”倒推每条旧主消息都在。PostgreSQL Failover/Streaming Replication
更危险的是旧主后来恢复又接受写入,而新主也在写:两条权威时间线会分叉。课程设计要求切换前后有单一权威写入口,并通过路由隔离、旧主停止写入或其他可核对的防护(fencing)处理旧主;真正实施方法取决于集群管理和故障模型。不能只改 DNS 就声称不存在旧连接写入。切换记录应包括旧/新节点身份、最后安全源位置、提升与路由生效时刻、未确认请求及稳定消息 ID 对账范围。
| 故障情况 | 需要核对 | 不能直接宣称 |
|---|---|---|
| 旧主本机提交,异步副本落后 | 旧主 WAL 与候选副本安全位置 | 零丢失、零重复 |
| 新主已提升,旧主恢复 | 旧主是否被禁止继续写入 | 两边可同时当主 |
| A 响应未知 | 稳定 message_id 在新权威的状态 | 换新 ID 重发一定安全 |
副本不是备份:误删一批历史可能也被复制过去。恢复此类错误要另看备份、归档、恢复点和演练,06.09 已建立 RPO/RTO 的问题表。
五、从旧存储 A 迁到 B:一致快照加无缺口增量
故障切换是同一复制体系内换权威,存储迁移可能换数据库/表结构/编码。固定教学目标:旧存储 A 对 c-a 的历史仍是唯一权威写入点,目标 B 先成为影子副本,验证后才考虑接管。设我们能取得一个一致的源快照,其变更流位置为 W0,快照内包含 seq=1..5;随后源在变更流中发生 seq=6,7,8 的插入。W0 是源日志/变更流位置,不是会话 seq=5 本身;一个源变更位置可覆盖多个会话和更新类型。
A 权威:稳定快照 W0 (c-a seq1..5) ──后续变更6,7,8──> 水位 W1
│ │
└──复制快照到 B──> B 1..5 ──幂等应用──> B 1..8真实系统必须解决“快照与变更流之间有没有空窗”:应使用引擎支持的一致快照/订阅流程,或从不晚于快照边界的位置消费并用稳定 message_id、版本/事件身份去重;不能先拷完文件再从“现在”开始监听,否则中间写入可能永久漏掉。若消息会编辑、撤回或删除,变更流还须覆盖这些操作和顺序;只回填新增消息不足以对齐最终历史。PostgreSQL 逻辑复制有初始数据同步与后续变更机制,MongoDB change stream 有 resume token 和保留窗口,各有操作前提。PostgreSQL Logical Replication · MongoDB Change Streams
教学阶段顺序为:准备兼容目标 schema → 建立可追溯 W0 → 回填快照 → 消费增量至 W1 → 同水位影子读对照 → 阻止旧权威继续产生未同步写 → 确认 B 追上切换水位 → 原子地变更权威路由/权限 → 观察并持续对账。每一步要有可检查的入口和失败出口;“影子读”只用于比较,不把未经验证的 B 返回给真实客户端。
六、对账必须在同一水位:总数相等仍可能错
在源 A 的 W1 快照里,c-a 键集合是 {1,2,3,4,5,6,7,8}。设 B 错误地成为 {1,2,3,4,5,6,8,9}:两边都是 8 条,但 B 缺 7、多 9。只比总数会误报一致;只比最大 seq 也不够,内容被改写而键仍相同也会漏。要在同一个已定义水位/快照范围比较会话+消息身份键集、必要字段或规范化内容散列、版本/删除标记,再给出缺失、多余、同键内容不一致三类差异。
| 对账维度 | 本题结果 | 为什么需要 |
|---|---|---|
| 总数 | A=8,B=8 | 只能证明数量相同,不能证明同一批消息 |
| 键集合 | A 独有 7;B 独有 9 | 定位漏写和多写 |
| 同键字段/版本 | 本题未给内容,须逐项对比 | 发现正文、撤回或权限元数据不一致 |
| 源变更位置 | 比较时固定 W1 | 避免边写边比产生假差异 |
对账不能把不同时间点的一边当真相:若 A 已有新 seq=9 而 B 仍在 W1,比较最新 A 与 W1 的 B 会把正常延迟误认成缺失。反之 B 意外多 9 也不能只说“它比较新”,要确认来源、写入权限和水位。差异修复后复查,并保存样本、范围、映射规则、散列算法、错误和修复事件;成员权限、附件元数据及会话摘要若在迁移范围内,也需分域验收。
七、切换后的回滚不是把地址改回去
直接让应用同时向 A 和 B 写入,看似简化迁移,实则两套独立提交没有自动跨库原子性:可能 A 成功 B 失败、重试顺序不同、同一 message_id 的版本被覆盖。即使预先保证消息键唯一,也要处理每个目的地的结果和重放顺序。课程默认A 单权威写,B 影子消费和比较,直到通过明确切换门槛。
切到 B 之后,新的 seq=9 若只写入 B,A 就不再具备完整历史;此时“回退路由到 A”会让消息 9 消失在旧视图。可回滚需要提前定义:继续安全地向 A 同步 B 的新写入、保留短暂只读/暂停写窗口并完成反向回填,或采用其他经验证的恢复协议。每种都会改变复杂度、可用性和允许的回滚时长。没有反向进度和同水位对账证据,就不能宣布随时可回滚。
迁移评审单至少写:权威源、源日志保留期限、W0/W1、影子读比例、差异门槛、追赶上限、写入冻结/路由切换方法、旧主 fencing、回滚截止点、回滚所需反向事件、用户可见确认合同。同步吞吐要能追上增量写入,否则 B 永远到不了切换水位;复制槽或 change stream 的保留窗口过短也会断链。
八、MongoDB 对照与交接练习
MongoDB 副本集以 primary 和 secondary、oplog 与选举/故障切换组织复制;secondary 异步应用 oplog,非 primary 读偏好可能读到旧数据。MongoDB change stream 的 resume token 是该引擎的继续读取标识,须保留足够 oplog 历史和相同流选项;不是 PostgreSQL WAL LSN,也不是 c-a.seq。写关注、读关注、会话及版本决定更细的确认语义,不能把本章 PostgreSQL 参数直接套用。MongoDB Replication · Read Preference · Change Streams
交接物是一张主→副本接收/持久/回放时间线、一份 A→B 快照+增量状态机、W1 对账结果与切换/回滚失败矩阵。它们都是方案与纸上推演,不是运行证明。
分层练习与反馈
1–8 先练复制与确认,9–16 推导迁移和对账,17–22 处理切换/回退业务取舍。每题先预测,再展开反馈。
1. 主库已提交 `seq=8`,副本只回放到 7,副本历史暂缺 8 就证明主库丢失了吗?
不能。先核对读路由、查询游标、权限和副本回放水位;复制滞后是一种可能原因。
2. 副本“已收到”日志与热备查询“已看见”一样吗?
不一样。还可能要经过写入、持久和回放;读可见要看所选引擎与副本的回放/可见水位。
3. `synchronous_commit=on` 能证明所有 PostgreSQL 副本都回放了消息吗?
不能。要看同步备库配置及所选等待模式;本地同步与远端回放是不同里程碑。
4. PostgreSQL 逻辑复制会自动复制所有 DDL 和序列状态吗?
不会。目标 schema 和序列续写等要另行核对与安排。
5. `c-a.seq` 能直接充当整个数据库 CDC 的位置吗?
不能。它只是会话内业务序号;源变更位置还要表达别的会话、更新/删除和正确的事件顺序。
6. 已收到副本日志却未回放,A 从该副本读到 8 有保证吗?
没有。收到/持久不等于进入该副本查询可见视图。
7. 副本可以直接做最新退群资格的唯一判断吗?
不应在本题合同下这么做。副本可能有旧成员版本;发送资格须在权威写入/冲突边界核对。
8. 故障切换时为何要处理旧主重新写入?
否则旧主和新主可能各自产生权威写入,历史分叉;需要可验证的单一写入口和 fencing。
9. 快照 W0 内 `c-a` 有 1..5,增量 6、7、8 全部应用后 B 应有什么?
在无编辑/删除的本题假设下,B 应有 1..8 八个键。
10. 从“文件拷完的此刻”才开始监听增量有什么风险?
快照与监听之间的源写入会漏掉。应有一致快照与覆盖该边界的变更流位置,必要时从较早位置重放并去重。
11. 重放可能重复时,目标应用如何避免把同一消息插两次?
用稳定消息身份、版本/事件身份和适当唯一约束实现幂等应用,再对账确认;不能仅靠“消费者通常只执行一次”。
12. 如果历史有编辑或撤回,只消费新插入事件足够吗?
不够。目标还要按顺序处理更新、删除/墓碑以及相关版本和可见性语义。
13. A 与 B 都是 8 条,就说明迁移完全一致吗?
不说明。本题 A 是 1..8,B 是 1..6、8、9;B 缺 7 且多 9。
14. 同键 `message_id` 存在于两边,还应比什么?
至少比需要保持的正文/元数据、版本和删除状态;散列需有相同字段顺序与规范化规则。
15. 为什么对账要固定同一水位 W1?
否则 A 的新写入和 B 的旧视图会制造假差异,无法分清复制延迟与真实漏写。
16. 对账发现 B 缺 7、多 9 后,直接切换路由可以吗?
不应。先查来源、修复并复查目标与权威源在同水位的一致性,再按切换门槛决定。
17. 异步副本落后时提升为主,旧主本机成功提交的消息一定在新主吗?
不一定。候选副本可能尚未收到/回放,必须按确认配置与安全位置判断并用稳定 ID 对账。
18. 为什么不能把普通应用双写当作跨 A/B 原子提交?
两源可一边成功另一边失败或顺序不同;没有共同事务协议,部分成功需补偿、重放和对账。
19. 切到 B 后只在 B 写 `seq=9`,此时回退 A 有何风险?
A 没有 9,直接切回会把这条新消息从旧视图中漏掉;需反向同步/冻结窗口等预先设计。
20. 复制槽或 change stream 保留窗口不够会怎样?
消费方可能再也取不到缺失变化,需重新建立安全快照与增量链;应监控保留与追赶速度。
21. MongoDB resume token 可以直接当 PostgreSQL WAL LSN 吗?
不能。它们来自不同引擎的变更流协议,c-a.seq 又是业务序号,三者都要分别保存。
22. 写出切换评审中的三个用户可见问题。
例如:A 发完是否立刻能查到、旧主失联后已确认消息会否丢失、切到 B 后失败能否回退且不漏新消息;每项都要对应具体水位、路由和对账证据。
本章完成标准与下一步
能不看答案画出主库提交到副本回放的路径、解释为何 seq=8 暂缺;能安排 A→B 的 W0 快照和增量、找出同为八条却缺 7 多 9 的差异,说明切换后回退所需的反向进度,才算完成第一轮。第二轮在个人隔离环境用真实版本、数据量、故障与对账记录验证;本文没有代替运行。
按学习路线,下一章06.12 数据库业务案例将把身份、分页、索引、事务、恢复、迁移和客户端确认组合成一份可评审的消息存储业务案例。