应急预案
本文范围
生产应急不是“谁命令熟谁上”,也不是“先重启试试”。真到现场,最怕的是大家都很忙,但没人知道第一刀该切哪里。
一般我会把应急拆成三句话:
- 先确认用户到底受了什么影响。
- 再选择最快、最可回退的止血动作。
- 恢复后再慢慢查根因、补监控、补演练。
所以这篇不是某个组件的完整教程,而是一份可以拿到生产现场用的手册。生产故障、发布失败、数据库异常、Redis 异常、MQ 积压、磁盘满、CPU 和内存飙高、证书过期、Nginx 入口异常、第三方依赖不可用、备份恢复、演练和复盘都会讲,但每一节都只围绕一件事:出了问题怎么判断、怎么止血、怎么验证。
本文不替代各组件的完整部署手册,不展开业务补偿平台、容灾架构设计、法务合规流程或组织级 SRE 制度。这里聚焦一线应急:分级、止血、回滚、恢复验证、证据留存、复盘闭环。
正式动手前,先在机器上执行下面几条命令:
whoami
hostname -f
date
pwd这几条命令看起来很普通,但生产上很有用。whoami 看当前用户权限,hostname -f 看你到底在哪台机器,date 方便记录时间线,pwd 避免在错误目录下执行脚本。
这里要注意:应急时不要一上来就复制命令。先确认环境、实例、命名空间和业务系统。命令没错,机器错了,事故会变成二次事故。
再看常用工具有没有:
command -v systemctl
command -v journalctl
command -v docker
command -v kubectl
command -v mysql
command -v redis-cli如果某个命令没有输出,说明当前机器可能没装这个工具,或者不在 PATH 里。内网环境尤其常见,后面会专门讲应急工具包怎么准备。
前置条件
真正的应急预案不是事故发生后才翻文档。至少提前准备好这些东西:
- 已指定应急指挥人、记录人、执行人、业务确认人和对外沟通人,事故群和升级电话可用。
- 已准备只读排障账号和受控应急提权账号,紧急权限有启用、记录、回收和复盘机制。
- 核心系统的 RPO、RTO、降级目标和最低可接受服务状态已经写清楚。
- 发布包、回滚包、数据库变更脚本、配置快照、容器镜像、证书和依赖包可在内网取到。
- 监控、日志、链路追踪、备份系统和发布系统的入口、账号、备用访问方式已经纳入预案。
- 常用止血动作有命令模板和回滚模板,例如切流、回滚、扩容、暂停任务、临时限流、恢复备份。
- 证据留存方式明确,包括时间线、命令输出、告警截图、日志片段、变更记录和业务验证结果。
- 第三方供应商、云厂商、网络专线、短信/支付/身份服务的联系人和 SLA 已定期校验。
- 预案至少在隔离环境跑过演练,确认命令能执行、权限够用、工具包不缺依赖。
预案分级怎么定
分级不是为了写制度,而是为了决定“谁必须到场、多久同步一次、先恢复到什么程度”。如果现场还在争论这算 P1 还是 P2,说明预案还没准备好。
一般可以这样分:
| 等级 | 怎么判断 | 现场动作 |
|---|---|---|
| P0 | 核心业务整体不可用,或者有资金、数据、合规风险 | 立即拉群,冻结发布,技术负责人和业务负责人到场 |
| P1 | 核心链路部分不可用,影响明显,但还有降级或绕行方案 | 10 分钟内响应,优先止血和回滚 |
| P2 | 非核心功能异常,影响有限 | 当日修复或给出规避方案 |
| P3 | 风险预警,还没造成直接影响 | 排期处理,形成任务闭环 |
分级一定要绑定指标,不然全靠感觉。比如:
HTTP 5xx 连续 5 分钟超过 2%:至少 P1
核心接口 P95 延迟超过平时 3 倍:至少 P1
磁盘使用率超过 95% 且写入失败:P0 或 P1
证书已过期:对外入口一般直接 P0
MQ 积压持续增长并影响核心链路:至少 P1这里要注意,阈值不要抄别人的。一个日请求百万的系统和一个内部后台系统,错误率阈值不应该一样。先用历史监控看基线,再定告警。
角色分工也要提前定好:
| 角色 | 主要干什么 |
|---|---|
| 应急负责人 | 判断等级、拍板止血动作、控制节奏 |
| 主操作人 | 执行生产命令,一次只允许一个人操作 |
| 观察人 | 盯监控、日志、业务指标,看动作有没有效果 |
| 记录人 | 记录时间线、命令、现象和结论 |
| 沟通人 | 对业务、客服、管理层同步影响和恢复进展 |
生产上最忌讳“大家都能操作”。P1 以上故障,主操作人只能有一个,其他人可以复核命令、看日志、盯监控,但不要同时开终端改生产。
应急响应的指挥链路要比命令更早确定。下面这张图不是组织架构图,而是事故现场的信息流和决策流:告警进来后,指挥人先定级和冻结变更,主操作人只执行被确认的止血动作,观察人持续看指标,记录人保存时间线,业务确认人判断用户是否真正恢复。
这条链路可以用很朴素的方式验证:事故群里是否已经明确 5 个名字,命令执行前是否有人复核目标环境,动作后是否有人用同一套指标确认恢复。现场至少保留这些证据:
date -Is
hostname -f
whoami
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}\n' https://example.com/health
kubectl rollout history deployment/app -n prod 2>/dev/null || true常见坑是沟通人和主操作人是同一个人,结果执行人一边改生产一边解释进展;或者观察人只盯服务状态,不看业务成功率。生产约束是:P1 以上故障必须单一主操作、固定同步节奏、高风险命令复核、证据先保存再破坏现场。
故障发现后先做什么
收到告警以后,先不要问“根因是什么”。第一步先问:“用户现在是不是还在失败?”
对外 HTTP 服务,先用 curl 看健康检查:
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}\n' https://example.com/health
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}\n' https://example.com/api/ping执行后应该看到类似:
code=200 time=0.083code 是状态码,time 是总耗时。如果 code 是 5xx,说明服务端已经失败;如果 time 很大但状态码是 200,说明服务还活着,但可能已经慢到用户不可接受。
如果是内网服务,就从调用方所在机器执行:
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}\n' http://service-name:8080/actuator/health
nc -vz service-name 8080nc -vz 主要看端口通不通。端口不通时,先查服务是否启动、服务发现是否正常、防火墙或安全组有没有变化。
再看系统基本状态:
uptime
free -h
df -h
df -ih这几条怎么读:
uptime看负载,负载长期高于 CPU 核数很多,要怀疑 CPU、IO 或锁等待。free -h看内存,available持续很低,要怀疑内存泄漏、缓存膨胀或 OOM。df -h看磁盘空间。df -ih看 inode,inode 满了以后,即使磁盘还有空间,也会写不进去文件。
如果是 Kubernetes 服务,先看 Pod 状态:
kubectl get pods -A -o wide
kubectl get events -A --sort-by=.lastTimestamp | tail -50CrashLoopBackOff 说明容器反复启动失败,下一步看 --previous 日志;ImagePullBackOff 多半是镜像仓库、镜像名、认证或网络问题;Pending 要看资源不足、亲和性、污点或 PVC。
kubectl logs -n prod app-xxxxx --previous --tail=200
kubectl describe pod -n prod app-xxxxx--previous 很关键。很多应用一启动就崩,普通 kubectl logs 只能看到当前容器,真正的异常在上一个崩掉的容器里。
前 15 分钟怎么打
生产故障刚开始的 15 分钟,不适合开长会,也不适合做深度根因分析。先按固定节奏来。
第一步,确认影响:
date
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}\n' https://example.com/health
kubectl get pods -n prod -l app=app -o wide
df -h
free -h这一步要产出一句话:现在影响的是哪个业务、哪个接口、哪个地区、多少用户,还是只有后台任务。
第二步,确认最近变更:
kubectl rollout history deployment/app -n prod
kubectl get events -n prod --sort-by=.lastTimestamp | tail -30如果故障紧跟发布、配置变更、数据库脚本、证书替换、扩容、流量切换出现,先把变更当成最大嫌疑。这里不是说根因一定是它,而是回滚它通常最快。
第三步,选择止血动作:
新版本导致异常:优先回滚
单实例异常:先摘除或重启单实例
流量突增:先限流或扩容
下游不可用:先熔断、降级、异步补偿
磁盘满:先恢复写入能力
数据库慢查询拖垮:先关入口、限流、杀明确异常 SQL如果现场同时出现多个异常,不要按声音最大的人来定动作。先按影响面和可回退性做决策:能切流就先切流,能降级就先降级,涉及数据的动作必须先保留证据和确认 RPO/RTO。
这张图不是替代组件手册,而是帮你在混乱现场先选止血方向。真正执行前仍要落到具体命令:入口看 curl、Nginx 和网关日志;数据库看慢 SQL、锁等待和连接数;MQ 看 lag、队列深度和消费错误;资源类故障看 df、free、top、容器和 Pod 事件。
第四步,动作后立刻验证:
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}\n' https://example.com/health
kubectl get pods -n prod -l app=app如果执行动作 5 分钟后指标没有恢复趋势,不要硬等。升级等级,扩大应急范围,让一个人继续恢复,一个人继续定位。
记录人要从第一分钟开始记:
10:01 告警触发,核心下单接口 5xx 升高
10:03 确认最近发布版本 v2026.07.09-1
10:05 暂停发布,保留异常 Pod 日志
10:08 回滚 deployment/app 到上一版本
10:12 错误率下降,订单成功率恢复复盘时最值钱的不是“谁说了什么”,而是“哪个时间点做了哪个动作,指标怎么变化”。
沟通要说人话
沟通不是把技术细节倒出去,而是告诉相关方:影响什么、正在做什么、什么时候再同步。
首次通报可以这样写:
【故障通报】
时间:2026-07-09 10:05
等级:P1
影响:核心下单接口错误率升高,部分用户下单失败
范围:生产环境,影响约 20% 请求
当前动作:已暂停发布,准备回滚到上一稳定版本
下一次同步:10:15 前过程中这样写:
【故障进展】
时间:2026-07-09 10:15
当前状态:已完成版本回滚,错误率从 12% 降到 1.5%
仍有影响:少量历史失败订单需要补偿
下一步:继续观察 15 分钟,同时排查新版本变更点
下一次同步:10:30 前恢复后这样写:
【故障恢复】
时间:2026-07-09 10:32
恢复结果:核心下单接口错误率恢复到基线,订单成功率恢复
后续动作:补偿失败订单,24 小时内输出复盘
注意事项:发布窗口暂时冻结,待根因确认后恢复这里要注意:根因没确认就不要定性,没验证恢复就不要宣布恢复,没有下一次同步时间就容易让业务方焦虑。
发布失败预案
发布失败最常见。不要把所有失败都叫“发布失败”,先看它失败在哪一段:构建、制品、部署、健康检查,还是发布后业务指标异常。
先看 Kubernetes 发布状态:
kubectl rollout status deployment/app -n prod
kubectl rollout history deployment/app -n prod
kubectl get rs -n prod -l app=app
kubectl get pods -n prod -l app=app -o wide这几条分别解决什么问题:
rollout status看发布有没有卡住。rollout history看历史版本,后面回滚会用到。get rs看新老 ReplicaSet 副本数。get pods看 Pod 分布在哪些节点、状态是不是正常。
如果 Pod 起不来,继续看:
kubectl describe pod -n prod app-xxxxx
kubectl logs -n prod app-xxxxx --tail=200
kubectl logs -n prod app-xxxxx --previous --tail=200describe 重点看 Events。拉镜像失败、探针失败、调度失败都会写在这里。logs --previous 用来看上一次崩溃前的日志。
如果是 Helm 发布:
helm history app -n prod
helm status app -n prod
helm get values app -n prod如果确认是新版本导致业务异常,先回滚:
kubectl rollout undo deployment/app -n prod
kubectl rollout status deployment/app -n prod如果要回滚到指定版本,先看历史:
kubectl rollout history deployment/app -n prod
kubectl rollout undo deployment/app -n prod --to-revision=3
kubectl rollout status deployment/app -n prodHelm 回滚:
helm history app -n prod
helm rollback app 12 -n prod
helm status app -n prod传统 systemd 发布,一般会有一个 current 软链指向当前版本。如果没有就新建这套发布目录规范,不然回滚会很痛苦。
ls -l /opt/app/current
ln -sfn /opt/app/releases/20260709_0950 /opt/app/current
systemctl restart app.service
systemctl status app.service --no-pager执行后看 systemctl status 里是不是 active (running)。如果不是,马上看日志:
journalctl -u app.service -n 200 --no-pager发布失败恢复后,要验证业务,不要只看服务启动:
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}\n' https://example.com/health
kubectl logs -n prod deployment/app --tail=100 | grep -Ei 'error|exception|timeout'如果有订单、支付、登录这类核心业务,再查一眼业务数据:
SELECT COUNT(*) AS cnt
FROM orders
WHERE created_at >= NOW() - INTERVAL 5 MINUTE;这里要注意:应用回滚不等于数据库也能回滚。数据库脚本如果已经做了不兼容变更,只回滚应用可能会更坏。生产上建议用“先兼容、再切换、后清理”的方式做数据库变更。
数据库异常预案
数据库异常不要上来就重启数据库。数据库是有状态组件,重启可能会让恢复更慢,也可能丢掉现场。
先进入 MySQL 看当前连接和事务:
SHOW FULL PROCESSLIST;
SHOW ENGINE INNODB STATUS\G
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Threads_running';这里怎么看:
Threads_connected高,说明连接很多,可能连接池没释放,也可能流量太大。Threads_running高,说明真正正在跑的线程多,通常比 connected 更值得关注。SHOW FULL PROCESSLIST里Time很长、State异常的 SQL 要重点看。SHOW ENGINE INNODB STATUS重点看最近死锁和锁等待。
查看长事务:
SELECT
trx_id,
trx_state,
trx_started,
trx_mysql_thread_id,
trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started;如果有事务开了很久不提交,它可能占着锁,也可能导致 undo 堆积。先联系业务确认,再考虑 kill。
查看非 Sleep 会话:
SELECT
id,
user,
host,
db,
command,
time,
state,
info
FROM information_schema.processlist
WHERE command <> 'Sleep'
ORDER BY time DESC;如果发现某条 SQL 明确异常,比如全表更新、无索引查询跑了很久,先记录现场:
SHOW FULL PROCESSLIST;
SHOW ENGINE INNODB STATUS\G确认线程 ID 后再 kill:
KILL 123456;这里要注意,KILL 不是万能药。大事务被 kill 后可能要回滚很久,这段时间数据库压力仍然很高。能在应用入口限流、关闭触发入口,就先从入口止血。
慢 SQL 要看执行计划:
EXPLAIN
SELECT *
FROM orders
WHERE status = 'WAIT_PAY'
ORDER BY created_at DESC
LIMIT 100;重点看这几个字段:
type如果是ALL,大概率全表扫描。key如果是NULL,说明没用上索引。rows很大,说明扫描行数多。Extra出现Using filesort、Using temporary,要结合数据量判断风险。
死锁一般不是靠“加大参数”解决,而是靠统一加锁顺序、缩短事务、补索引、减少事务内外部调用。SHOW ENGINE INNODB STATUS\G 里最近一次 deadlock 会告诉你两个事务分别持有什么锁、等什么锁。
恢复验证:
SHOW GLOBAL STATUS LIKE 'Threads_running';
SHOW GLOBAL STATUS LIKE 'Threads_connected';
SHOW GLOBAL STATUS LIKE 'Slow_queries';应用侧再验证:
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}\n' https://example.com/api/core生产建议:
- 核心库必须有慢 SQL 告警。
- 发布前高风险 SQL 要看执行计划。
- 批任务要有限速和暂停开关。
- 应用连接池要有最大连接数、等待时间和泄漏监控。
- 恢复数据前先恢复到临时实例,不要直接覆盖生产。
Redis 异常预案
Redis 出问题,表现通常是接口突然慢、缓存命中率下降、数据库压力升高、连接池打满。不要直接 KEYS *,生产上这个命令可能把 Redis 卡住。
先确认 Redis 活不活:
redis-cli -h redis.example.com -p 6379 PING正常应该返回:
PONG再看整体状态:
redis-cli -h redis.example.com -p 6379 INFO clients
redis-cli -h redis.example.com -p 6379 INFO memory
redis-cli -h redis.example.com -p 6379 INFO stats
redis-cli -h redis.example.com -p 6379 INFO replication这几段分别看:
clients看连接数和 blocked clients。memory看 used memory、maxmemory、碎片率。stats看 QPS、拒绝连接、过期 key、淘汰 key。replication看主从角色和复制状态。
查慢命令:
redis-cli -h redis.example.com -p 6379 SLOWLOG GET 10
redis-cli -h redis.example.com -p 6379 LATENCY DOCTORSLOWLOG GET 能看到最近慢命令,LATENCY DOCTOR 会给延迟诊断建议。注意,慢日志只是 Redis 执行命令的耗时,不包含客户端排队、网络和应用处理时间。
查大 key:
redis-cli -h redis.example.com -p 6379 --bigkeys
redis-cli -h redis.example.com -p 6379 MEMORY USAGE key:name如果需要按前缀扫 key,用 SCAN:
redis-cli -h redis.example.com -p 6379 --scan --pattern 'prefix:*' | head大 key 不要高峰期直接 DEL,优先用异步删除:
redis-cli -h redis.example.com -p 6379 UNLINK key:nameRedis 的止血思路一般是:
慢命令:停掉危险入口,优化访问方式
热 key:本地缓存、请求合并、限流、拆 key
大 key:低峰拆分或迁移,高峰只做止血
内存满:临时扩容,检查淘汰策略,清理可删除 key
缓存雪崩:加随机过期时间,预热热点数据,应用限流验证恢复:
redis-cli -h redis.example.com -p 6379 INFO stats | grep -E 'instantaneous_ops_per_sec|rejected_connections|expired_keys|evicted_keys'
redis-cli -h redis.example.com -p 6379 INFO memory | grep -E 'used_memory_human|maxmemory_human|mem_fragmentation_ratio'
redis-cli -h redis.example.com -p 6379 SLOWLOG GET 5这里要注意:Redis 恢复不代表系统恢复。缓存故障期间,数据库可能已经被打出慢查询或连接耗尽,所以还要看数据库和核心接口。
MQ 积压预案
MQ 积压先不要急着扩消费者。先判断是生产太快、消费太慢、消费者异常、下游不可用,还是某条毒性消息一直重试。
Kafka 看消费组:
kafka-consumer-groups.sh \
--bootstrap-server kafka1:9092 \
--describe \
--group order-consumer重点看三列:
CURRENT-OFFSET:当前消费到哪里。LOG-END-OFFSET:队列最新位置。LAG:还差多少消息没消费。
如果所有分区 lag 都涨,说明整体消费能力不够;如果只有一个分区涨,可能是 key 分布不均、单分区热点,或者顺序消费卡住。
RocketMQ 看消费进度:
mqadmin consumerProgress -n namesrv:9876 -g order-consumer
mqadmin topicStatus -n namesrv:9876 -t order-topic
mqadmin consumerStatus -n namesrv:9876 -g order-consumerRabbitMQ 看队列:
rabbitmqctl list_queues name messages messages_ready messages_unacknowledged consumers
rabbitmq-diagnostics alarmsmessages_ready 高,说明待消费消息多;messages_unacknowledged 高,说明消费者拿到了消息但没确认,一般要查消费者处理慢、异常或下游超时。
止血时按这个顺序来:
- 先限制生产入口,不要让积压继续扩大。
- 再扩消费者或提高线程数。
- 如果下游不可用,暂停消费或转补偿,不要把下游打死。
- 如果是毒性消息,隔离到死信队列或补偿表。
- 如果要跳过消息或重置 offset,必须确认业务允许。
扩容消费者前先确认幂等。最简单的办法是用业务唯一键:
CREATE UNIQUE INDEX uk_order_event ON processed_event(event_id);Kafka 重置 offset 一定先 dry run:
kafka-consumer-groups.sh \
--bootstrap-server kafka1:9092 \
--group order-consumer \
--topic order-events \
--reset-offsets \
--to-latest \
--dry-run确认影响后再执行:
kafka-consumer-groups.sh \
--bootstrap-server kafka1:9092 \
--group order-consumer \
--topic order-events \
--reset-offsets \
--to-latest \
--execute这里要注意:--to-latest 会跳过积压消息。它能快速止血,但也可能造成业务数据缺口。生产上要有补偿方案和业务确认。
验证 MQ 恢复时,不是看“lag 变小了”就结束,还要算清空时间:
预计清空时间 = 当前积压量 / (消费速率 - 生产速率)如果消费速率仍然小于生产速率,等待没有意义,必须限流、扩容或降级。
磁盘满预案
磁盘满是很容易处理错的故障。先分清楚是空间满、inode 满,还是文件已经删除但仍被进程占用。
先看空间和 inode:
df -h
df -ihdf -h 看磁盘空间,df -ih 看 inode。inode 可以理解为文件数量额度,日志切得太碎、小文件太多时,空间没满也可能写不进去。
继续定位大目录:
du -xhd1 / 2>/dev/null | sort -h
du -xhd1 /var 2>/dev/null | sort -h
du -xhd1 /var/log 2>/dev/null | sort -h
find /var/log -type f -size +500M -print 2>/dev/null-x 表示不要跨文件系统,避免把挂载的其他盘也算进去。一般先从 /var、日志目录、数据目录查起。
如果你明明删了文件,但空间没释放,查这个:
lsof +L1这条命令会列出“已经删除但还被进程打开”的文件。处理方式通常不是继续删,而是重启持有文件的服务,让文件句柄释放。
Docker 环境再看:
docker system df
docker ps --size
docker inspect app --format '{{.LogPath}}'这里顺便讲一下为什么容器也会把磁盘打满。Docker 使用 Overlay2 这类联合文件系统,镜像层是只读的,容器启动后会多一层可写层。正常情况下多个容器共享镜像层,很省空间;但应用在容器里不断写日志、写临时文件,都会落到容器可写层。CoW 的好处是节省镜像空间,代价是写入的数据如果不落到挂载卷,可能把容器层越写越大。
所以看到容器磁盘异常时,要重点看三类东西:
容器 json 日志
容器可写层
挂载卷里的业务数据清理日志前先保留现场:
tail -n 5000 /var/log/app/app.log > /tmp/app-log-tail-$(date +%Y%m%d%H%M%S).log
truncate -s 0 /var/log/app/app.logtruncate -s 0 会清空文件内容但保留文件本身,适合正在被进程写的日志文件。不要直接 rm 当前日志,因为进程可能继续写到已删除文件句柄里。
systemd journal 可以这样清:
journalctl --disk-usage
journalctl --vacuum-size=2G
journalctl --disk-usageDocker 清理先看占用,再谨慎执行:
docker system df
docker image prune -a
docker container prune不要随手执行:
docker system prune -a --volumes这个命令会清理未使用卷,生产上可能把数据卷删掉。除非你非常确定卷不再使用,并且有备份。
验证磁盘恢复:
df -h
df -ih
touch /var/log/.disk-write-test
rm -f /var/log/.disk-write-test恢复后要补日志轮转:
应用日志按大小和天数轮转
Docker json-file 配置 max-size 和 max-file
journal 设置保留空间
磁盘 80%、90%、95% 分级告警CPU 和内存飙高预案
CPU 高和内存高,不要只看平均值。要先定位是哪台机器、哪个进程、哪个线程。
先看整机:
uptime
top
vmstat 1 5top 里重点看 %Cpu、load、占用最高的进程。vmstat 里 r 很高说明运行队列长,wa 高说明 IO 等待高。
找 CPU 最高进程:
ps -eo pid,ppid,cmd,%cpu,%mem --sort=-%cpu | head如果是 Java 进程,继续找高 CPU 线程:
jps -l
top -Hp <PID>
printf '%x\n' <TID>
jstack <PID> > /tmp/app-jstack-$(date +%Y%m%d%H%M%S).log
grep -A 30 -i '<tid_hex>' /tmp/app-jstack-*.log这里的流程是:先拿 Java 进程 PID,再用 top -Hp 找线程 ID,然后把线程 ID 转成 16 进制,因为 jstack 里线程号是 16 进制。最后在 jstack 里看它到底卡在哪段代码。
常见结果这样判断:
单线程 100%:死循环、正则灾难、JSON 大对象处理
多线程高 CPU:流量突增、线程池过大、批任务失控
CPU 高且频繁 GC:内存压力或对象创建过快
load 高但 CPU 不高:IO 等待、锁等待、不可中断等待内存先看:
free -h
ps -eo pid,ppid,cmd,%mem,rss,vsz --sort=-%mem | head
dmesg -T | grep -i -E 'oom|out of memory' | tail -50如果看到 OOM 记录,说明进程可能已经被系统杀过。容器环境还要看容器限制:
docker inspect app --format '{{.HostConfig.Memory}}'
kubectl describe pod -n prod app-xxxxx | grep -A 5 -E 'Limits|Requests'Java 内存看:
jcmd <PID> GC.heap_info
jstat -gcutil <PID> 1000 5
jcmd <PID> Thread.print > /tmp/app-thread-$(date +%Y%m%d%H%M%S).log如果要 dump 堆,先看磁盘空间:
df -h /tmp
jcmd <PID> GC.heap_dump /tmp/app-heap-$(date +%Y%m%d%H%M%S).hprof这里要注意,堆转储可能很大,也会影响进程性能。P0 现场如果用户还在失败,先止血:回滚、限流、摘除实例、扩容副本。证据能保留就保留,不能为了保留证据让故障继续扩大。
恢复验证:
uptime
free -h
ps -eo pid,cmd,%cpu,%mem --sort=-%mem | head
kubectl top pods -n prodCPU 和内存降下来以后,还要看接口延迟、错误率、队列积压。资源恢复只是技术指标,业务恢复才算真正恢复。
证书过期预案
证书过期的现象很直接:浏览器报不安全,客户端握手失败,回调失败,移动端请求失败。处理时不要只看一个入口,很多系统有公网域名、内网域名、回调域名、管理后台域名。
先看线上实际加载的证书:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -dates -issuer -subject这里一定要带 -servername,因为很多网关是 SNI 多证书,不带的话可能拿到默认证书。
再看本地证书文件:
openssl x509 -in /etc/nginx/ssl/fullchain.pem -noout -dates -issuer -subject
openssl x509 -in /etc/nginx/ssl/fullchain.pem -checkend 604800 -noout-checkend 604800 表示检查 7 天内是否过期。返回码是 0,说明 7 天内不会过期;非 0,说明即将过期或已经过期。
检查 Nginx 有没有引用正确证书:
nginx -t
nginx -T | grep -nE 'ssl_certificate|ssl_certificate_key|server_name'如果使用 Certbot,先做续期演练:
certbot renew --dry-run替换证书后 reload:
nginx -t
nginx -s reloadsystemd 管理的 Nginx:
systemctl reload nginx
systemctl status nginx --no-pager验证:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -dates -issuer -subject
curl -Iv https://example.com/health常见坑:
- 只换了证书文件,没有 reload Nginx。
- 证书链不完整,浏览器可能能打开,部分客户端失败。
- 证书域名不匹配,SAN 里没有对应域名。
- 服务器时间不对,导致证书看起来未生效或已过期。
- 自动续期依赖 80 端口、DNS 或文件权限,生产上没提前演练。
生产建议是证书到期至少提前 14 天告警,提前 7 天必须人工确认,提前 1 天还没续上直接升级。
Nginx 入口异常预案
Nginx 是很多系统的第一入口。入口挂了,后面应用再健康也没用。
先看配置有没有语法问题:
nginx -t如果输出 syntax is ok 和 test is successful,说明语法层面没问题。语法没问题不代表业务没问题,还要看服务状态:
systemctl status nginx --no-pager
journalctl -u nginx -n 200 --no-pager
ss -lntp | grep -E ':80|:443'ss 看 80 和 443 是否被监听。如果没有监听,可能 Nginx 没起来,也可能端口被别的进程占用。
看错误日志和访问日志:
tail -n 100 /var/log/nginx/error.log
tail -n 100 /var/log/nginx/access.log统计状态码:
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head如果 499 多,通常是客户端提前断开,可能后端太慢;如果 502 多,通常是上游不可达或连接失败;如果 504 多,通常是上游超时。
配置变更前先备份:
cp /etc/nginx/conf.d/app.conf /etc/nginx/conf.d/app.conf.$(date +%Y%m%d%H%M%S).bak回滚配置:
cp /etc/nginx/conf.d/app.conf.20260709100000.bak /etc/nginx/conf.d/app.conf
nginx -t
nginx -s reload这里要注意:不要跳过 nginx -t。Nginx reload 如果配置有问题,可能失败;更麻烦的是你以为 reload 了,实际还是旧配置在跑。
验证入口:
curl -sS -o /dev/null -w 'code=%{http_code} time=%{time_total}\n' https://example.com/health
tail -f /var/log/nginx/access.log
tail -f /var/log/nginx/error.log如果 Nginx 恢复后错误率仍然高,继续查上游应用。入口 200 不等于业务完全恢复。
第三方依赖不可用预案
第三方依赖包括支付、短信、地图、OCR、对象存储、外部接口、身份认证、制品仓库、镜像仓库。它们不在你手里,所以预案重点不是“修好第三方”,而是“别让自己的系统被拖死”。
先从生产机器上测,不要只在本地电脑测:
curl -v --connect-timeout 3 --max-time 5 https://api.vendor.com/health
curl -sS -o /dev/null -w 'code=%{http_code} dns=%{time_namelookup} connect=%{time_connect} total=%{time_total}\n' https://api.vendor.com/health
dig api.vendor.com
nc -vz api.vendor.com 443这几条怎么读:
time_namelookup高,先查 DNS。time_connect高,先查网络、出口、安全策略。total高但 connect 不高,通常是第三方处理慢。nc不通,说明 TCP 层就有问题。
应用配置一般需要有超时、重试、熔断:
vendor:
connectTimeoutMs: 1000
readTimeoutMs: 3000
retry:
maxAttempts: 2
backoffMs: 200
circuitBreaker:
failureRateThreshold: 50
waitDurationSeconds: 30这里要注意,重试不是越多越好。第三方已经慢了,你再无限重试,会把自己的线程池、连接池也打满。
止血动作一般是:
核心依赖失败:切备用供应商,或者进入人工处理
非核心依赖失败:降级,不阻塞主链路
回调失败:落补偿表,后续重放
制品仓库失败:暂停发布,使用内网缓存或备用仓库
镜像仓库失败:使用本地缓存镜像或备用仓库补偿表可以这样设计:
CREATE TABLE vendor_retry_task (
id BIGINT PRIMARY KEY,
biz_type VARCHAR(64) NOT NULL,
biz_id VARCHAR(128) NOT NULL,
request_body TEXT NOT NULL,
status VARCHAR(32) NOT NULL,
retry_count INT NOT NULL DEFAULT 0,
next_retry_at DATETIME NOT NULL,
last_error TEXT,
created_at DATETIME NOT NULL,
updated_at DATETIME NOT NULL
);验证恢复时看四个指标:第三方调用错误率、本系统线程池、本系统连接池、补偿任务数量。第三方接口通了,但补偿任务没处理完,业务还没有完全收口。
备份恢复预案
备份不是为了“有文件”,而是为了在故障时能恢复。这里先记住两个指标:RTO 是最多多久恢复,RPO 是最多能丢多少数据。
先看备份文件是不是存在:
ls -lh /backup/mysql
find /backup/mysql -type f -mtime -2 -print再做校验:
sha256sum /backup/mysql/app_db.sql.gz
gzip -t /backup/mysql/app_db.sql.gzgzip -t 能检查压缩包是否损坏,但不能证明数据一定能恢复。真正可靠的办法是恢复到临时实例。
MySQL 逻辑备份示例:
mysqldump --single-transaction --routines --triggers --events \
-h 127.0.0.1 -u backup -p app_db | gzip > app_db-$(date +%F).sql.gz--single-transaction 适合 InnoDB,可以在不锁全库的情况下拿一致性快照。这里要注意,大表备份会占 IO 和网络,生产上要评估时间窗口。
恢复到临时库:
gunzip -c app_db-2026-07-09.sql.gz | mysql -h 127.0.0.1 -u root -p app_db_restore恢复后校验:
SELECT COUNT(*) FROM orders;
SELECT MAX(created_at) FROM orders;
CHECKSUM TABLE orders;配置文件也要备份:
tar -czf nginx-conf-$(date +%F).tar.gz /etc/nginx
sha256sum nginx-conf-$(date +%F).tar.gz > nginx-conf-$(date +%F).tar.gz.sha256查看压缩包内容:
tar -tzf nginx-conf-2026-07-09.tar.gz | head这里要注意,误删数据时不要直接把备份覆盖回生产。一般做法是先恢复到临时实例,对比差异,再生成补偿 SQL 或补偿任务。
备份恢复预案必须写清楚:
备份在哪里
谁有权限取
密钥怎么申请
恢复到哪里
是否需要暂停写入
恢复后谁验收
恢复失败走哪条备用路径中国大陆、内网和离线环境方案
内网和离线环境的应急难点不是命令不会写,而是现场拿不到依赖:镜像拉不下来,软件包装不上,证书签不了,文档打不开,远程支持进不来。
所以一般要提前准备一个应急资产包:
基础工具:curl、wget、vim、tar、unzip、jq、sysstat、lsof、tcpdump
组件客户端:kubectl、helm、mysql、redis-cli、MQ 管理工具
镜像:应用稳定版、基础镜像、调试镜像、Nginx、BusyBox
制品:最近稳定包、回滚包、数据库脚本、配置模板
文档:应急预案、拓扑图、端口表、账号申请流程、恢复步骤
校验:SHA256 文件、签名、版本清单到机器上先检查工具:
command -v curl
command -v jq
command -v lsof
command -v tcpdump
command -v kubectl
command -v helm
command -v mysql
command -v redis-cli内网镜像仓库要提前同步稳定镜像,不要等故障时才去公网拉:
docker tag nginx:1.27 registry.internal.example.com/base/nginx:1.27
docker push registry.internal.example.com/base/nginx:1.27Kubernetes 节点要验证能拉取:
kubectl run image-pull-test \
--image=registry.internal.example.com/base/nginx:1.27 \
--restart=Never \
-n prod
kubectl get pod image-pull-test -n prod
kubectl delete pod image-pull-test -n prod如果 Pod 是 ImagePullBackOff,先查镜像地址、镜像标签、imagePullSecret、节点到仓库网络。
离线证书也要提前准备。内网环境不能假设公网 ACME 一定可用,要确认根证书、中间证书、业务域名和 Java truststore。
keytool -list -keystore truststore.jks | head导入内网 CA:
keytool -importcert \
-alias internal-ca \
-file internal-ca.crt \
-keystore truststore.jks这里要注意,离线环境的预案不能写“下载某工具”“拉取某镜像”。要写清楚文件名、版本号、校验值和存放位置,但正式文章里不要暴露任何内部实际路径。
演练怎么做
预案如果没演练,基本等于没预案。演练不用一开始就搞很大,先从最容易出事故的场景做。
每月可以演练这些:
应用回滚
磁盘满清理
证书续期
备份恢复到临时实例
Redis 慢命令排查
MQ 积压处理每次演练都要记录:
演练主题:
演练时间:
参与人员:
模拟故障:
预期 RTO:
预期 RPO:
执行步骤:
实际恢复时间:
发现问题:
改进动作:
负责人:
截止时间:演练要验证三件事:
- 人是否知道怎么做。
- 命令是否能执行。
- 恢复结果是否可验证。
只在会议里读一遍文档,不算演练。至少要有一个临时环境或隔离环境,把关键命令真的跑一遍。
复盘怎么写
复盘不是追责,而是把同类事故变得更难发生。复盘里最重要的是时间线、根因、改进项。
模板可以这样写:
故障标题:
故障等级:
发生时间:
发现时间:
恢复时间:
影响范围:
用户影响:
直接原因:
根因:
触发因素:
止血动作:
恢复动作:
验证方式:
做得好的地方:
暴露的问题:
改进项:
负责人:
截止时间:
验证标准:时间线要具体:
10:01 监控触发告警
10:03 值班人员确认核心接口 5xx 升高
10:05 拉起 P1 应急群,暂停发布
10:08 执行回滚
10:12 错误率恢复到 1% 以下
10:20 确认根因为新版本 SQL 未命中索引
10:40 补充索引方案和回归用例改进项不要这样写:
加强监控。
提高发布质量。
注意数据库变更。要这样写:
为 order-service 增加核心接口 5xx 和 P95 告警,阈值为 5 分钟 5xx 超过 2%,负责人张三,2026-07-15 前上线。
发布流水线增加 SQL 执行计划检查,发现全表扫描阻断发布,负责人李四,2026-07-20 前上线。
把 order-service 回滚命令加入应急脚本,演练通过后纳入发布手册,负责人王五,2026-07-18 前完成。一句话:改进项必须有负责人、截止时间和验证标准。
落地工程深水区
应急现场最难的不是命令,而是在压力下不制造二次事故:
- 先止血,再根因。用户影响仍在扩大时,优先回滚、切流、限流、降级、扩容或暂停任务;根因分析可以在服务恢复后继续。
- 时间线要用统一时间源。所有关键动作都记录绝对时间,最好同时保留告警时间、发现时间、响应时间、止血时间、恢复时间和确认时间。
- 证据不能被止血动作破坏。删除日志、清空队列、重启服务、覆盖数据前,要先保存关键日志、配置、指标截图和业务样本。
- 回滚不等于恢复。版本回滚后还要确认数据库结构、缓存、消息、任务、文件和第三方状态是否兼容旧版本。
- 数据恢复要绑定 RPO/RTO。恢复到哪个时间点、允许丢多少数据、哪些业务需要补偿,必须由技术和业务共同确认。
- 沟通要稳定节奏。P0/P1 事故中,固定频率同步“影响、动作、下一步、风险”,不要让执行人同时承担大量解释工作。
- 应急权限要复盘回收。临时账号、临时白名单、临时开口、临时绕过策略,恢复后都要有关闭记录。
- 每个改进项都要能验收。补监控、补限流、补回滚脚本、补演练,不写负责人、截止时间和验证标准,等于没有改进。
预案模板
系统基础信息模板:
系统名称:
业务负责人:
技术负责人:
值班群:
核心域名:
核心接口:
部署方式:Kubernetes / Docker / systemd / 其他
生产命名空间:
应用名称:
数据库:
Redis:
MQ:
第三方依赖:
监控面板:
日志入口:
链路追踪入口:
发布系统:
回滚方式:
备份位置:场景预案模板:
场景名称:
触发条件:
影响范围:
故障等级:
第一响应人:
应急负责人:
确认命令:
1.
2.
3.
止血动作:
1.
2.
3.
回滚方案:
1.
2.
3.
验证方式:
1.
2.
3.
风险动作:
1.
2.
3.
恢复后补偿:
1.
2.
3.
复盘关注:
1.
2.
3.发布回滚模板:
当前版本:
上一稳定版本:
发布开始时间:
发布人:
变更内容:
数据库变更:
配置变更:
回滚命令:
回滚验证:
不允许回滚的原因:
需要补偿的数据:备份恢复模板:
恢复对象:
备份时间:
备份文件:
校验值:
恢复目标:
是否暂停写入:
恢复命令:
恢复后校验 SQL:
业务验收人:
回退方案:最后一遍检查清单
事前检查:
核心服务有健康检查接口
核心接口有错误率、延迟、流量告警
发布系统支持暂停和回滚
数据库备份每日校验
至少每月做一次恢复演练
Redis 慢日志和大 key 有检查机制
MQ 积压有告警和清空时间估算
磁盘空间和 inode 都有告警
证书到期提前 14 天告警
Nginx 配置变更必须 nginx -t
第三方依赖有超时、熔断、降级
内网环境有镜像、制品、工具应急包事中检查:
已确认故障等级
已指定应急负责人
已冻结相关发布
已确认最近变更
已记录关键时间线
已选择止血动作
高风险命令已复核目标环境
每次动作后都有验证
已按约定频率同步进展
已保留必要现场证据事后检查:
业务指标恢复到基线
错误率和延迟恢复到基线
补偿任务有负责人和完成时间
发布冻结已明确解除条件
复盘已产出时间线和根因
改进项有负责人、截止时间、验证标准
监控、脚本、文档已更新
同类系统已排查官方文档入口
生产预案以本地手册为主,官方文档用来校准命令和版本差异:
| 主题 | 官方入口 |
|---|---|
| 应急响应框架 | NIST SP 800-61 Rev.3 |
| Kubernetes Deployment 和回滚 | Kubernetes Deployment、kubectl rollout undo |
| Nginx 配置检查和 reload | Nginx Beginner's Guide、Nginx command-line parameters |
| MySQL 备份恢复 | MySQL Backup and Recovery、mysqldump |
| Redis 慢日志和延迟 | Redis SLOWLOG GET、Redis LATENCY DOCTOR |
| Kafka 消费组和 Lag | Apache Kafka Basic Operations |
| RabbitMQ 告警 | RabbitMQ Alarms |
| RocketMQ 消费进度 | RocketMQ Admin Tool、RocketMQ Consumer Progress |
| Prometheus 告警 | Prometheus Alerting Rules、Alertmanager |
| systemd 日志 | journalctl man page |
| 磁盘空间 | df man page |
| Docker 日志和清理 | Docker json-file logging driver、docker system prune、docker stats |
| OpenSSL 证书检查 | openssl x509 |
| Certbot 续期演练 | Certbot instructions |
最后说一句
真正能救命的预案,不是写得厚,而是半夜被叫醒时也能照着做。先确认影响,再止血,再验证,再复盘。命令要能跑,结果要会看,失败时知道下一步去哪查,这才算生产可用。
