第 14 / 23 篇

第13章:亲眼看到一笔已确认事务在切换后消失

讨论异步复制风险时,人们常说“理论上可能丢一点”。这个表述太轻,容易被当成可以忽略的边角。本章让源明确确认一笔订单,再保证副本没有接收它,最后切换到副本并查询。这笔订单在旧主存在、在新主不存在,风险从模糊可能变成可定位的业务差异。

把窗口固定下来

程序让副本追平后,执行STOP REPLICA停止接收与应用,保存副本完整前缀。源随后提交订单五千零一,金额十三,标签为已确认但未复制。mysql客户端必须返回成功,源完整业务状态比副本多一笔订单,并包含对应分录与余额扣减。此时副本查询仍然没有这笔订单。

停止通道是一种故障注入,不是对公网延迟分布的模拟。它不测量典型丢失概率,也不说明任何负载下都会丢同样数量。它只证明协议允许的关键事实:异步源可以在副本没有事务时向客户端确认。只要后续承接写入的节点缺少该事务,已确认业务就会从服务视角消失。

源容器最终执行docker stop,十秒宽限后可能强杀,不删除数据卷。即使旧主磁盘完好,新主也不会自动知道这笔订单。现实中如果还能抢救旧主日志,可能减少损失,但那需要额外流程。为了展示明确边界,本书不在提升前从旧主补日志,报告如实保留这笔缺失。

源端持久与副本拥有是两回事

将sync_binlog和InnoDB刷盘参数设得严格,有助于源端崩溃持久性,但不会把异步复制变成同步确认。订单五千零一即使已经稳稳写在旧主磁盘,只要新主没有收到,切换后的查询仍然缺失。保护源盘和保护跨节点确认范围,需要分别设计。

半同步、组复制或其他协议可能缩小某些窗口,但它们各有确认条件、退化行为和故障模型。不能从名称推导无条件零丢失。本书没有执行这些协议,因此不借它们装饰结果。要采用更强承诺,就应设计与承诺匹配的真实故障实验,包括退化、超时和网络分区。

丢失计数来自业务差集

我们保存故障前源状态和副本前缀,提升后显式查询订单五千零一不存在,同时核对新主其他数据仍然等于前缀再加新主专属订单。RPO报告因此写丢失一笔已确认事务,并列出具体ID。它没有通过源副本时间差估算订单数量,也没有把所有未执行GTID简单等价成客户交易数。

一项业务操作可能跨多个数据库事务,某个GTID也可能包含多笔订单,真实RPO需要选择业务单位。支付系统可能更关心确认金额和客户数量,配置系统可能关心最近发布版本。报告应同时保存技术水位与业务差集,避免一个看似很小的事务数量掩盖高价值损失。

时间窗口如何解读

程序记录停止复制后取得前缀到源确认缺失事务之间的单调时钟间隔。这个数值是本次客户端观测窗口,不是生产承诺,也不是最后一笔幸存事务的精确年龄。我们明确保存边界,而不把几百毫秒写成“系统RPO小于一秒”。

若要测真实业务RPO时间,需要独立确认日志及各事务确认时间,明确哪些记录在新主存在。时钟同步、网络回执和日志持久性都会影响精度。把这些外部证据放在同一故障域也可能同时丢失,设计时应考虑独立保存。这里的小型演练只给出可重复的事务级结果。

不把数据损失当成测试失败

本章场景PASS表示预期风险被正确观测,不表示系统满足零丢失目标。如果测试要求切换后订单五千零一存在,这套异步方案就应该失败。理解测试名称与业务承诺的区别很重要:我们验证了机制的边界,而不是通过修改断言掩盖缺陷。

恢复决策还需要业务接受。若丢失范围超过允许预算,应停止自动开放写入,尝试补齐日志或选择更完整节点。若选择继续服务,则必须保留缺失集合,安排对账、重试和客户处理。数据库切换完成不能把已确认交易的责任一起切走。

本章练习

假设五千零一订单对应外部支付已扣款,新主查不到订单。说明为什么直接让客户重新支付不安全,以及至少需要哪两个稳定标识支持修复。再为业务方写一句准确状态说明,要求包含已知缺失范围、当前可服务状态和后续核对动作,不能使用“无损恢复”四个字。