故障切换是一把演练过的开关
备用节点只有在提升路径可用、旧写者不能破坏新历史时才有价值。故障切换既是决策,也是流程与演练。副本看起来健康,不代表客户端能连上它,也不代表所有已确认写入都已经复制过去。先确定恢复时间目标与可接受的数据损失,再决定何时切换,而不是等报警响起才现场讨论。 提前设计好决定 确定谁有权批准提升、什么证据足以说明故障,以及由自动化还是操作员执行。心跳丢失可能是网络分区,而不是主库已经停止。故障检测本身不能授权两个独立写者。系统需要真正执行所有权边界的机制,例如经过确认的节点隔离,或存储端校验的 fencing,并使它与复制协议的保证相匹配。 单写者服务的安全目标是「最多一个被接受的写者」,并非每一瞬间都恰好有一个可用写者。隔离旧节点后、提升新节点前,暂时没有写者可能正是正确的停机选择。操作手册应写明候选节点、复制位置、可接受的丢失范围、客户端如何重连,以及前提不成立时由谁叫停。自动切换能缩短决策时间,却不能凭空补回尚未复制的数据。 演练真实经过的路径 根据风险决定演练频率,并在相关变更后重测。演练只能证明实际覆盖到的路径:绕过 DNS 的数据库提升不能验证地址缓存;测试客户端使用另一套身份,也不能证明生产权限正确。在安全前提下,加入真实的重连流程和已确认写入核对。记录范围、耗时、复制延迟、拒绝条件及恢复证据,不要把最近一次成功的日期当成完整的就绪评分。 开始前先安排安全停止点和恢复步骤。新主库一旦接受写入,切回旧节点往往就不再是撤销一项配置那么简单。旧主库可能需要对账或重建为副本,不能因为进程重新启动就恢复写权限。备份提供另一条恢复路径,但「有备份」本身也不等于已经验证能恢复。 坏掉的样子 旧主库在提升之后仍可写,就会产生脑裂。控制器里记录一句「已隔离」不够,存储或隔离机制必须真正阻止旧写入。落后的候选节点也可能在切换成功后丢失已确认写入。延迟阈值必须有明确单位,并与数据损失目标相连;允许非零延迟,不能同时宣称零数据丢失。 健康检查反复抖动会触发来回切换,应设置明确的故障判定和恢复条件,不要在重新同步之前自动切回。DNS 修改只影响后续查询:已有连接可能持续使用旧地址,按旧 TTL 缓存的回答也不会立刻消失。事故发生时调低 TTL,不能追溯缩短已经缓存的寿命。除了权威 DNS 的响应,还要验证真实客户端的重试、缓存和重连行为,否则控制台显示成功,用户路径仍可能失败。 先隔离,再提升写者。 依据原文与明确限制静态绘制;未为本图重跑原文夹具。 快速验证 使用 Python 3.9 或更新版本,将下面内容保存为 failover_drill.py,在临时目录运行 python3 failover_drill.py。运行时不要使用 -O、-OO 或非零的 PYTHONOPTIMIZE,以免断言被跳过。它只修改内存对象。健康状态和复制延迟是模拟值,fence() 只是更新集合,并不执行真实隔离。模型保留一个被指定的主节点,但隔离期间允许没有可写节点。候选节点通过检查后会被明确解除隔离,因此切回旧节点时不会出现「已经成为主库,却还留在隔离集合中」的自相矛盾状态。 ...