Files
zhuangang/topics/devops/linux/02-troubleshooting.md
T

4.8 KiB
Raw Blame History

id, title, type, domain, stack, tags, level, prerequisites, est_hours, related, week, order
id title type domain stack tags level prerequisites est_hours related week order
devops.linux.troubleshooting 线上排查:CPU、内存、磁盘、网络、日志的定位思路 lesson devops
linux
排查
top
负载
OOM
磁盘
TIME_WAIT
pprof
intermediate
devops.linux.commands
3
devops.linux.scenario.incident-troubleshooting
devops.linux.interview
14
2

线上排查:CPU、内存、磁盘、网络、日志的定位思路

解释说明

排查的总顺序:先止血(回滚、扩容、限流、重启),再定位,最后复盘。定位时从现象到资源再到代码:

资源 看什么 命令
负载 load average 和 CPU 核数的比较;高负载可能是 CPU 忙,也可能是 IO 等待 uptime、top(看 us / sy / wa)
CPU 哪个进程、哪个线程占用高 top -H -p PID;Go 用 pprof、Python 用 py-spy
内存 剩余可用内存(看 available)、是否用到 swap、是否有 OOM free -h、`journalctl -k
磁盘 空间和 inode 是否用满、IO 是否繁忙 df -h、df -i、du -sh、iostat -x 1
网络 端口监听、连接状态分布、连接数是否打满 ss -lntp、ss -ant 统计状态、curl -w 看耗时
日志 错误日志、慢请求、时间点 journalctl -u 服务、grep、awk

几个关键概念

  • load average:正在运行和等待运行(包括等待 IO)的进程数的平均值。4 核机器 load 为 4 左右算满载。
  • wa(iowait)高:CPU 在等磁盘,问题在 IO(慢查询扫盘、日志写太多),而不是计算。
  • OOM Killer:内存耗尽时内核按评分杀掉进程,应用日志里往往什么都没有,只能在内核日志里看到 Killed process。
  • TIME_WAIT 很多:主动关闭连接的一方会进入 TIME_WAIT。大量短连接(每次请求都新建连接)导致,解决办法是复用连接(HTTP keep-alive、连接池)。
  • CLOSE_WAIT 很多:对方已关闭,本方没有关闭——通常是代码里忘了关闭连接(如 Go 的 resp.Body.Close())。

语言级工具:Go 服务开启 net/http/pprof(只在内网端口暴露),go tool pprof http://127.0.0.1:6060/debug/pprof/profile?seconds=30 采集 CPU 热点,/debug/pprof/heap 看内存,/debug/pprof/goroutine 看 goroutine 泄漏。Python 用 py-spy top --pid PID。

案例

  • 接口变慢:top 看到 wa 30%,iostat 显示磁盘利用率 100%;进一步发现是同一台机器上的数据库在做全表扫描的报表查询。把报表改到从库。
  • 服务每天半夜重启:应用日志没有错误,journalctl -k 里有 Out of memory: Killed process ... (api)。用 pprof 看 heap,发现一个按天累积的本地缓存没有上限。
  • ss -ant 统计出几千个 CLOSE_WAIT:代码里调用第三方接口后没有关闭响应体。

代码示例

一分钟快照脚本 examples/log-analysis/diag.sh(只读命令)的核心部分:

uptime
ps -eo pid,user,%cpu,%mem,etime,cmd --sort=-%cpu | head -n 10
free -h
df -h -x tmpfs -x devtmpfs
ss -ant | awk 'NR > 1 {count[$1]++} END {for (s in count) print s, count[s]}'

Go 服务暴露 pprof(只监听本机端口,不要暴露到公网):

package main

import (
	"log"
	"net/http"
	_ "net/http/pprof" // 注册 /debug/pprof/* 到 http.DefaultServeMux
)

func main() {
	go func() {
		// 单独的端口,只监听 127.0.0.1;业务服务用自己的 ServeMux,不暴露 pprof
		log.Println(http.ListenAndServe("127.0.0.1:6060", nil))
	}()
	biz := http.NewServeMux()
	biz.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok")) })
	log.Fatal(http.ListenAndServe(":8080", biz))
}
# 在服务器上采集 30 秒 CPU 数据,然后看最耗 CPU 的函数
go tool pprof -top http://127.0.0.1:6060/debug/pprof/profile?seconds=30
# goroutine 数量是否持续增长(泄漏)
curl -s http://127.0.0.1:6060/debug/pprof/goroutine?debug=1 | head -n 1

易错点

  • 一上来就重启:现场没了,问题下次还会出现。重启前至少保存快照(diag.sh 的输出、pprof、日志片段)。
  • 只看 CPU 不看 iowait:负载高不一定是计算问题。
  • free 看 free 列认为内存不够:Linux 会把空闲内存用作缓存,要看 available。
  • 在业务端口暴露 pprof:泄露内部信息,还能被用来消耗资源。
  • 删了大文件磁盘空间没释放:lsof +L1 找到仍持有已删除文件的进程。
  • 排查结论没有数据支撑:每一步都记录命令和输出,便于复盘。

面试常问

见 Linux 面试题:CPU 飙高怎么排查、内存泄漏怎么查、磁盘满了怎么办、TIME_WAIT 和 CLOSE_WAIT。