第 16 / 23 篇

第15章:新主开始写入之后,旧主应该去哪里

提升的终点不是关闭只读开关,而是新主能够按照预定数据边界接受业务,同时旧主无法重新夺回写入权。尤其当旧主磁盘还在,它可能带着新主没有的订单重新启动。这个节点很有价值,也很危险。本章保留其差异,证明它被隔离,而不把恢复网络连接误当作重新加入成功。

新主第一笔写入是验收对象

旧主停止后,副本停止复制并清理通道配置,依次关闭super_read_only与read_only,检查两项均为零。程序由ledger_app提交订单六千零一,金额十九,标签为新主专属。随后查询该订单存在、旧主独有订单五千零一不存在,全部订单必须等于幸存前缀加六千零一,余额必须在前缀上再减十九。

这些断言同时证明新角色可写、历史边界明确以及新写入确实落在目标节点。只关闭super_read_only会保留read_only,root等管理身份可能绕过后者,因此验收必须由没有管理特权的业务账号完成。仅执行SET GLOBAL成功不能证明业务表可写,空表INSERT成功也不能证明真实账本关系成立。我们用与正常业务相同的三表事务完成验证,使提升验收与业务语义相连。

手工RTO在所有这些检查完成后结束,而不是在开关改变那一刻结束。读者可以从报告看出等待旧主停止占据多少时间,但不能把十秒左右的教学结果宣传为整套线上切换耗时。应用连接池、路由与外部依赖不在程序内,相关时间没有被测量。

两边此时已经有不同未来

旧主拥有五千零一,新主拥有六千零一,两边都保留原来幸存的订单。旧主余额包含十三的支出,新主包含十九的支出。它们各自内部平衡,但业务事实不同。再次启动旧主后,直接把它作为可写主库暴露出去,会让用户读到不同结果,还可能重复执行已经确认的操作。

更换源配置也不能自动解决。若想让旧主跟随新主,需要确定如何处理其额外事务、是否重建数据、如何保存丢失订单证据。把两边GTID集合合并可能导致冲突,直接丢弃旧卷又会失去补偿依据。这里选择最清楚的教学结果:隔离保存,不自动重新加入。

返回路径先断网

程序在旧主仍停止时,将它从任务网络断开,然后启动。这样即使数据库进程刚启动到只读设置生效之间存在时间,其他容器也无法通过该网络连接它。程序随后通过docker exec的本地管理路径设置super_read_only,再发起真实旧主写入,必须得到ERROR 1290。

Docker inspect还要确认网络集合为空。数据库状态查询确认六千零一没有神奇出现在旧主,失败写入七千零一也没有产生。旧主保留原有分叉历史,既不是已经修复,也不是可以重新承接流量。报告明确automatically_rejoined为false,让状态含义可机器识别。

网络隔离与管理路径的区别

docker exec能在断网容器里执行本地SQL,是受控管理路径。它不意味着应用仍可从任务网络写入。生产也常存在独立管理面,必须明确哪些主体仍有修改角色的能力。隔离操作应限制业务访问,同时保留必要取证能力;无法分清两者,可能把所有管理权限都误当成业务安全。

只读变量不是不可撤销保险。拥有足够权限的管理员仍能关闭它,所以审计和变更流程必须覆盖角色变更。正确运行的隔离方案需要防止自动脚本在检测旧主恢复后自行启用写入。自动化的默认行为应与当前集群角色来源一致,不能只根据本机启动成功决定当主。

安全重建通常更容易解释

当差异已经分析并导出,重建旧节点为新主的副本,往往比试图原地合并更可控。重建需要新的正确基线、匹配的GTID历史与完整复制验收。旧节点曾经是主,不代表它天然适合再次跟随。每一次角色转换都应记录源身份和初始数据范围。

本书没有执行这条重建路径,原因是它会引入第二套备份初始化和额外事务处理,不适合在同一实验中悄悄增加未经充分验证的步骤。我们把它留作明确扩展任务,要求读者设计额外验收,而不是用一句“再配置复制即可”糊弄过去。

切换之后还有业务工作

订单五千零一的客户已经收到成功确认,需要对账与补偿。若外部支付存在,不能只在新主插回一行订单就认为所有状态一致;要核对支付金额、幂等键和下游事件。新主六千零一也应进入后续备份和日志保护,避免切换成功后保护链路尚未恢复。

还应检查监控指向、备份任务源、复制账户、只读查询路由和告警规则。很多恢复事故发生在“服务已经好了”之后,因为旧任务仍从错误节点备份,新的日志没有归档。技术切换需要一个明确收尾清单,确保下一次故障仍然有可用恢复输入。