第 13 / 23 篇

第12章:暂停应用,再证明副本真正追平

延迟不是一个抽象秒数,而是一组已经发生却尚未在目标可见的业务变化。要理解异步复制,最好先主动制造可控延迟,再观察恢复过程。本章只暂停SQL应用线程,让接收可以继续,借此区分网络传输与事务执行,也说明为什么恢复服务前应等待明确业务水位。

冻结一个已知前缀

程序先让副本追平源,并保存其完整业务状态。之后执行STOP REPLICA SQL_THREAD,源端新增订单四千零一。该事务成功返回后,副本state必须仍等于之前的前缀,查询不到新订单。源端GTID集合也必须不是副本已执行集合的子集。这个差异来自显式停止应用,而不是随机等待某次网络碰巧变慢。

接收线程没有被停止,所以新日志可能已经写入relay log。我们不依赖它一定在某个毫秒内接收完成,也不把这个实验称作网络延迟测量。它证明的命题更窄而清楚:源已成功提交,并不保证副本业务查询立即可见;接收与执行是不同阶段。

如果这里仅查看Seconds_Behind_Source,可能得到难以稳定比较的数值。我们直接用业务ID与GTID包含关系表示差异,保证结果不依赖机器时钟刚好相差几秒。生产也可使用类似水位,但需要关注水位生成频率与覆盖范围,避免一个低频心跳掩盖关键表积压。

恢复应用不等于立即追平

START REPLICA SQL_THREAD只发起执行恢复,不是等待所有事务完成的同步屏障。程序再次读取源当前GTID集合,并等待该集合在副本执行,之后才比较状态。若省略等待,在启动后立刻查询可能读到旧状态,从而把正常追赶误判成复制失败;若只等待固定几秒,则会对机器速度形成隐含依赖。

等待有明确超时,失败输出复制状态。超时可能意味着应用错误、源日志不可用、重连退避或资源不足。不同原因需要不同处理,不能通过跳过事务让绿色状态回来。GTID目标明确后,诊断可以围绕哪个阶段没有跨过这个水位展开,比一句“从库慢”更具体。

追平后再核对全部业务

等待成功之后,订单、余额和分录必须与静止源完全相同,特别包括订单四千零一。集合水位与内容比较一起证明追平,而不是只看新增订单存在。单点存在检查可能漏掉旧订单被意外改动,完整摘要则约束整个教学数据集。

生产数据很大时,可以选择关键业务抽样、分块摘要与持续一致性检查组合。但抽样只支持概率或局部结论,报告要说明范围。将几个样本相同写成“全库完全一致”,会把验证能力夸大。这里的数据足够小,因此我们能够直接给出全部有序订单行。

暂停期间的读取语义

副本读服务可能继续返回旧数据。用户刚完成订单就跳转到副本查询,可能误以为下单失败并再次提交。应用可采用读己之写策略、指定水位等待或临时回源等方式处理,但每种方式都涉及超时与失败选择。本书不实现路由层,只把底层可见性差异真实展示出来。

如果读请求要求跨多个表一致,单看某张表更新到某个ID也未必足够。需要事务级视图与合适水位,并理解并行复制的执行顺序。恢复服务不是“连上就可以读”,而是选择能满足业务语义的数据版本。这也解释了为什么切换验收需要业务条件,而不只是TCP探针。

延迟积压影响恢复时间

当主库故障时,副本可能还有大量已经接收但尚未应用的日志。如果这些日志完整且可用,等待应用可以减少丢失,却增加恢复时间。直接提升并开放写入可能让情况更复杂,因为新的本地事务和旧relay事务会相遇。正确做法依赖具体拓扑与切换计划,不能机械选择“最快打开只读开关”。

本书后面的丢失实验会停止整个通道,使缺失事务明确没有到达副本,因此不涉及尾部relay抢救。这个选择让RPO结果可确定,也避免把“尚未执行”和“根本没收到”混为一谈。读者应保留这两个故障模型,分别设计恢复与验收。

本章练习

把暂停对象改成IO接收线程,保留SQL线程运行,思考已经接收的relay log会怎样变化。重新运行时应给出源GTID、副本执行集合和业务订单的三项证据。若实验结果没有按预期出现,先检查暂停发生时已有多少日志,再决定是否需要新的事务标记,不能用任意睡眠替代因果判断。