
开篇先聊一个很有意思的场景在很多开发群、技术群里每天都能见到类似的问题——“有没有大佬看看我这个项目为什么跑不起来”“谁能帮我看看这段代码哪里有问题”“这个报错是什么意思在线等急”而这些问题往往没有提供日志、没有贴出关键报错信息、没有说明自己的操作步骤甚至是上午刚有人问过同样的问题。这个时候屏幕另一端的老开发内心通常是这样的问什么问再问停雾“停雾”这个词本来是一个网络调侃意思是“再问就把你的网络停掉、让你安静一下”。但换成技术人的视角来看它是很有现实意义的与其反复问别人不如掌握一套自己排查问题的完整方法论。本文就用一篇系统教程的形式从问题排查的底层逻辑讲起梳理一套适合后端开发、运维和测试同学的故障定位实操方案覆盖日志分析、系统监控、常用排查命令、典型故障复现与修复最后给出一些工程层面的最佳实践。无论你是刚入行的新人还是已经带项目的开发都能从里面找到可以直接落地的思路和命令。1. 背景与核心概念1.1 “问什么问”背后的真实问题是什么一个提问如果没有上下文别人几乎无法回答。举个最常见的例子问Java 应用启动报错怎么办这个问题缺少的信息太多了用什么 JDK 版本启动的Spring Boot 还是普通 Java 应用报错是在编译期还是运行期完整堆栈贴出来了吗是启动到一半报错还是刚执行就退出是不是端口被占用了这些信息缺失提问者得到的回复大概率只能是“贴日志”“贴堆栈”“Spring Boot 项目先看启动日志”这些标准话术。所以“问什么问”的本质不是拒绝回答问题而是希望提问者具备基础的信息收集和问题定位能力。于是“停雾”这个调侃词在技术圈广泛流行恰恰说明很多开发者在遇到问题时的第一反应是“找人问”而不是“先自己查”。本文要分享的内容正是帮助你把“找人问”的念头转变成“自己动手看日志、看状态、看监控、做测试验证”的完整流程。掌握这套流程之后你会发现自己能独立解决的问题比想象中多得多。1.2 问题排查的基本模型现象 → 假设 → 验证 → 结论不管是单机脚本报错还是分布式系统的线上故障排查过程本质上都遵循同一个模型阶段核心动作典型输出现象采集收集报错信息、日志、截图、复现步骤完整报错堆栈、异常发生时间线信息整理梳理系统架构、变更记录、最近发布变更清单、影响范围判断假设提出根据现有信息定位可疑环节候选原因列表验证假设通过日志、命令、测试、监控逐一验证每条假设被证实或排除修复与回归实施修复并验证问题不再复现修复方案、回归测试结果很多新手一上来就跳过“现象采集”和“信息整理”直接跳到“假设提出”然后拿着一个不成熟的猜测去群里问。这种做法效率极低而且容易把别人带到错误的方向上。1.3 “停雾”式表达与故障演练的关系“再问停雾”是一句玩笑话但在团队协作中适度的“故障演练”和“问题自助排查指引”却是非常严肃的工程实践。现在很多中大型团队都会建设内部的故障排查手册、问题诊断知识库、监控告警系统目的就是让成员在遇到常见故障时先按手册排查而不是直接在生产群里刷屏提问。这块内容在后面的工程建议部分会展开说明。2. 环境准备与版本说明在进入具体排查实操之前先约定一下本文使用的环境。排查命令与工具在多数 Linux 环境中都是通用的但版本差异可能会导致输出字段、参数不完全一致所以以下说明用于帮助你对照自己的环境做调整。2.1 操作系统与基础工具操作系统Linux本文示例以 CentOS 7.x / Ubuntu 20.04 为主ShellBash核心工具top、free、df、iostat系统资源查看ps、netstat、ss、lsof进程与端口排查tail、grep、awk、sed日志分析与过滤curl、ping、telnet、nc网络连通性测试2.2 Java 应用环境Java 版本的命名和输出在不同版本之间有差异比如 JDK 8 的jstack输出和 JDK 11 的jcmd输出并不完全一样。本文以常见的 JDK 8 和 Spring Boot 2.x 应用为例如果你的项目使用的是 JDK 17 或 Spring Boot 3.x命令主体思路不变只是部分输出字段名称会有变化需要根据实际版本调整。2.3 数据库与中间件MySQL 5.7 / 8.0Redis 6.xNginx 1.18版本需要根据你的项目实际情况调整本文示例以常见环境为例重点演示配置思路和排查方法不绑定特定发行版。3. 核心排查方法论与常用命令拆解这一节是全文的基础掌握这些命令和工具之后你遇到问题就不会只能干瞪眼。3.1 第一步先看日志再看状态日志是系统留给你的“现场记录”。很多问题从报错堆栈里就能直接看出方向。日志查看有两个关键点看时间范围故障发生前后几分钟内的日志往往比所有日志更有价值。看异常堆栈不要只看“Exception: xxx”那一行要往下翻看Caused by那才是真正的根因所在。常用命令示例# 实时跟踪日志输出 tail -f /var/log/app/app.log # 查看最近 200 行 tail -n 200 /var/log/app/app.log # 过滤某个关键词比如 ERROR 或订单号 grep -n ERROR /var/log/app/app.log | tail -n 100 # 按时间范围截取日志 awk /2025-03-10 10:00:00/,/2025-03-10 10:30:00/ /var/log/app/app.log fault_window.log # 统计某个异常出现次数 grep -c NullPointerException /var/log/app/app.log这里需要特别说明一个常见误区日志不是看得越多越好而是要学会“先纵览后聚焦”。先用grep -c统计异常数量再用tail -n查看最近内容最后按时间窗口截取故障期间日志这样才不至于被几十万行日志淹没。3.2 系统资源排查CPU、内存、磁盘、负载如果日志没有明显的异常信息问题很可能出在系统资源层面。CPU 居高不下top -c按Shift P按 CPU 使用率排序记录占用 CPU 最高的进程 PID然后按Shift H查看线程级别。如果是 Java 应用可以导出线程栈# 假设 CPU 最高的 PID 是 12345 top -Hp 12345把线程 PID 转成十六进制printf %x\n 12345然后导出堆栈jstack 12345 /tmp/thread_dump.txt在导出文件中搜索上面得到的十六进制线程号就能看到这个线程正在执行什么代码。这个方法是排查 Java 应用 CPU 飙高的标准套路非常实用。内存不足或频繁 Full GCfree -m如果free命令显示 available 很小检查应用堆内存设置和 JVM GC 日志。常见命令# 查看 Java 进程的启动参数 ps aux | grep java # 输出 GC 日志统计JDK 8 jstat -gcutil 12345 1000 10如果 GC 频繁需要结合堆转储分析对象占用。导出堆快照可以使用jmap -dump:formatb,file/tmp/heap.hprof 12345然后使用 MAT 或 VisualVM 分析大对象。磁盘写满df -h du -sh /var/log排查时要注意大日志文件、临时文件、core dump 文件这些都会快速占满磁盘。如果你发现某个目录异常大可以用du -h --max-depth2 / | sort -hr | head -203.3 网络与端口排查很多“应用连不上”“请求超时”的问题根源在网络层。查看端口监听状态ss -lntp或者netstat -lntp如果端口没有监听检查服务是否启动成功。如果端口监听正常但访问失败检查防火墙firewall-cmd --list-all # CentOS 7 ufw status # Ubuntu测试远程端口连通性telnet 192.168.1.100 3306nc -vz 192.168.1.100 3306curl -v http://192.168.1.100:8080/health这三条命令分别适用于不同场景telnet 适合快速验证端口nc 更轻量curl 则可以直接看到 HTTP 请求的响应状态和耗时。3.4 数据库排查慢 SQL 与锁等待排查数据库问题要遵循“先慢查询后锁等待”的顺序。查看 MySQL 慢查询日志是否开启SHOW VARIABLES LIKE slow_query_log%; SHOW VARIABLES LIKE long_query_time;查看当前正在执行的 SQL 和连接状态SHOW FULL PROCESSLIST;如果看到大量Waiting for table metadata lock或Lock wait timeout exceeded说明存在锁等待问题。可以用下面这条 SQL 查看事务和锁信息SELECT * FROM information_schema.innodb_trx; SELECT * FROM information_schema.innodb_locks; SELECT * FROM information_schema.innodb_lock_waits;注意生产环境执行这些查询需要具备合法授权并且建议在低峰期排查不要随意 kill 事务。4. 完整实战案例从“接口突然变慢”到定位根因为了把上面的方法串起来本文设计一个完整的故障排查实战案例。场景如下环境Linux 服务器部署了一个 Spring Boot 应用连接 MySQL 数据库对外提供订单查询接口。 现象下午 14:30 开始接口响应时间从正常时的平均 200ms 飙升到 5 秒以上部分请求直接超时但服务没有完全宕机。下面按照标准排查流程走一遍。4.1 故障现象采集第一步先确认现象的真实影响范围。登录服务器查看应用日志中最近一段时间的异常cd /var/log/app ls -lh tail -n 500 app.log | grep -E ERROR|WARN | tail -n 50假设日志里看到了大量 MySQL 连接超时的异常java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available, request timed out after 30000ms这说明数据库连接池中的连接已经被占满新请求拿不到连接。这时候不要急着调大连接池要继续往下追为什么连接会被占满4.2 系统资源与网络排查用top查看服务器负载top -c假设发现 Java 进程 CPU 不高但系统整体负载偏高。再查看数据库服务器的情况如果数据库是独立部署的在数据库服务器上执行top -c free -m假设数据库服务器 CPU 使用率冲到了 90% 以上问题重点就转移到了数据库侧。4.3 数据库慢查询定位登录 MySQL开启慢查询相关临时分析SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 2;查看当前正在执行的 SQLSHOW FULL PROCESSLIST;这时候大概率会发现某条订单查询 SQL 频繁出现在 PROCESSLIST 中状态为Sending data单次执行时间已经超过几秒。这条 SQL 的常见问题就是查询条件字段没有索引或者 SQL 写法导致索引失效。用 EXPLAIN 分析EXPLAIN SELECT * FROM order_info WHERE user_id U10001 AND status 1 ORDER BY create_time DESC;如果type列显示为ALL说明是全表扫描这正是导致数据库 CPU 升高的直接原因。4.4 修复方案找到根因后修复分为两步。第一步先恢复服务重启应用清空阻塞的连接池。但重启不是根治方案只是让连接池恢复可用。第二步给订单表相关查询字段添加联合索引ALTER TABLE order_info ADD INDEX idx_user_status_time (user_id, status, create_time);生产环境执行 DDL 需要谨慎评估如果表数据量大建议使用在线 DDL 工具或在业务低峰期执行并先在测试环境验证索引效果。添加索引后再次执行 EXPLAINEXPLAIN SELECT * FROM order_info WHERE user_id U10001 AND status 1 ORDER BY create_time DESC;预期type从ALL变成ref或rangerows数量大幅下降。4.5 验证与结果说明在测试环境进行压测验证ab -n 1000 -c 50 http://localhost:8080/order/query?userIdU10001修复前平均响应时间 5000ms 以上修复后可恢复到 100ms 左右数据库 CPU 降回正常水平。这个案例虽然用的是常见场景但它完整演示了从日志到连接池、从连接池到数据库、从数据库到慢 SQL、从慢 SQL 到索引的排查链路。实际项目中你遇到的大多数接口变慢问题都可以套用这个思路。5. 常见问题与排查思路下表汇总了开发与运维中常见的问题现象、可能原因和解决思路遇到类似情况可以按表格快速定位方向。问题现象常见原因解决思路应用启动失败端口被占用使用ss -lntp查看端口占用修改端口或结束占用进程应用启动失败依赖组件未启动确认 MySQL、Redis、Nacos 等中间件状态接口响应慢慢 SQL / 缺少索引打开慢查询日志用 EXPLAIN 分析执行计划接口响应慢GC 频繁使用jstat查看 GC 情况必要时 dump 堆分析内存溢出堆大小不足检查 JVM 参数分析 heap dump 定位大对象CPU 飙高死循环 / 锁竞争使用top -Hpjstack定位线程连接池耗尽连接泄漏 / 慢 SQL检查连接获取与释放逻辑排查慢 SQL请求超时网络延迟 / 防火墙ping、telnet、curl -v分段测试服务偶发重启OOM 被杀查看 dmesg每条问题出现时建议按照“日志 → 系统指标 → 组件状态 → 代码逻辑”的顺序逐层排查不要跳步。6. 最佳实践与工程建议6.1 建立统一的日志规范日志是排查问题的第一手资料。建议在项目中约定统一的日志格式至少包含时间戳精确到毫秒日志级别DEBUG / INFO / WARN / ERROR请求链路 ID用于串联一次请求在多个服务间的日志类名与方法名关键业务参数订单号、用户 ID 等异常堆栈推荐使用 SLF4J Logback 或 Log4j2生产环境将日志输出到独立文件并设置合理的滚动策略。例如appender nameERROR_FILE classch.qos.logback.core.rolling.RollingFileAppender file/var/log/app/error.log/file filter classch.qos.logback.classic.filter.ThresholdFilter levelERROR/level /filter rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePattern/var/log/app/error.%d{yyyy-MM-dd}.log/fileNamePattern maxHistory15/maxHistory /rollingPolicy /appender6.2 监控告警先行很多线上问题如果能提前被发现根本不需要走到“排查”这一步。建议每个项目至少覆盖以下监控维度应用层接口 QPS、响应时间、错误率、线程池状态JVM 层堆内存使用、GC 频率与耗时、线程数系统层CPU、内存、磁盘、网络中间件层数据库连接数、慢 SQL 数量、Redis 命中率告警规则不要只看平均值要关注 P99 响应时间和错误率突增6.3 变更管理是排查的加速器排查故障时一定要先想一个问题这个系统最近有没有发生变更发布新版本、修改配置、扩缩容、数据库变更、第三方接口调整这些都有可能是故障诱因。建议每次变更都记录变更单至少包含变更时间、变更内容、操作人、回滚方案。出现问题时先对照变更时间线很多时候能很快锁定范围。6.4 按权限操作避免扩大故障生产环境排查时要遵守最小权限原则只读命令优先先用top、ss、tail、grep等只读命令观察变更操作前备份需要改配置、加索引、重启服务时先确认影响范围高危操作要审批kill 进程、DROP 表、清空日志等操作应经过审批并在测试环境验证保留现场在修复之前尽量先保留日志、线程栈、堆快照等现场信息6.5 把常见问题沉淀成排查手册个人经验和团队知识如果不沉淀下来就会变成一种隐性资产懂的人觉得“这不是很简单吗”不懂的人反复踩同一个坑。建议团队按季度整理“典型故障案例库”每个案例包含现象、排查路径、根因分析、修复方案、预防措施。这样下次再遇到类似问题可以直接搜索内部文档而不是在群里“问什么问”。7. 总结与学习路线本文从“问什么问再问停雾”这个网络调侃出发聊的其实是技术人的问题排查能力。核心内容可以概括为以下几点排查问题的基本模型是现象采集 → 信息整理 → 假设提出 → 验证假设 → 修复与回归Linux 常用命令是排查的基础工具top、ss、tail、grep、jstack等要熟练掌握接口变慢问题通常沿着“应用日志 → 连接池 → 数据库 → 慢 SQL → 索引”的链路定位生产环境操作要谨慎先备份、先验证、按最小权限原则执行把个人排查经验沉淀为团队知识库是提升整体效率的有效方式下一步可以重点学习这几个方向JVM 调优与故障分析学习jstat、jmap、jcmd的进阶用法分布式链路追踪调研 SkyWalking、Zipkin 或类似方案在项目中的应用监控系统搭建基于 Prometheus Grafana 构建应用与系统监控看板数据库进阶深入学习 MySQL 执行计划、索引优化和锁机制最后给你一个很实用的小建议下次遇到问题先强迫自己独立排查 10 分钟。把报错信息、运行环境、操作步骤、已经尝试过的方案记录下来如果 10 分钟后仍然没有头绪再带着这些信息去问别人。你会发现大部分问题在记录信息的过程中就已经有了方向。这比“问什么问再问停雾”式的吐槽更有效也是一个技术人员走向成熟的重要标志。