订阅更新

线上日志一天50G,只会tail -f和grep?——一次故障10分钟锁定问题日志

Linux手册 第 1 题 约 11 分钟读完

本文目录
  1. 先说结论
  2. 业务场景:Databricks Spark作业凌晨挂了,YARN日志50G
  3. 手段一:grep 组合拳(上下文 + 多文件 + 排除)
  4. 手段二:zgrep/zcat 直接查归档日志
  5. 手段三:awk 按时间切片和字段提取
  6. 四种排查手段对比
  7. 面试高频追问
  8. 实战踩坑:Spark作业'ExecutorLost'定位不到根因
  9. 总结

先说结论

日志排查的核心思维是'缩小范围':先按时间过滤到'故障发生前后3分钟',再按关键字缩到'异常/错误级别',最后看上下文和堆栈。这一套下来日志从50G缩到几百KB。

必须掌握的5个命令组合:`grep -C/-A/-B` 看上下文、`zgrep/zcat` 直接查归档、`awk` 按时间段切片、`tail -f | grep --line-buffered` 实时追踪、`find ... -newer` 找故障期间改动的文件。

不要再只用 tail -f 了:tail -f 只能看尾部,对大文件和归档文件完全无力。老手的工具箱是 less、grep、awk、zgrep、sed 的组合。

先把这段讲顺;面试官追问时,再展开下面的原理和细节。

详细讲解

面试问Linux,不是考你会不会用 `ls` 和 `cd`,而是考你有没有在凌晨三点被告警短信叫醒过。日志排查是最能体现线上经验的题——新人只会 `tail -f` 和 `grep ERROR`,一个上午找不到原因;老手用 grep 组合拳 + awk 时间范围 + zgrep 归档日志,10分钟内就能锁定问题那一行。这一篇讲的是我踩了很多次坑才磨出来的一套排查流程。

业务场景:Databricks Spark作业凌晨挂了,YARN日志50G

我在某出海硬件品牌做Databricks平台运维时,有一次凌晨2点的ETL主作业挂了。早上9点打开告警群一看,一个 application 的 YARN 日志总大小 48G,分布在十几台 NodeManager 上,`yarn logs -applicationId xxx` 拉下来 30 多分钟。新人的做法是直接 `grep ERROR`,结果一堆 info 级别的 ERROR 关键字(比如 `error_code=0`)全被匹上,根本定位不到真正的报错。

我的思路是反过来:故障时间 2:13,先把日志按时间段切出 2:10-2:20 的 10 分钟窗口,再在这个窗口里找 Exception 和 Caused by,直接定位到是一个 executor OOM 导致的 task 重试失败。整个过程不到 10 分钟。

# 1. 按时间段切出 10 分钟窗口(关键的第一步)
awk '/2024-03-15 02:10:/,/2024-03-15 02:20:/' app.log > window.log
 
# 2. 在窗口里找异常(注意是 Exception 不是 ERROR)
grep -n 'Exception\|Caused by\|OutOfMemory' window.log
 
# 3. 找到第一条异常的行号后,往上下各看30行
sed -n '12340,12400p' window.log
 
# 4. 发现是 executor OOM,拉那个 executor 的日志
yarn logs -applicationId app_xxx -containerId container_xxx_000015

手段一:grep 组合拳(上下文 + 多文件 + 排除)

grep 最被低估的是它的 -A/-B/-C 参数。线上排查看到一行 ERROR 有什么用?你需要的是它前后的上下文,才能看出是什么触发了这个错误。

# -A 3:匹配行 + 后面 3 行(常用于看异常堆栈)
grep -A 30 'Exception' app.log
 
# -B 5:匹配行 + 前面 5 行(看异常之前在干什么)
grep -B 5 'OOM' app.log
 
# -C 10:上下各 10 行,最常用
grep -C 10 'Caused by' app.log
 
# 多文件递归查,-n 带行号,-l 只显示文件名
grep -rn --include='*.log' 'NullPointer' /data/logs/
 
# 排除干扰的 info 级别日志(-v 反向匹配)
grep 'Exception' app.log | grep -v 'Retryable'
 
# 多个关键字同时查(注意 -E 扩展正则)
grep -nE 'Exception|Error|FATAL|Caused by' app.log

坑:grep 大文件慢,10G 以上的文件建议先用 `split -l 1000000 big.log part_` 切分,或者直接上 `ripgrep`(rg),它比 grep 快 5-10 倍,而且默认跳过 .gitignore。

手段二:zgrep/zcat 直接查归档日志

生产环境日志一般用 logrotate 压缩,前一天的日志会变成 app.log.1.gz、app.log.2.gz 这样。新人不知道有 zgrep 这种东西,先 `gunzip` 再 `grep`,一个 gz 解开几 G 的文件出来,磁盘都被撑爆。

# 直接在 gz 文件上查,不解压
zgrep 'Exception' app.log.*.gz
 
# 多个文件按时间顺序连起来看
zcat app.log.3.gz app.log.2.gz app.log.1.gz app.log | grep 'ERROR'
 
# 结合 awk 按时间过滤(查昨天 23:00 之后的)
zcat app.log.1.gz | awk '/2024-03-14 23:00:/,0' | grep 'OOM'
 
# 查所有节点、所有归档(find + zgrep)
find /data/logs -name 'app.log*' -mtime -2 -exec zgrep -l 'OOM' {} \;

适用场景:滚动日志归档(logrotate)、HDFS 上的压缩日志、S3 上的 gz 归档都可以直接查,不用下载解压。

手段三:awk 按时间切片和字段提取

awk 的两个杀手级用法:一是 `/start/,/end/` 的范围匹配,二是 `-F` 按字段切。线上日志一般有固定格式(时间 + 级别 + 线程 + 类 + 消息),awk 几行命令就能做 grep 做不了的事。

