news 2026/7/5 14:45:02

线上问题排查:为什么你查了3小时,别人只用了10分钟?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
线上问题排查:为什么你查了3小时,别人只用了10分钟?

从慢SQL到OOM,一套日志驱动的问题定位方法论

线上问题不可怕,可怕的是你像无头苍蝇一样乱撞。

凌晨2点,报警群突然炸了。

"接口超时率飙升到30%!" "数据库CPU 98%!" "用户反馈页面打不开!"

你睡眼惺忪打开电脑,开始排查。看监控、查日志、登服务器……一顿操作猛如虎,一看进度原地杵。3小时过去了,问题还没定位到。

而隔壁工位的老王,10分钟就找到了原因——一条慢SQL把数据库拖垮了。

你和老王的差距在哪?不是技术,是思维方式


一、90%的人排查问题的方式都是错的

先说一个反直觉的事实:大多数线上问题,不是靠"猜"解决的,而是靠"看"解决的。

什么叫"猜"?

  • "是不是缓存挂了?" → 去看缓存

  • "是不是数据库慢了?" → 去看数据库

  • "是不是代码有bug?" → 去看代码

这种排查方式有个专业名词——试错法。听起来很科学,实际上效率极低。因为你每次"猜"都要花时间验证,猜错了又要重新猜。

什么叫"看"?

先看日志,再下结论。

日志是什么?日志是系统给你写的"病历本"。系统哪里不舒服、什么时候开始不舒服、严重到什么程度,全都记在日志里。

高手排查问题,不是从"猜测"开始,而是从"阅读"开始。


二、日志分析的三层境界

第一层:看现象(青铜)

# 查看错误日志 tail -f /var/log/app/error.log | grep "Exception"

看到报错就去搜百度,搜到方案就去改。这是最基础的用法,但也是最低效的。

因为你只看到了"症状",没看到"病因"。

第二层:看关联(黄金)

# 查看错误发生前后的上下文 grep -B 10 -A 5 "OutOfMemoryError" /var/log/app/app.log

不只是看错误本身,还要看错误发生前10行、后5行。很多时候,真正的线索藏在"前因后果"里。

比如OOM之前,可能有一段大量对象创建的代码;慢SQL之前,可能有一个参数异常的请求。

第三层:看趋势(王者)

# 统计每分钟的错误数量 awk '/ERROR/{print $1,$2}' app.log | cut -d: -f1,2 | uniq -c | sort -rn

不只看单条日志,而是看日志的分布和趋势

  • 错误是突然出现的,还是逐渐增多的?

  • 错误集中在某个时间段,还是分散的?

  • 错误和什么业务操作相关?

看现象是看病人的症状,看关联是看病人的病史,看趋势是看疾病的传播规律。


三、慢SQL排查:从发现到解决的完整流程

3.1 发现慢SQL

方式一:MySQL慢查询日志

# 查看慢查询配置 SHOW VARIABLES LIKE 'slow_query%'; ​ # 开启慢查询日志(临时) SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; # 超过1秒记录 ​ # 查看慢查询日志 tail -f /var/log/mysql/slow.log

方式二:Arthas在线诊断(推荐)

# 启动Arthas java -jar arthas-boot.jar ​ # 监控SQL执行耗时 trace org.apache.ibatis.mapping.MappedStatement query -n 5

方式三:Druid监控

# Spring Boot配置 spring: datasource: druid: filter: stat: enabled: true log-slow-sql: true slow-sql-millis: 1000

3.2 分析慢SQL

拿到慢SQL后,第一步不是优化,而是分析

-- 查看执行计划 EXPLAIN SELECT * FROM orders WHERE user_id = 123 AND status = 'PENDING' ORDER BY create_time DESC LIMIT 10;

重点关注:

字段含义警告值
type访问类型ALL(全表扫描)
rows扫描行数> 10000
Extra额外信息Using filesort, Using temporary

常见问题诊断:

-- 问题1:索引失效(LIKE左模糊) SELECT * FROM user WHERE name LIKE '%张%'; -- ❌ 索引失效 ​ -- 问题2:索引失效(函数操作) SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01'; -- ❌ 索引失效 ​ -- 问题3:索引失效(类型转换) SELECT * FROM user WHERE phone = 13800138000; -- ❌ phone是varchar,传入int

3.3 解决慢SQL

方案一:添加索引

-- 添加复合索引 ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);

方案二:改写SQL

-- 优化前:索引失效 SELECT * FROM orders WHERE DATE(create_time) = '2024-01-01'; ​ -- 优化后:使用范围查询 SELECT * FROM orders WHERE create_time >= '2024-01-01 00:00:00' AND create_time < '2024-01-02 00:00:00';

