上周四凌晨两点十七分,我的手机像被鬼掐住喉咙一样疯狂震动。生产库挂了,二十三个从库全部失联。那会儿我刚躺下不到四十分钟,脑子里全是白天上线时那个诡异的告警曲线。说实话,那一刻我脑子里闪过的第一个念头不是怎么恢复,而是“完了,这个月又要通宵”。但真正让我后背发凉的,是我发现自己居然要一台一台手动恢复——二十三个实例,每个都要登录、检查、拉备份、回放日志。干到早上七点,才恢复了四个。那四个小时里,我无数次想抽自己:为什么平时不把批量恢复的脚本写好?为什么总觉得“手动也花不了多少时间”?

这事儿之后我花了两天时间,把过去三年踩过的坑全翻出来,整理了一套批量恢复的完整流程。今天写出来,不是教你怎么敲命令,而是想告诉你:批量恢复这事儿,真正的难点从来不在“恢复”本身,而在“批量”两个字上。你单独恢复一个库,哪怕数据量再大,也就是时间问题。但一旦数量上来了,各种幺蛾子就全冒出来了——有的实例配置不一样,有的备份文件命名不统一,有的甚至binlog格式都不同。你以为自己在做运维,其实你在做考古。
先说最基础的一步:摸清家底。我见过太多人一上来就写脚本,结果跑到一半发现这台机器的数据目录挂载点跟别的不一样,那台机器的MySQL版本是5.6有的是8.0。所以批量恢复的第一条铁律,不是写脚本,而是先做一次全面的资产盘点。你得知道每台机器的IP、端口、数据目录、配置文件路径、备份策略、binlog格式、GTID开关状态。把这些信息整理成一个统一的清单文件,最好用JSON或者YAML格式,别用Excel——你写脚本的时候还得解析Excel,那不是给自己找事吗?我自己的习惯是维护一个inventory.yml,每台机器一行,字段固定,格式统一。这个文件就是整个批量恢复的“宪法”,一切操作都以它为准。
盘点完之后,第二件事是统一备份文件的命名规则和存放位置。这事儿听起来简单,但真做起来特别容易翻车。我见过有人备份文件名带时间戳,有人带实例名,有人干脆就是一串哈希值。你说你写个脚本遍历目录,找最新的备份文件,结果正则写了半天,匹配不上。后来我想通了:与其去适配千奇百怪的命名,不如在备份阶段就统一规范。比如强制要求所有备份文件名必须是这种格式,存放路径统一在下。这样批量恢复脚本只需要按规则拼路径、拼文件名,逻辑清晰,不容易出错。当然,这需要你在备份环节就有这个意识,临时抱佛脚是改不了历史数据的。
接下来是真正的核心环节:并行度控制。很多人一听说批量恢复,第一反应就是“全速跑,能多快多快”。但现实是,如果你同时启动二十个恢复任务,每台机器都要解压备份、拉取binlog、回放事务,瞬间的IO和CPU压力能把存储打满。我上次就是这么干的——二十个任务同时跑,结果存储阵列直接报错,所有任务全部失败,比手动恢复还慢。所以并行度一定要控制,我建议先做一轮小规模测试,比如先跑两三个实例,观察一下存储的IOPS和网络带宽,然后根据实际情况调整并发数。我自己常用的做法是,用GNU parallel或者xargs -P参数控制并发数,比如表示同时跑五个任务。跑完一批再拉下一批,稳扎稳打。
再说说恢复过程中的状态监控。你以为把脚本丢出去就完事了?错了。批量恢复最怕的就是某个实例悄悄失败,而你还在傻乎乎地等结果。所以我强烈建议你在脚本里加上状态上报机制——每恢复完一步,就往一个状态文件里写一行记录,比如“实例A:备份解压完成”“实例B:binlog回放失败,错误码1234”。然后你用一个简单的watch命令或者写个轮询脚本,实时查看这些状态文件。更高级一点的做法是接入企业微信或钉钉的webhook,每完成一个实例就推一条消息到群里。这样你就算人在厕所,也知道进度到了哪儿。我上次就是靠这个,在凌晨四点半的时候,通过手机看到第15个实例恢复完成,心里那个踏实啊。
别忘了校验环节。恢复完之后,数据到底对不对,不能光看进程起来了就算完。你得做两件事:第一,检查关键表的行数和主键最大值,跟备份时的记录比对;第二,随机抽几张业务表做数据一致性校验,比如用或者写个简单的SQL对比。我见过最惨的一次,恢复完所有实例,业务方一查数据,发现某个分库的数据跟主库差了整整两天的量——就是因为恢复过程中binlog回放断了一个环节,但脚本没检测到。从那以后,我每次批量恢复完,都会写一个校验脚本,自动跑一遍全库的checksum,只要有不一致的就自动告警。虽然校验本身也要花时间,但比起事后被业务方追着骂,这点时间花得值。
还有个大坑,就是版本兼容性。你恢复的备份可能是MySQL 5.7的,但目标机器装的是8.0,或者反过来。虽然官方说8.0能读5.7的备份,但实际操作中经常碰到字符集、排序规则、甚至存储引擎的兼容问题。我建议在批量恢复之前,先写个脚本检查每台目标机器的MySQL版本和备份文件对应的版本,如果不匹配,要么先升级目标机器,要么用工具做版本转换。别嫌麻烦,这一步能帮你避免后面一大串莫名其妙的报错。另外,如果用的是物理备份工具(比如Percona XtraBackup),还要确认工具版本跟MySQL版本匹配,不然解压的时候直接报错,连恢复的边都摸不着。
我想说说批量恢复的“人”的因素。再好的脚本,再完善的流程,如果你自己慌了,全白搭。我见过一个同事,恢复过程中某个实例报了个他没见过的错,他当场就慌了,直接Ctrl+C把所有任务全停了——结果其他正在正常恢复的实例也被他搞挂了。所以我的建议是,脚本里一定要做好容错处理:单个实例失败,绝不能影响其他实例;错误信息要尽量详细,至少要包含实例名、错误码、错误描述、可能的解决方案。同时,你自己也要有个心理预期——批量恢复一定会出问题,关键不是“会不会出”,而是“出了之后怎么应对”。提前写好一份故障排查手册,把常见的错误码和对应的处理方案列清楚,真出事儿的时候照着查就行了。
写到这里,回头看看那次凌晨的惨痛经历,其实也挺感谢的。没有那次教训,我可能还在傻乎乎地手动恢复,觉得自己挺勤奋。但运维这行,勤奋从来不是核心竞争力,把重复劳动自动化、把不可控变成可控,才是真正的高效。批量恢复这件事,说到底就是把“恢复”这个动作拆解成一个个可复用的模块,然后用流程和脚本把它们串起来。今天的这些方法,你拿去用,不敢保证能让你完全睡个安稳觉,但至少下次再遇到批量恢复,你能从容地泡杯咖啡,看着脚本跑完,然后跟业务方说一句:“好了,数据没问题。”那感觉,比通宵达旦地敲命令强太多了。


