第 15 / 23 篇

第14章:提升之前,先证明旧主不能继续写

故障切换最危险的时刻,是有人认为旧主已经死了,旧主却仍在另一条网络路径上服务。新主一旦开始接受写入,两份账本就可能同时前进。复制落后只造成一个历史前缀缺失,双写则会形成两个不同未来。因此写入边界必须先于提升建立,并且需要实际证据。

连接失败不能证明隔离

管理节点连不上源,可能只是管理网络故障;部分应用仍可通过旧连接写入。DNS切换也不会立即关闭已有连接。代理不再转发新请求,仍可能有绕过代理的客户端。任何一项观测都应与整体访问路径对应,不能把某次ping失败当作全局写入权已经收回。

本书选择更容易验证的教学边界:旧主容器必须处于停止状态。promote函数先通过docker inspect读取实际Running,只有明确false才允许继续。检测到仍在运行就抛出FENCE_REQUIRED,副本保持super_read_only。这个机制依赖同一Docker控制面能够真实停止旧主,不是跨机房自动仲裁方案。

负例必须实际发生

在旧主仍运行且拥有未复制订单时,程序故意调用提升函数。它必须拒绝,然后以不具有SUPER或CONNECTION_ADMIN的ledger_app向副本发起一次真实业务事务。客户端必须非零退出,错误包含MySQL的ERROR 1290及read-only说明,副本完整业务状态不得改变。这样负例同时证明控制流程拒绝,以及数据库仍不接受新的业务写入。

如果只检查super_read_only变量为一,可能漏掉错误客户端没有真正发出写入,或权限路径与预想不同。如果只看到stdout为空,也可能只是命令运行失败于别的原因。退出码、错误语义与状态核对合在一起,才支持“只读拒绝”的结论。这是恢复实验中很常见的证据差别。

正确路径的顺序

开始计时后,先停止旧主,等待Docker命令完成,再由提升函数重新检查停止状态。之后停止并清除副本复制连接设置,依次关闭super_read_only与read_only并核对两者均为零,最后由只有账本增删改查权限的ledger_app写入新主专属订单并验证完整账本。任何前置步骤失败都不会继续开放写入。把这些步骤写成一个受控程序,比手册里一句“确认旧主关闭”更易复现。

RESET REPLICA ALL清理复制通道配置,不会把副本业务数据变成源的最新数据,也不会补齐那笔未复制事务。操作名称里有RESET,不代表恢复完成。我们在清理前已保存幸存前缀,清理后仍以这个前缀为验收基准,明确承认已知缺失。

数据库只读与外部隔离各有作用

super_read_only可以阻止普通数据写入,但管理权限仍能改变开关。因此生产隔离通常需要结合云实例断电、存储隔离、网络策略、代理控制和凭据策略,具体方式取决于故障域和控制面可靠性。多种措施应形成可验证的顺序,而不是堆叠术语。

本书实际使用容器停止实现提升前硬边界;返回旧主时再使用断网与只读。它没有实现远程电源管理、分布式租约或多数派仲裁,因此不会把单机Docker实验称为生产自动HA。读者可以沿同样的证据结构替换隔离机制:先构造旧主仍可写的负例,再证明所有实际写路径被阻断。

隔离失败时系统应怎样做

无法确认旧主隔离时,自动提升可能扩大事故。流程应保留当前只读状态,给出缺少的隔离证据,并进入人工处理。可用性与一致性选择需要事先由业务承诺约束,不应由值班人员在压力下临时决定。拒绝提升是一个明确结果,比在不确定中同时开放两边更可审计。

这不意味着所有环境都必须选择相同策略。某些业务可以承受分叉后合并,另一些账务业务不能。但策略必须反映到具体数据模型与修复流程。声称“我们可以最终一致”而没有冲突解决规则,并不能替代写入权控制。

保留隔离动作的证据

记录旧主身份、停止请求、停止结果、提升开始、首笔新主提交和验收完成,可以帮助事后证明没有重叠写入。生产还应记录控制面调用ID和应用流量切换时间。本书用单调时钟衡量阶段耗时,用实际容器状态判断边界,避免把墙钟文本顺序误当作严格因果关系。

下一章会让旧主重新启动,展示即使数据还在,也不能直接回到原角色。恢复能力不仅是第一次成功提升,还包括如何处理一个带着分叉历史返回的旧节点。