服务器系统修复
凌晨两点,服务复监控大屏突然跳出十七条告警。器系一台跑了三年多的统修数据库服务器,磁盘 I/O 延迟从正常的服务复 3ms 飙到 800ms,连接数卡在 1500 死活上不去。器系值班同事在群里甩了句“这台机器怕是统修要原地升天”,然后我爬起来连上 VPN,服务复开始了这场持续四个小时的器系修复。
服务器系统出问题,统修最怕的服务复不是故障本身,而是器系不知道故障从哪来。这台机器跑的统修是 CentOS 7,上面挂着 MySQL 5.7 和一套 Java 应用。服务复先看 top,器系负载 12.8,统修CPU 占用倒不高,但 iowait 占了 40%。iostat -x 1 一跑,sda 盘的 %util 直接顶到 100%,await 超过 500ms。典型的磁盘瓶颈。
但磁盘为什么会突然卡死?这台机器用的是 RAID 10,四块 SSD,按理说不至于。我翻了翻 dmesg,发现内核日志里有一串 EXT4-fs warning: sda1: orphan cleanup on readonly fs。文件系统出过问题,可能是上次非正常关机留下的烂摊子。再查 smartctl -a /dev/sda,SSD 的 Media_Wearout_Indicator 已经掉到 12%,Program_Fail_Count 涨了不少。盘快不行了。
问题定位到这里,思路就清晰了:不是软件层面的慢查询或锁竞争,是硬件在物理层面开始崩。SSD 的闪存颗粒磨损到一定程度,写入放大变得极其严重,垃圾回收跟不上,I/O 延迟就会像过山车一样往上窜。这台机器上的 MySQL 又是个写密集型的业务,每秒几千条 insert,等于在拿砂纸磨一块快报废的盘。
当务之急是先把业务稳住。我做了三件事:第一,临时把 MySQL 的 innodb_flush_log_at_trx_commit 从 1 改成 2,牺牲一点持久性换吞吐;第二,用 ionice 把 MySQL 的 I/O 优先级调到最高,别让其他杂七杂八的进程抢资源;第三,把 /var/log 和 /tmp 挂到内存盘上,减少对那块快挂的 SSD 的写入。这三招下去,iowait 从 40% 降到 15%,连接数终于能爬到 2500 了。
但这只是续命,不是治病。天亮之后,我联系了机房那边准备换盘。换盘的过程比想象中麻烦——RAID 10 虽然支持热插拔,但这台机器的 RAID 卡固件有个老 bug,新盘插进去之后重建速度被限制在 30MB/s,一块 480G 的盘要重建四个多小时。期间任何一块老盘再出问题,整个阵列就废了。那四个小时我基本没干别的,就盯着 MegaCli -LDInfo 的输出,看重建进度一格一格往前爬。
盘换完,文件系统也重新做了 fsck,但这事没完。我后来查了这台机器的采购记录,发现这批 SSD 是某品牌的企业级入门款,标称 DWPD 只有 0.3。也就是说,按每天全盘写入 0.3 次算,五年质保期内总写入量大概 260TB。而这台机器三年的实际写入量已经超过 400TB。采购的时候只看容量和接口,没算写入寿命,这是根子上的问题。
修复完成之后,我做了两件事:一是给所有数据库服务器加了 SSD 磨损度的监控,smartctl 的 Media_Wearout_Indicator 低于 20% 就告警;二是把 MySQL 的 binlog 和 redo log 拆到了单独的 SATA SSD 上,让写入压力分散开。系统修复不只是把故障灯摁灭,得把那个让灯亮起来的逻辑也一并拆掉。
现在那台机器还在跑,新换的盘是企业级高耐久型号,DWPD 标称 3。监控面板上那条 I/O 延迟曲线平得像条死人心电图,看着挺让人安心。