MySQL高可用架构实战:9年安全运维的自动容灾设计
|
跟产品经理吵完的下午三点十七分,我盯着双屏右屏那张去年在敦煌拍的鸣沙山照片——沙粒在光里浮着,像没做索引的慢查询日志。左屏是 Percona Toolkit 的输出窗口,滚到一半卡住了。键盘空格键按下去有滞涩感,得补第二下才出空格。这个我敢打包票:不是键帽松动,是底下的橡胶碗老化了。上个月换过一批,就这一个漏了。 改版上线翻车。上周三凌晨两点四十一,用户反馈支付页白屏,后端查日志发现主库切从库时延迟突增到八秒,但监控没告警——不是阈值设高了,是那个自定义的 `replica_lag_seconds` 指标压根没上报。因为去年四月我们砍掉了一台旧中控节点,顺手删掉了它上面跑的 `pt-heartbeat` daemon,没人同步更新 Prometheus 的 service discovery 配置。我说不准当时谁删的,但日志里 `systemctl status pt-heartbeat` 返回 `not found` 的时间戳,和运维变更单里“优化冗余服务”的描述对得上。我原以为删完就完了,结果半年后它用八秒延迟+白屏,把整条支付链路喂给 Sentinel 的 failover 逻辑嚼碎了吐出来。
文章配图,仅供参考 优胜劣汰?呵。不是说架构本身多优雅,是人太懒。你看 MHA 那套 failover 流程,master 故障检测靠三秒 ping 三次,中间插着两次 `SELECT @@server_id`——这玩意儿在 IO 压力大的时候能卡住四十毫秒以上。可没人改,因为改了要重测所有网络抖动场景。我们去年四月的自动容灾设计,本质就是把七套脚本、四份 Ansible Playbook、两套 Consul KV 触发器,全塞进一个叫 `autoheal.sh` 的文件里,再 chmod +x 扔进 cron。运行时它会随机选一个存活的 slave 启动 mysqld --skip-grant-tables ——注意,不是 `--skip-networking`,是跳授权表。所以它能在主库脑裂时强行接管,也能在权限混乱时把自己变成本地 root。这个我敢打包票:上周五下午测试环境执行了 `./autoheal.sh -f --force-local`,三分钟内它重建了 `mysql.user` 表里的六行记录,其中一行 host 是 `%`,密码字段写着 `81FEDCD51043F7196F3A267E327B07D5C264311C`——跟生产库里十年前那个弱口令 hash 一模一样。没人动过这个值。它抄的是 `/etc/my.cnf` 末尾注释里手写的 demo 密码。旁边同事去倒水,路过我工位时带歪了我桌上那盒刚拆封的酸辣粉。汤洒了一滴,在键盘右下角聚成个不规则的椭圆,刚好盖住 Ctrl 键。他边拧保温杯边说:“你那个心跳检测,能不能加个 checksum 校验?” 我摇头。加了更慢,而我们线上库的 binlog_format 是 MIXED,row-based event 里有些字段根本没声明 charset,checksum 一算就崩。这事去年四月就试过,崩在 `utf8mb4_0900_as_cs` 和 `utf8mb4_general_ci` 的 collation 冲突上。后来我们直接关了 verify-checksum,改成只校验 event header 的 length 字段。这操作听着离谱?可你知道测试同学上周提的 bug 里,第十七条写着:“切换后新主库插入中文,select 出来显示乱码,但 order by 还能走索引”。我没回。这不算 bug。这是兼容性债务的利息,得付。 再说个无关的:Chrome DevTools 此刻切在 Network tab,正在抓一个前端发的 /api/v2/orders/summary?_t=1712892101 的请求。Response size 显示 2.3 MB。我不点开,知道里面塞了三百七十二个订单的 JSON 数组,每个含五个嵌套对象,每个对象里都藏着一个未脱敏的 user_phone 字段。这事跟 MySQL 高可用没关系,但跟我们的容灾设计有关——因为 backup 脚本每小时 dump 一次 `orders` 表,而 dump 出来的 SQL 文件里 `INSERT INTO orders VALUES (...)` 那一行,电话号码明文躺在那里。没人加密。不是忘了,是 dump 时加 `--hex-blob` 会导致 restore 失败,因为某些字段类型在 MySQL 5.7 和 8.0 之间解析行为不一致。所以现在备份目录里最新那个 `orders_202404172200.sql.gz`,你 `zcat | grep '138' | head -1` 就能捞出真实号码。这个我说不准——当年定备份规范时,是 DBA、后端、法务三方签字,可法务签完就跳槽了,后端那哥们今年转去做 AI inference pipeline,只剩我对着备份磁盘空间报警单发呆。 说到AI……昨天 PR review 看一个 junior 提的变更:把配置中心的 MySQL 连接池超时从 30 秒调成 5 秒。理由是“提升故障感知速度”。我批了 LGTM。但晚上回家刷微博,看到有人写 CSS 命名规范,说 class 名别用 `.btn-primary-active-hover-focus-disabled` 这种组合技,因为维护者看到就会想删库跑路。我盯着手机屏幕笑出声——我们 `autoheal.sh` 里那行 `if [ "$(grep -c 'mysqld.is.alive' /var/log/mysql/error.log | tail -n1)" -eq 0 ]; then`,后面跟的 `kill -9 $(pgrep -f "mysqld.--port=3307")`,本质上也是同一种绝望的命名术。它管用,因为它粗暴,因为它不讲道理,因为三年前它救过凌晨四点的支付网关,所以没人敢动。 我现在打开终端,cd 到 `/opt/mysql-autoheal/scripts/`,输入 `ls -la | grep autoheal`,回车。列表第三行是 `autoheal.sh -> autoheal_v2.sh.bak-20240405`。我删掉这个软链接,重新指向 `autoheal_v3.sh`。新的版本把原来那三秒 ping 改成了基于 `SHOW SLAVE STATUS\\G` 里 `Seconds_Behind_Master` 的指数退避重试,加了两次 sleep,一次是 0.8 秒,一次是 1.3 秒。为什么是这两个数?因为去年四月我在咖啡馆用笔记本测过,0.8 秒时 TCP retransmit 能覆盖绝大多数局域网丢包,1.3 秒能等完 MySQL 的 innodb_log_flush_timeout 默认值。至于为什么没推上线?今天先不推。我要等下周一早上九点,PM 开需求评审会的时候,当着他面 `git checkout -b hotfix/autoheal-timing && make test && make deploy`。不是信不过脚本。是信不过我自己记不记得清,上次改这里是什么时候。 (编辑:站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |


