大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。
前年我跟过一次真实的断电。系统是两个数据中心加一个异地灾备。方案评审我参与了,里面写的切换时间是 30 秒。每一步都写了,脚本也提前写好,平时跑得也稳。说实话,我当时觉得定期演练有点多余。
那天真断电了,从主中心挂掉到业务恢复,我们花了 40 分钟。
事后复盘,我把这 40 分钟拆开看。真正执行切换的那一段,只用了 90 秒。有 12 分钟,是两边的人在确认"到底是不是真的要切"。还有将近 20 分钟,耗在应用那边的连接重建和数据对账上。方案里写的 30 秒,只算了执行这一段。
那次之后我改了个习惯。任何一套高可用方案,我都要求它在演练里跑一遍。跑不过的方案,写得再漂亮我也不敢签字。以前做设计的时候我们有个词叫走查。设计稿画得再好,也得有人拿着它逐屏点一遍才算验收。数据库的高可用也一样,方案写得再全,也得跑一遍才知道行不行。
先定义什么叫"正常"
演练最容易漏掉的一步,是事先定义稳态。稳态就是一组能说清楚的指标。它们落在设定范围内,就说明系统正常。没有这个判据,演练做完你也不知道过没过。更糟的是,你可能压根没发现演练把系统弄坏了。
指标要从业务层选,不能只看数据库层。数据库各项都平稳,业务可能已经是坏的。举个我遇到过的情形。主库被切成只读之后,数据库进程好好的,连接数正常,复制也没断。但所有写入都失败了。只看数据库指标,这次演练的结论会是"没有影响"。我一般盯这几个。
| 指标 | 看什么 | 采集方式 |
|---|---|---|
| 错误率 | 业务接口的失败占比 | 应用监控 |
| P99 延迟 | 尾部请求的耗时 | 应用监控 |
| 复制延迟 | 主从之间的位点差 | 心跳表或监控项 |
| 可写状态 | 当前主库能不能写 | 定期试写 |
| 数据一致性 | 主从关键表的行数与校验和 | 对账脚本 |
稳态基线要在注入前采一次,恢复后再采一次,逐项对比。
复制延迟这项要单独说。它是演练里最容易骗人的指标。Seconds_Behind_Master 这类字段有个坑。从库回放线程一停,它就显示成 NULL,看着像没有延迟。我更信心跳表。主库定时写一个时间戳,从库读出来算差值,这个数骗不了人。
-- 复制延迟:主库定时写心跳,从库读时间差
SELECT
TIMESTAMPDIFF(SECOND, ts, NOW()) AS repl_lag_seconds
FROM heartbeat_log
ORDER BY ts DESC
LIMIT 1;
注入点清单
演练的核心动作是注入故障。故障不是随便制造的,每一种注入都对应一个要验证的能力。
| 注入点 | 手法 | 验证什么 |
|---|---|---|
| 进程崩溃 | kill -9 数据库进程 | 编排层能不能在秒级发现并拉起 |
| 主库只读 | 打开 super_read_only | 写入失败会不会被业务感知并正确反馈 |
| 复制中断 | 停掉从库回放线程 | 复制延迟告警会不会触发 |
| 网络分区 | 屏蔽对端数据库端口 | 脑裂防护有没有生效 |
| 磁盘抖动 | 限制 IO 带宽或加延迟 | 慢查询和连接堆积的扩散速度 |
| 连接打满 | 开满连接数 | 应用侧的超时与重试策略 |
| 时钟漂移 | 人为偏移系统时间 | 依赖时钟的判定逻辑会不会出错 |
几种常用的注入手法:
# 注入点一:数据库进程直接崩掉
kill -9 $(cat /var/lib/mysql/mysqld.pid)
# 注入点二:主库置为只读,模拟存储故障后的保护状态
mysql -e "SET GLOBAL super_read_only = ON;"
# 注入点三:掐断本机与对端的数据库端口,模拟网络分区
iptables -A INPUT -p tcp --dport 3306 -j DROP
iptables -A OUTPUT -p tcp --sport 3306 -j DROP
网络分区这一项要多留个心。它验证的是脑裂防护。两个中心互相看不到。如果两边都认为自己该当主,数据就分叉了。演练时先把仲裁或投票机制的原理搞清楚,再动手。
爆炸半径一级一级放大
这是我最看重的一条纪律。演练的破坏力必须可控。方法是一级一级放大,上一级通过才允许进下一级。
| 级别 | 环境 | 允许的注入 |
|---|---|---|
| 一级 | 预发单实例 | 任意注入都可以 |
| 二级 | 预发集群 | 任意注入都可以 |
| 三级 | 生产从库 | 只做只读类注入,不碰主库 |
| 四级 | 生产主库 | 前三级的同一条剧本都跑过,且有完整回退 |
第一次演练绝不能在生产的核心主库上做。这句话我说过很多次,还是见过有人直接在主库上试 kill -9。他的理由是"我们有从库,切过去就行"。结果那套切换脚本从没在真实场景跑过。脚本里一个写死的 IP 没改,切换直接卡住。
剧本里必须先写中止条件
演练脚本不能只写"做什么",还要写"什么时候停"。要提前定三件事。哪几个指标一破就立刻回滚。谁有权喊停。喊停之后多久能回到稳态。这三个问题在演练开始前就要有明确答案,不能现场商量。
阈值我给个参考。错误率超过基线的两倍,P99 超过基线的三倍,复制延迟超过 30 秒,切换后关键表对账不一致。任何一条命中就中止,不犹豫。
喊停权要落到一个人头上,不能是"大家一起判断"。演练现场最怕的局面,是所有人都觉得该停,但没人开口。
数据安全的三条底线
演练前必须确认三件事。备份是真的能恢复的,不是备份任务显示成功。回退通道是通的,回退动作有人验过。演练产生的数据能回收,不会混进生产。第一件我要专门强调。备份任务的"成功"只代表文件写出来了。能不能恢复出来,是另一回事。我现在的做法是,演练开始前先在一个隔离环境里做一次真实恢复。恢复不出来的备份,等于没有备份。
一次完整的切换演练时间线
下面这张表是一场完整演练的时间线,从准备到恢复稳态。这是我每次评审都会拿出来看的东西。
| 时刻 | 动作 | 关注点 |
|---|---|---|
| T-3 天 | 冻结变更,确认备份可恢复,通知业务方 | 变更窗口 |
| T-30 分钟 | 采集稳态基线 | 基线快照 |
| T-5 分钟 | 确认回退通道、中止权归属 | 人员到位 |
| T-0 | 注入:主库进程 kill -9 | 告警是否触发、多久触发 |
| T+40 秒 | 检测完成,编排层发现主库不可用 | 检测耗时 |
| T+2 分钟 | 触发切换:VIP 漂移、连接串切换 | 切换开始时间 |
| T+4 分钟 | 验证新主可写,应用侧连接重建 | 应用恢复时间 |
| T+22 分钟 | 关键表对账:行数、校验和 | 数据差异 |
| T+30 分钟 | 业务指标回到基线范围 | 实际 RTO |
| T+45 分钟 | 复盘,逐段核对耗时 | 问题清单 |
这张表最有用的地方,是把 RTO 拆成了几段。检测一段,决策一段,执行一段,应用恢复一段,数据校验一段。方案里写的那个数字,通常只覆盖执行那一段。
| RTO 组成 | 方案里估的 | 实际测的 | 差在哪 |
|---|---|---|---|
| 检测 | 15 秒 | 40 秒 | 告警阈值设得太宽 |
| 决策 | 未计 | 12 分钟 | 没人被明确授权喊切 |
| 执行 | 30 秒 | 90 秒 | 脚本里有写死的 IP |
| 应用恢复 | 未计 | 18 分钟 | 连接池没配自动重建 |
| 数据校验 | 未计 | 8 分钟 | 校验脚本要手工跑 |
差距的来源基本都在这里。方案里那个 30 秒没有错,它只是定义得比业务感知到的范围窄。
避坑清单
别在生产主库上做第一次演练。爆炸半径永远从最小的那一级开始,一级一级放大。上一级的剧本没跑通,就不许进下一级。
演练脚本要和变更一起做版本管理。不然半年后有人问起来,当时注入了什么、跑了哪些步骤,没人答得上来。脚本进了版本库,演练才有可复现性。
最后一条是我自己搞错的。我有一次演练报告写得挺漂亮,问题清单列了七条。然后就没有然后了。半年后另一次演练,前三条问题原封不动又出现了一遍。那次我才意识到,演练报告不进变更流程,就是一张废纸。现在我的做法是,演练发现的问题当天就建任务,指定负责人和期限。下次演练先复核上一批问题的闭环情况。
写在最后
高可用不是架构图画出来的,是演练出来的。一份没有发现任何问题的演练报告,本身就是最大的问题信号。系统是活的,配置会漂,脚本会旧,人也会换。每次都演练出"一切正常",只说明两件事,要么没注入到点上,要么判据设得太松。
我现在判断一套高可用方案靠不靠谱,不看架构图上有几个圈。我看它有没有一份带时间线的演练记录。有记录的,说明被真实检验过。没有的,说明还停在纸上。
你们的高可用演练,最近一次是什么时候做的?评论区聊聊。
我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