方案三:分页优化

-- 优化前:深分页(慢) SELECT * FROM orders ORDER BY id LIMIT 1000000, 10; ​ -- 优化后:游标分页(快) SELECT * FROM orders WHERE id > 1000000 ORDER BY id LIMIT 10;

四、OOM排查:从堆dump到定位泄漏

4.1 发现OOM

# 查看JVM日志 grep "OutOfMemoryError" /var/log/app/app.log ​ # 查看GC日志 grep "Full GC" /var/log/app/gc.log

OOM类型判断:

// 堆内存溢出(最常见) java.lang.OutOfMemoryError: Java heap space ​ // 元空间溢出(类加载泄漏) java.lang.OutOfMemoryError: Metaspace ​ // 栈溢出(递归太深) java.lang.StackOverflowError

4.2 获取堆dump

# 方式一:手动dump jmap -dump:format=b,file=dump.hprof <pid> ​ # 方式二:自动dump(推荐) # 启动参数添加: -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app/dump.hprof

4.3 分析堆dump

使用MAT(Memory Analyzer Tool):

  1. 打开dump文件

  2. 查看Leak Suspects Report(自动分析泄漏点)

  3. 查看Dominator Tree(找出占用内存最大的对象)

常见泄漏场景:

// 场景1:集合只加不删 private static final List<Object> cache = new ArrayList<>(); public void addToCache(Object obj) { cache.add(obj); // ❌ 永远不清理 } ​ // 场景2:未关闭的资源 public void readFile() { InputStream is = new FileInputStream("file.txt"); // ❌ 没有close,资源泄漏 } ​ // 场景3:ThreadLocal未清理 private static final ThreadLocal<User> userHolder = new ThreadLocal<>(); public void setUser(User user) { userHolder.set(user); // ❌ 请求结束后没有remove }

4.4 解决OOM

// 方案1:使用LRU缓存 private static final Cache<String, Object> cache = CacheBuilder.newBuilder() .maximumSize(1000) .expireAfterWrite(10, TimeUnit.MINUTES) .build(); ​ // 方案2:try-with-resources自动关闭 public void readFile() { try (InputStream is = new FileInputStream("file.txt")) { // 自动关闭 } } ​ // 方案3:请求结束清理ThreadLocal @Around("@annotation(...)") public Object around(ProceedingJoinPoint point) throws Throwable { try { return point.proceed(); } finally { userHolder.remove(); // 必须清理 } }

五、CPU飙高排查:从top到定位代码

5.1 排查流程

# 1. 找到CPU最高的Java进程 top -c ​ # 2. 找到该进程中CPU最高的线程 top -Hp <pid> ​ # 3. 将线程ID转为16进制 printf "%x\n" <tid> ​ # 4. 导出线程栈,搜索该16进制ID jstack <pid> | grep <tid_hex> -A 30

5.2 常见原因

原因1:死循环

// ❌ 死循环 while (true) { // 没有break或return } ​ // ✅ 正确写法 while (!Thread.currentThread().isInterrupted()) { // 可以被中断 }

原因2:频繁GC

# 查看GC情况 jstat -gcutil <pid> 1000 ​ # 如果YGC和FGC都很频繁,说明内存压力大

原因3:正则回溯

// ❌ 灾难性回溯 String regex = "(a+)+b"; input.matches(regex); // 输入"aaaaaaaaaaaaaaaac"会卡死 ​ // ✅ 优化正则 String regex = "a+b";

六、接口超时排查:从链路追踪到定位瓶颈

6.1 分层排查

请求链路: 客户端 → Nginx → Gateway → 服务A → 服务B → 数据库/Redis ​ 排查顺序: 1. Nginx日志:确认请求是否到达 2. Gateway日志:确认是否转发成功 3. 服务A日志:确认是否处理成功 4. 服务B日志:确认是否调用成功 5. 数据库/Redis:确认是否响应正常

6.2 日志分析技巧

# 查看请求耗时分布 awk '{print $NF}' access.log | sort -n | tail -10 ​ # 查看超时请求的共同特征 grep "timeout" app.log | awk '{print $4}' | sort | uniq -c | sort -rn ​ # 按接口统计耗时 grep "api/order" app.log | awk '{sum+=$NF; count++} END {print sum/count}'

6.3 常见瓶颈

瓶颈特征解决方案
数据库慢SQL执行时间长优化SQL、加索引、读写分离
Redis慢命令执行时间长避免大key、使用pipeline
HTTP调用慢第三方接口超时设置超时、异步调用、降级
线程池满日志出现"RejectedExecution"扩大线程池、异步化

七、日志分析的黄金法则

法则一:先看时间线