# 范围匹配:从第一次匹配 /start/ 到第一次匹配 /end/
awk '/02:10:00/,/02:20:00/' app.log
 
# 按字段统计:看每个线程报了多少错
grep 'ERROR' app.log | awk '{print $4}' | sort | uniq -c | sort -rn | head
 
# 按异常类型聚合(日志格式:时间 级别 线程 类 异常)
awk -F'Exception: ' '/Exception/ {print $2}' app.log | sort | uniq -c | sort -rn
 
# 统计每分钟的错误数(看错误是突发还是持续)
awk '/ERROR/ {print substr($2,1,5)}' app.log | sort | uniq -c
 
# 提取 trace_id 做关联查询
awk '/ERROR/ {match($0, /trace_id=[a-f0-9]+/, m); print m[0]}' app.log

四种排查手段对比

手段适用场景优势局限
grep + 上下文已知关键字、定位堆栈简单直观、所有机器都有大文件慢,不支持时间
zgrep/zcat归档 gz 日志不用解压、省磁盘只支持 gzip 格式
awk 时间切片按时间段精确切支持范围和字段语法门槛稍高
ripgrep (rg)大日志、多文件比 grep 快 5-10 倍机器上可能没装

面试高频追问

追问一:日志文件 20G,grep 一次要几分钟,怎么加速?

三种办法:第一是上 ripgrep(rg),它用 SIMD 加速 + 并行,比 grep 快 5-10 倍;第二是先用 `split -l 1000000 big.log part_` 按行数切成小文件再 `parallel grep`;第三是用 `LC_ALL=C grep`,不走 unicode 匹配能快一倍。生产上我一般直接 `rg --threads 8 'pattern' /data/logs/`,对 20G 的日志也就十几秒。

追问二:多台机器的日志怎么集中查?

两种方案。临时排障用 `pssh` 或 `ansible` 在所有机器上并行跑 grep,然后把结果拉回来:`ansible all -m shell -a 'grep OOM /data/logs/app.log'`。长期方案是上日志平台,大公司用 ELK(Elasticsearch + Logstash + Kibana),小厂用 Loki + Grafana 更轻量,按 trace_id 或者标签一搜就全出来了。我在某出海硬件品牌就是用 Loki,原来排查跨 20 台机器的作业要 1 小时,上了 Loki 之后几分钟搞定。

追问三:日志里没有 ERROR 关键字,但作业确实挂了,怎么定位?

这个很典型。很多时候作业不是被'显式错误'干掉的,而是被 kill -9、OOM killer、或者 JVM crash 杀的,这种情况日志里只会突然断掉。排查思路:① 看作业日志最后几行(`tail -100`)有没有异常截断;② 看系统日志 `dmesg -T | grep -i -E 'kill|oom|segfault'`;③ 看 JVM 是否生成了 hs_err_pid.log 崩溃文件;④ 看 YARN/K8s 的 exit code,137 是 OOM killer,143 是 SIGTERM。线上踩过最坑的一次就是作业日志无异常,最后发现是 dmesg 里 OOM killer 把 executor JVM 干掉了。

实战踩坑:Spark作业'ExecutorLost'定位不到根因

还是某出海硬件品牌,有段时间 Spark 作业经常报 `ExecutorLost: Slave lost`,driver 日志没有其他有用信息,只说 executor 丢了。新来的同事查了一天,以为是网络问题,准备申请运维排查网络。我上手做了四件事:

第一,`yarn logs -applicationId xxx -containerId <丢失的那个 container>` 拉 executor 日志,发现 executor 日志也是最后几行突然断掉,没有 Exception。第二,定位到 executor 所在的 nodemanager,`ssh` 上去看 `/var/log/hadoop-yarn/userlogs/application_xxx/container_xxx/stderr`,一样是截断。第三,`dmesg -T | grep -i kill | tail`,立刻发现凌晨 2:13 有 `Out of memory: Kill process 23451 (java) score 892`,锁定是 Linux OOM killer 把 executor 干掉了。第四,再 `grep 'java' /var/log/messages` 确认 JVM 申请的物理内存超过了 cgroup 限制。

# OOM killer 定位三板斧
# 1. 查 dmesg 里被 kill 的进程
dmesg -T | grep -i -E 'killed process|out of memory'
 
# 2. 查 /var/log/messages(CentOS/RHEL)
grep -i 'killed process' /var/log/messages | tail
 
# 3. 查 exit code(YARN/K8s)
# 137 = 128 + 9 (SIGKILL, 常见 OOM killer)
# 143 = 128 + 15 (SIGTERM, 正常终止)
kubectl describe pod xxx | grep 'Exit Code'

根因和修复:executor JVM heap 设了 8G,但没给 off-heap 预留空间,实际 RSS 到了 9.5G,超过 container 物理内存限制被 OOM killer 杀。改 `spark.executor.memoryOverhead` 从 1G 到 2G 之后问题消失。

总结

日志排查看似简单,实际是区分'会用 Linux'和'真在线上救过火'的分水岭。面试时别只说 `grep`,要说出你的完整排查思路:先按时间缩范围、再按关键字找线索、看上下文看堆栈、必要时查 dmesg 查 OOM killer。能说出 zgrep、awk 范围匹配、ripgrep 这些进阶工具的,面试官一眼就知道你真踩过坑。

下一篇聊另一个线上必问题:某台机器 CPU 飙到 100%,怎么一步步定位到哪行代码?top → top -H → jstack 的完整链路。

你线上排查日志用的最顺手的工具是什么?评论区聊聊~

关注「Wood聊大数据」| 9年大数据老兵,聊技术、聊职场、聊成长

刷题进度保存在当前浏览器