置顶生产问题排查手记:37 篇线上事故的完整排查过程(总目录与症状速查)
从 CPU 飙高、OOM、慢 SQL 到消息堆积、Docker 与 Nginx 故障,37 篇第一人称排查记录的总目录:分层排查地图、症状速查表、按分类索引。
真实生产问题的排查过程合集:现象、思路、命令、根因、复盘。既是笑话集,更是备忘录。
从 CPU 飙高、OOM、慢 SQL 到消息堆积、Docker 与 Nginx 故障,37 篇第一人称排查记录的总目录:分层排查地图、症状速查表、按分类索引。
客服转来一句“下单好慢”,怎么在半小时内定位到具体机构、具体接口、具体那一行调用?本文用一个完整时间线讲 traceId 贯穿(MDC、网关、Dubbo、MQ、线程池的坑)、Kibana KQL 检索、SkyWalking 看 span 耗时,并总结排查先问的 5 个问题和“定位漏斗”。
两类高频“灵异事件”:Nacos 配置改了不生效(dataId/group/namespace 对不上、缺 @RefreshScope、@Value 与 @ConfigurationProperties 的差异、本地缓存),以及服务下线后仍有流量(心跳摘除延迟、消费者缓存、优雅停机顺序)。附 curl 查询配置与实例的排查清单。
下游一个慢接口,叠加 Dubbo 超时与默认 retries=2(最多调 3 次),流量被放大、provider 200 线程被占满并报 Thread pool is EXHAUSTED,故障从一个服务扩散到整条链路。附排查命令、超时分层、集群容错与限流降级的修法。
同一个定时任务有时跑两遍、有时一遍都没跑、有时执行器在后台显示离线。沿着调度中心到执行器的链路,查注册地址、accessToken、路由与阻塞策略、misfire、执行超时,最后用幂等设计兜底。
日志写入突然全部失败,报 index read-only / allow delete (api),集群还变了 yellow。顺着 _cluster/health、_cat/allocation、allocation/explain 一路查到磁盘水位线,讲清 85/90/95 三道线、解除只读的正确姿势,以及分片过多这个隐形推手。
积分服务变慢,订单服务接口全部超时,CPU 却只有 5%。jstack 看到 200 个 Tomcat 线程都阻塞在 socket read 与连接池里,根因是没设超时、队列无界、没有隔离。附拒绝策略与超时、舱壁隔离的落地写法。
接口 P99 隔几分钟就冲到 3 秒,机器 CPU 却不高。开 GC 日志、jstat 看趋势,发现老年代被一个无界的静态 Map 缓存慢慢撑满,Full GC 越来越频繁。附 JDK8 与 JDK11+ 日志参数、G1 与 CMS 取舍。
运营点了一下“导出全部订单”,服务就抛出 Java heap space 并重启。靠 HeapDumpOnOutOfMemoryError 留住现场,用 MAT 的 Dominator Tree 揪出一个 List,最后改成分页 + EasyExcel 流式写出。
消息推送网关的堆使用率一直只有 40%,容器 RSS 却每天涨一截,最后被 OOMKilled,日志里还偶发 Direct buffer memory。用 NMT、Netty 泄漏检测 PARANOID 找到忘记 release 的 ByteBuf,并给堆外内存加上限。
商品详情接口每隔几十次就慢 3 秒,日志里什么都没有。用 Arthas 的 dashboard、thread、trace、watch、stack 逐层下钻到一个连不上的备用节点,再用 jad、redefine 临时止血,最后用 profiler 火焰图复核。
服务进程在、端口通、健康检查也过,但所有业务请求都卡住,CPU 只有 2%。jstack 直接报出 Found one Java-level deadlock。本文含最小可复现代码、输出逐行解读,以及锁顺序统一、tryLock 超时、缩小锁粒度三种修法。
告警说 Java 进程 CPU 800%,接口全部超时。top -Hp → printf 转十六进制 → jstack,三步定位到一条正则的灾难性回溯。附完整命令、根因分析和三种修法。
月初征期一到,一批热点 key 在同一秒过期,请求全部回源,MySQL 连接数瞬间打满。从命中率与 QPS 曲线定位击穿,辨析穿透/击穿/雪崩,并给出互斥锁、逻辑过期、随机 TTL、布隆过滤器与多级缓存的落地写法。
一条上线半年都很快的 SQL 突然变慢,EXPLAIN 显示 type=ALL。从慢日志、pt-query-digest 到 EXPLAIN 逐列解读,逐个排查隐式类型转换、函数包裹列、前导 % 与字符集不一致导致索引失效,并给出改法。
业务偶发报 MySQL 1213 死锁,几百单里才一两单。用 SHOW ENGINE INNODB STATUS 逐行读懂 LATEST DETECTED DEADLOCK,配合 innodb_print_all_deadlocks 复现两类典型死锁:相反顺序加锁与间隙锁,并给出重试和统一加锁顺序的治法。
线上执行 ALTER TABLE 加字段,结果这张表所有读写全部排队。原因是一个没提交的长事务持有 MDL 读锁,ALTER 等写锁,后续请求全被堵在队列。附 processlist、performance_schema、sys 视图定位与 kill、Online DDL、gh-ost、lock_wait_timeout 的做法。
同一笔账被两个实例同时处理,锁明明加了。从 redis-cli 现场查锁 key 与 TTL 入手,讲清锁过期自动释放、误删他人锁、Redisson 看门狗只在不传 leaseTime 时续期、unlock 抛 IllegalMonitorStateException,以及主从切换丢锁与 Redlock 争议。
读写分离上线后,用户“下单成功却看不到订单”。用 SHOW REPLICA STATUS 看 Seconds_Behind_Source,定位到大事务与单线程回放,再讲并行复制、Canal 延迟排查,以及“读自己写的”几种落地方案。
接口大面积报 Connection is not available, request timed out after 30000ms。用 Hikari 日志、show processlist 与 jstack 交叉验证,找出事务里调远程接口占着连接的元凶,并讲清 maximumPoolSize 的公式、leakDetectionThreshold 与指标告警。
Redis 没宕机、CPU 也不高,却隔几分钟整体卡顿,别的业务跟着超时。用 SLOWLOG、latency doctor、--bigkeys / --memkeys、MEMORY USAGE 揪出一个大 Hash 和一条 HGETALL,附 UNLINK、SCAN、拆 key 的完整修法。
用户投诉验证码半小时没到,RocketMQ 短信 Topic 堆积 300 万。用 mqadmin consumerProgress / consumerConnection / topicStatus 判断是消费慢还是消费者掉线,定位到线程卡在下游接口与失败无限重试,并给出扩容、批量消费、死信与跳过历史消息的处置。
同一天两起投诉:验证码收到两条、一笔扣款没有对应的消息。以 RocketMQ 为主,讲清 at-least-once 下重复消费的根源(超时重投、重平衡、重试),落地唯一键/去重表/状态机/SETNX 幂等;再沿生产端、Broker、消费端三环节查丢消息并给出配置。
管理界面里 Unacked 涨到 12 万,内存水位告警、生产者连接被 blocked。用 rabbitmqctl list_queues / list_channels / list_connections 定位忘记 ack 的消费者,讲清 prefetch、内存与磁盘水位流控、死信与 TTL,并给出止血与根治配置。
老 JDK 不识别 cgroup,容器限 2G 却按宿主机 64G 算堆,还按 32 核起线程,结果被 OOMKilled 或越跑越慢。讲清 UseContainerSupport、MaxRAMPercentage、堆外开销、cgroup v1/v2 文件,以及容器里 jstack/jmap 的正确姿势。
容器写挂载目录报 Permission denied,根因常是 UID 不一致,也可能是 SELinux 或只读挂载。顺带整理容器时区不对、构建缓存导致改了代码镜像没更新两个小坑,每条都给出复现现象与 id、ls -ln、docker exec 排查命令。
docker ps 里一个容器永远显示 Restarting,日志什么都没有。docker inspect 看 OOMKilled 与 ExitCode 137,配合 docker events、stats、dmesg 定位内存超限,讲清 restart policy 如何放大故障、-m 与 swap 的关系,附 exit code 速查表。
凌晨磁盘告警,df 显示根分区 100%,罪魁是 /var/lib/docker。用 docker system df -v、du 逐层下钻,定位到 json-file 日志无限增长,讲清 max-size/max-file 配置、docker system prune 各选项的风险(--volumes 会删数据)与构建缓存清理。
容器访问宿主机 MySQL 报 Connection refused,同一 compose 里 localhost 也不通。梳理 host.docker.internal 与 host-gateway、服务名 DNS、-p 与 EXPOSE、默认 bridge 无 DNS、ufw 被 Docker 绕过,附 docker network inspect 与 nsenter 排查命令和网络拓扑图。
top 里一个名字怪异的进程吃满 CPU,杀了又活。本文从防御视角复盘一次挖矿木马应急:定位进程、/proc/PID/exe、外连矿池、cron 与 authorized_keys 持久化、日志溯源,以及清理与加固清单。
积分服务变慢,订单服务接口全部超时,CPU 却只有 5%。jstack 看到 200 个 Tomcat 线程都阻塞在 socket read 与连接池里,根因是没设超时、队列无界、没有隔离。附拒绝策略与超时、舱壁隔离的落地写法。
接口 P99 隔几分钟就冲到 3 秒,机器 CPU 却不高。开 GC 日志、jstat 看趋势,发现老年代被一个无界的静态 Map 缓存慢慢撑满,Full GC 越来越频繁。附 JDK8 与 JDK11+ 日志参数、G1 与 CMS 取舍。
运营点了一下“导出全部订单”,服务就抛出 Java heap space 并重启。靠 HeapDumpOnOutOfMemoryError 留住现场,用 MAT 的 Dominator Tree 揪出一个 List,最后改成分页 + EasyExcel 流式写出。
消息推送网关的堆使用率一直只有 40%,容器 RSS 却每天涨一截,最后被 OOMKilled,日志里还偶发 Direct buffer memory。用 NMT、Netty 泄漏检测 PARANOID 找到忘记 release 的 ByteBuf,并给堆外内存加上限。
商品详情接口每隔几十次就慢 3 秒,日志里什么都没有。用 Arthas 的 dashboard、thread、trace、watch、stack 逐层下钻到一个连不上的备用节点,再用 jad、redefine 临时止血,最后用 profiler 火焰图复核。
服务进程在、端口通、健康检查也过,但所有业务请求都卡住,CPU 只有 2%。jstack 直接报出 Found one Java-level deadlock。本文含最小可复现代码、输出逐行解读,以及锁顺序统一、tryLock 超时、缩小锁粒度三种修法。
月初征期一到,一批热点 key 在同一秒过期,请求全部回源,MySQL 连接数瞬间打满。从命中率与 QPS 曲线定位击穿,辨析穿透/击穿/雪崩,并给出互斥锁、逻辑过期、随机 TTL、布隆过滤器与多级缓存的落地写法。
一条上线半年都很快的 SQL 突然变慢,EXPLAIN 显示 type=ALL。从慢日志、pt-query-digest 到 EXPLAIN 逐列解读,逐个排查隐式类型转换、函数包裹列、前导 % 与字符集不一致导致索引失效,并给出改法。
业务偶发报 MySQL 1213 死锁,几百单里才一两单。用 SHOW ENGINE INNODB STATUS 逐行读懂 LATEST DETECTED DEADLOCK,配合 innodb_print_all_deadlocks 复现两类典型死锁:相反顺序加锁与间隙锁,并给出重试和统一加锁顺序的治法。
线上执行 ALTER TABLE 加字段,结果这张表所有读写全部排队。原因是一个没提交的长事务持有 MDL 读锁,ALTER 等写锁,后续请求全被堵在队列。附 processlist、performance_schema、sys 视图定位与 kill、Online DDL、gh-ost、lock_wait_timeout 的做法。
同一笔账被两个实例同时处理,锁明明加了。从 redis-cli 现场查锁 key 与 TTL 入手,讲清锁过期自动释放、误删他人锁、Redisson 看门狗只在不传 leaseTime 时续期、unlock 抛 IllegalMonitorStateException,以及主从切换丢锁与 Redlock 争议。
读写分离上线后,用户“下单成功却看不到订单”。用 SHOW REPLICA STATUS 看 Seconds_Behind_Source,定位到大事务与单线程回放,再讲并行复制、Canal 延迟排查,以及“读自己写的”几种落地方案。
用户投诉验证码半小时没到,RocketMQ 短信 Topic 堆积 300 万。用 mqadmin consumerProgress / consumerConnection / topicStatus 判断是消费慢还是消费者掉线,定位到线程卡在下游接口与失败无限重试,并给出扩容、批量消费、死信与跳过历史消息的处置。
同一天两起投诉:验证码收到两条、一笔扣款没有对应的消息。以 RocketMQ 为主,讲清 at-least-once 下重复消费的根源(超时重投、重平衡、重试),落地唯一键/去重表/状态机/SETNX 幂等;再沿生产端、Broker、消费端三环节查丢消息并给出配置。
管理界面里 Unacked 涨到 12 万,内存水位告警、生产者连接被 blocked。用 rabbitmqctl list_queues / list_channels / list_connections 定位忘记 ack 的消费者,讲清 prefetch、内存与磁盘水位流控、死信与 TTL,并给出止血与根治配置。
top 里一个名字怪异的进程吃满 CPU,杀了又活。本文从防御视角复盘一次挖矿木马应急:定位进程、/proc/PID/exe、外连矿池、cron 与 authorized_keys 持久化、日志溯源,以及清理与加固清单。
服务凌晨无声消失,没有 hs_err 文件也没有 OutOfMemoryError。dmesg 里一行 Out of memory: Killed process 揭开真相:JVM 堆加堆外内存把机器吃光,被内核枪毙。附排查命令、oom_score 原理和堆参数预留算法。
监控里 load 飙到几十,top 里 CPU 却几乎空闲。原来 Linux 的 load 也统计 D 状态进程。用 vmstat、iostat -x、pidstat -d、iotop 和 ps 找出是磁盘慢还是 NFS 卡死,并讲清 await 与 %util 怎么读。
接口突然大面积报 Too many open files。先分清 ulimit、limits.conf、systemd LimitNOFILE 与容器的差异,再用 lsof、ss 统计连接状态,定位到 CLOSE_WAIT 堆积,根因是 HttpClient 响应没关闭导致连接泄漏。
磁盘告警 100%,du 加起来却只有一半。原来是日志被 rm 了,Java 进程还握着文件句柄。用 lsof +L1 找到它,/proc/PID/fd 截断止血,再顺带讲 inode 耗尽、大文件定位和 logrotate copytruncate。
备份脚本手敲一切正常,放进 crontab 却悄无声息地不执行。逐条排查 cron 服务与日志、PATH 环境变量、百分号转义、时区、权限、输出重定向、flock 防重叠、时间漂移,并对比 XXL-JOB。
老 JDK 不识别 cgroup,容器限 2G 却按宿主机 64G 算堆,还按 32 核起线程,结果被 OOMKilled 或越跑越慢。讲清 UseContainerSupport、MaxRAMPercentage、堆外开销、cgroup v1/v2 文件,以及容器里 jstack/jmap 的正确姿势。
容器写挂载目录报 Permission denied,根因常是 UID 不一致,也可能是 SELinux 或只读挂载。顺带整理容器时区不对、构建缓存导致改了代码镜像没更新两个小坑,每条都给出复现现象与 id、ls -ln、docker exec 排查命令。
docker ps 里一个容器永远显示 Restarting,日志什么都没有。docker inspect 看 OOMKilled 与 ExitCode 137,配合 docker events、stats、dmesg 定位内存超限,讲清 restart policy 如何放大故障、-m 与 swap 的关系,附 exit code 速查表。
凌晨磁盘告警,df 显示根分区 100%,罪魁是 /var/lib/docker。用 docker system df -v、du 逐层下钻,定位到 json-file 日志无限增长,讲清 max-size/max-file 配置、docker system prune 各选项的风险(--volumes 会删数据)与构建缓存清理。
容器访问宿主机 MySQL 报 Connection refused,同一 compose 里 localhost 也不通。梳理 host.docker.internal 与 host-gateway、服务名 DNS、-p 与 EXPOSE、默认 bridge 无 DNS、ufw 被 Docker 绕过,附 docker network inspect 与 nsenter 排查命令和网络拓扑图。
一次上线连踩两个 Nginx 默认值:client_max_body_size 默认 1m 导致上传 413,Spring Boot multipart 与多层代理要逐层放开;WebSocket 每 60 秒断线,缺 Upgrade 头且 proxy_read_timeout 默认 60s。附 curl 复现命令与心跳方案。
前端喊“接口 502”,后端说“我这儿好好的”。本文讲清 502(上游连不上/被掐断)与 504(上游超时)的区别,用 error.log 原文加自定义 access_log 三个 upstream 变量定位问题,并解释 keepalive、重启瞬间 502 与 proxy_next_upstream 的坑。
某天早上全站弹“连接不是私密连接”,根因是 HTTPS 证书过期。用 openssl s_client 和 curl -v 看证书有效期与证书链,梳理 certbot/acme.sh 自动续期失败的常见原因,并给出提前 30 天告警的检查脚本。
客服转来一句“下单好慢”,怎么在半小时内定位到具体机构、具体接口、具体那一行调用?本文用一个完整时间线讲 traceId 贯穿(MDC、网关、Dubbo、MQ、线程池的坑)、Kibana KQL 检索、SkyWalking 看 span 耗时,并总结排查先问的 5 个问题和“定位漏斗”。
两类高频“灵异事件”:Nacos 配置改了不生效(dataId/group/namespace 对不上、缺 @RefreshScope、@Value 与 @ConfigurationProperties 的差异、本地缓存),以及服务下线后仍有流量(心跳摘除延迟、消费者缓存、优雅停机顺序)。附 curl 查询配置与实例的排查清单。
下游一个慢接口,叠加 Dubbo 超时与默认 retries=2(最多调 3 次),流量被放大、provider 200 线程被占满并报 Thread pool is EXHAUSTED,故障从一个服务扩散到整条链路。附排查命令、超时分层、集群容错与限流降级的修法。
同一个定时任务有时跑两遍、有时一遍都没跑、有时执行器在后台显示离线。沿着调度中心到执行器的链路,查注册地址、accessToken、路由与阻塞策略、misfire、执行超时,最后用幂等设计兜底。
日志写入突然全部失败,报 index read-only / allow delete (api),集群还变了 yellow。顺着 _cluster/health、_cat/allocation、allocation/explain 一路查到磁盘水位线,讲清 85/90/95 三道线、解除只读的正确姿势,以及分片过多这个隐形推手。