# 按时间排序查看日志 sort -k1,2 app.log ​ # 查看某个时间段的日志 awk '/2024-01-01 10:00/,/2024-01-01 10:05/' app.log

为什么要看时间线?

因为很多问题是"并发"导致的。只有把日志按时间排列,才能看到事件之间的关联。

法则二:先看异常,再看正常

# 先看错误日志 grep -E "ERROR|Exception" app.log | tail -20 ​ # 再看正常日志,对比差异 grep "INFO" app.log | tail -20

法则三:先看变化,再看静态

# 实时监控日志 tail -f app.log | grep --line-buffered "ERROR" ​ # 监控GC jstat -gcutil <pid> 1000

法则四:先看全局,再看局部

# 查看错误分布 grep -c "ERROR" app.log # 总数 ​ # 按小时统计 awk '/ERROR/{print $1,$2}' app.log | cut -d: -f1,2 | uniq -c

日志分析的本质,是从海量信息中提取"异常模式"。


八、排查工具箱

工具用途常用命令
tail/grep查看日志tail -f app.log \| grep ERROR
awk/sed日志统计awk '/ERROR/{count++} END {print count}' app.log
jps查看Java进程jps -lvm
jstat查看GC情况jstat -gcutil <pid> 1000
jmap堆dumpjmap -dump:format=b,file=dump.hprof <pid>
jstack线程dumpjstack <pid> > thread.txt
Arthas在线诊断java -jar arthas-boot.jar
MAT堆分析打开hprof文件

写在最后

线上问题排查,本质上是一场信息战

你不是在和bug战斗,而是在和"信息不对称"战斗。系统知道问题在哪,它把答案写在了日志里。你的任务,不是"猜"答案,而是"读"答案。

高手和新手的差距,不是谁的工具多,而是谁更会"听"系统说话。

所以下次遇到线上问题,别急着猜,先打开日志,从头到尾读一遍。

你会发现,答案一直在那里。


📌 互动问题:你遇到过最难排查的线上问题是什么?最后是怎么解决的?欢迎在评论区分享你的故事。


如果你觉得这篇文章有价值,欢迎转发给需要的朋友。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/5 14:43:50

Arthas MCP Proxy MCP 服务说明文档

1. 服务概述一句话简介&#xff1a;基于MCP的Arthas诊断代理服务器&#xff0c;支持在Claude对话中通过SSH动态连接到任意服务器进行JVM实时诊断&#xff0c;实现多进程并行诊断和零延迟切换。服务名称&#xff1a;Arthas MCP Proxy版本号&#xff1a;1.0.0开发者/提供方&#…

作者头像 李华
网站建设 2026/7/5 14:43:44

流放之路2角色构建:如何用Path of Building告别无效配装?

流放之路2角色构建&#xff1a;如何用Path of Building告别无效配装&#xff1f; 【免费下载链接】PathOfBuilding-PoE2 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding-PoE2 你是否曾花费数小时调整装备和天赋&#xff0c;却在实战中发现自己精心设…

作者头像 李华
网站建设 2026/7/5 14:42:55

[MAF Workflow编排模式-05]Group Chat:构建多人智囊团式的自由协作大群

群聊(Group Chat)编排模拟了多个Agent之间的协作对话&#xff0c;由编排器负责协调&#xff0c;该编排器决定发言者选择和对话流程。这种模式非常适合需要迭代改进、协作解决问题或多视角分析的场景&#xff0c;比如&#xff1a; 迭代改进&#xff1a;多轮审查和改进&#xff…

作者头像 李华
网站建设 2026/7/5 14:41:59

AI个体生存三部曲:2026年了还不会用AI的人,正在被悄悄淘汰2026年了还不会用AI的人,正在被悄悄淘汰

上周在深圳&#xff0c;一个做了8年前端开发兼设计的哥们儿跟我说&#xff0c;他被一个刚毕业的小孩替代了。那小孩月薪只有他三分之一&#xff0c;但人家会用AI做vibe coing和生图&#xff0c;他还在坚持古法编程。数据说话&#xff1a;这不是个例是结构性重组 2026年5月Meta裁…

作者头像 李华
网站建设 2026/7/5 14:39:12

Level 3 的“Component(组件视图)”是软件架构视图之一,主要用于向开发人员展示系统某模块内部的组成结构

Level 3 的“Component&#xff08;组件视图&#xff09;”是软件架构视图之一&#xff0c;主要用于向开发人员展示系统某模块内部的组成结构&#xff0c;包括关键组件&#xff08;如类、服务、微服务、库、接口等&#xff09;、组件之间的依赖关系、职责划分及交互方式。该视图…

作者头像 李华