最近运维执行 DB 变更,给业务表 T_pay 新增字段 F_id,引起 DAO 模块读数据接口 GetBillInfo 出现大量找不到记录的失败。为什么只是新增 MySQL 字段,会引起模块接口失败?
1.原因分析
业务 MySQL 采用了读写分离的方式,写 Master 主机,读 Slave0 从机。这样的好处是读写负载被分布到不同机器,同时可以防止大量读引起 Master 故障。
看 DB 监控发现新增字段时,出现了大量的主备延时。什么是主备延时?
下图是主备同步的流程,Master 生成 binlog 后,dump_tread 线程将 binlog 同步传输给 Slave。Slave 上的 io_thread 线程接收数据并存储为中转日志 relog,最后由 sql_thread 线程将数据重放到引擎存储中。主备延时是从主机执行事务成功到备机 sql_thread 线程重放数据到最新事务 id 的时间差。
1.1.为什么 DB 新增字段会影响主备延时?
运维操作 DDL 变更时选择的策略是从主机变更,那么会先在 Master 进行 DDL 操作,再同步 binlog 到 Slave。
T_pay 表有 300M 大小,主库进行 DDL 时,会产生 mixed 的 binlog。从库 sql_thread 为单线程重放 binlog 日志,这个过程需要重建数据,占用线程时间长,造成 relaylog 堆积,产生延时。
1.2.主备延时会影响 master 上的写操作吗?
出现主备延迟只影响了 Slave0 上的 select,并没影响 Master 上的 insert、update。原因是主备延时不会影响半同步。
业务写 Master 时,会先写 binlog,只要有一个 Slave 返回 ACK,半同步就完成了。Slave 只要更新到 binlog 就返回 ACK 给 Master 了,然后 Slave上 的 sql_thread 进程可以慢慢重放数据。
1.3.出现读数据接口失败原因
用户先付款成功,会在 Master 上 InsertBillInfo 生成一条订单记录;然后在 Slave0 上 GetBillInfo 去读这条订单记录,但由于主备延时,这条订单记录还没有在从机上生成,所以出现找不到记录的失败。
2.如何变更
方案一:DAO 切换为写 Master 读 Master
优势:切换之后实时性更好,不会出现主备延时
劣势:DAO 在读写分离时,可以容忍读故障。例如,索引问题出现慢查询引发 DB 故障,此时只是影响读,业务可以正常运行。读写分离可以对这些场景进行兜底。如果切换为写 Master 读 Master,一旦出现故障,业务会不可用。可以考虑在读 Master 后变更 DB,观察业务情况后再判断是否需要切回 Slave。
风险:需要修改配置切换为读写 Master
方案二:低峰期从主机缓慢变更
优势:DB 变更已开始执行,这种方案不需要代码层面的修改
劣势:每个表之间的变更间隔必须加大,尽量让中间 sleep 的时间追齐前面表变更导致的延迟。不加间隔时大约需要执行 4 小时
风险:出现主备延时,导致销帐延迟
方案三:运维写脚本分开主备变更
优势:可以尽量减少备机延时
劣势:需要运维手写脚本来分开主备变更,属于非标准操作,风险较高
风险:运维手写脚本分开主备变更
方案四:从机读不到时再读 Master 兜底
优势:可以解决一部分主备延时问题
劣势:放大请求,有些订单本来就读不到
方案五:从机获取两个 Slave 的延时,选最优 Slave 读
优势:可以减轻主备延时
劣势:可能还是存在延时问题
方案六:低峰期挂公告停服再变更
优势:不需要开发和运维变更
劣势:MySQL 执行无法并行,执行时间 4 小时+,停服时间过长
风险:影响用户体验
结论
对比以上方案后,选择方案一进行变更。变更当晚又出现主备延时。但因为 GetBillInfo 读订单表已经切换到读 Master,所以不再出现读不到订单的问题。
3、主备延迟来源
在备库上执行 show slave status 命令查看 Seconds_Behind_Master 字段可以知道当前备库的延迟时间。
主备延时包含两部分时间损耗:主机传输 binlog 日志到备库;备机接收完 binlog 和执行完这个事务。在网络正常情况下,第一部分耗时很短,主备延迟主要由第二部分引起,也就是备库 sql_thread 线程消费 relay log 的速度比主库生产要慢。
除了上面业务中出现的场景会导致这种问题,还有哪些场景也会导致主备延迟?
1、备机机器性能比主机差,或者参数配置不同
2、备机负载压力大,比如在上面执行了数据统计分析语句、运维日常导出脚本
3、大事务,在主机上执行 1 min,在备机上可能重放数据也需要 1 min
参考
MySQL 45 讲:MySQL 是这么保证高可用的?