Appearance
第九部分:Linux 生产事故诊断、CPU 100% 黄金排查、Nginx/Docker 与 DevOps 工程实战
第 1 章:CPU 100% 暴涨/卡死线上定位黄金四步法 (图解 + 真实输出对比)
当生产环境突然告警“某台服务器 CPU 占用率达到 100%”时,切忌盲目重启进程。请遵循 CPU 100% 诊断黄金四步法:
text
[ 步骤 1: top -H -p <PID> ] ➔ 找到占用 CPU 最高的线程 TID (如 1409376)
│
↓
[ 步骤 2: printf "%x\n" <TID> ] ➔ 将十进制 TID 转为十六进制 (如 158160)
│
↓
[ 步骤 3: jstack <PID> | grep -i <HEX_TID> -A 30 ] ➔ 定位 JVM 堆栈中的代码行
│
↓
[ 步骤 4: 导出完整 Dump / 修复代码 ] ➔ jstack -l <PID> > /tmp/java_thread_dump.txt1.1 黄金四步法命令详解、真实控制台输出与效果对比
步骤 1:定位高 CPU 进程与内部高 CPU 线程 ID (TID)
- 命令输入:bash
# 按线程维度 (Threads) 查看指定进程 12345 内部各个线程的 CPU 消耗 top -H -p 12345 - 控制台真实输出结果 (Terminal Output Result):text
top - 09:30:15 up 45 days, 12:10, 2 users, load average: 8.50, 6.20, 4.10 Threads: 25 total, 1 running, 24 sleeping, 0 stopped, 0 zombie %Cpu(s): 99.5 us, 0.5 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 32768000 total, 4120000 free, 25000000 used, 3648000 buff/cache PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1409376 root 20 0 12.5g 4.2g 18m R 99.9 13.4 15:40.12 java 1409377 root 20 0 12.5g 4.2g 18m S 0.3 13.4 0:05.10 java 1409378 root 20 0 12.5g 4.2g 18m S 0.0 13.4 0:02.00 java - 结果分析与效果对比:
- 未加
-H选项:只能看到整体java进程占用 100% CPU,无法知道内部究竟是哪个线程在飙高; - 加上
-H选项后:精准锁定进程内部 PID/TID 为1409376的线程占用了 99.9% 的 CPU!
- 未加
步骤 2:将十进制线程 TID 转换为十六进制 (Hex)
- 命令输入:bash
# 将十进制线程 ID (1409376) 转为十六进制小写 printf "%x\n" 1409376 - 控制台真实输出结果 (Terminal Output Result):text
158160 - 结果分析与效果对比: JVM
jstack输出的线程 Native ID (nid) 统一采用 十六进制小写(如nid=0x158160)。若不转换,后续使用grep 1409376将无法查找到任何日志。
步骤 3:使用 jstack 检索十六进制 TID 抓取线上代码行
- 命令输入:bash
# 精准捕获目标十六进制线程 158160 上下 30 行调用堆栈 jstack 12345 | grep 158160 -A 30 - 控制台真实输出结果 (Terminal Output Result):text
"http-nio-8080-exec-5" #25 daemon prio=5 os_prio=0 tid=0x00007f8b90001000 nid=0x158160 runnable [0x00007f8b701fe000] java.lang.Thread.State: RUNNABLE at com.example.service.UserService.checkOrderLoop(UserService.java:88) at com.example.controller.OrderController.pay(OrderController.java:45) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ... - 结果分析与效果对比:
- 效果对比:盲目摸排代码可能需要几个小时;通过此步骤,在 10 秒内直接定位到
UserService.java:88行中的while(true)死循环代码,实现秒级排错定位!
- 效果对比:盲目摸排代码可能需要几个小时;通过此步骤,在 10 秒内直接定位到
步骤 4:导出全量线程堆栈离线分析
- 命令输入:bash
# 将进程 12345 的全量线程堆栈导出至临时文件 jstack -l 12345 > /tmp/java_thread_dump.txt
第 2 章:进程查找、批量提取与杀进程 (命令 + 真实输出对比)
2.1 基础进程查找与输出
- 命令输入:bash
ps -ef | grep queue - 控制台真实输出结果:text
www 18785 12345 0 09:10 ? 00:00:05 php queue_consumer.php --queue=order www 18786 12345 0 09:10 ? 00:00:05 php queue_consumer.php --queue=order root 19012 18900 0 09:35 pts/0 00:00:00 grep --color=auto queue
2.2 批量提取 PID 并杀进程(awk vs cut vs xargs)
💬 生产安全杀进程:平滑 kill -15 vs 强制 kill -9
kill -15 <PID>(SIGTERM):通知进程平滑退出。进程会执行 ShutdownHook 钩子,完成正在处理的事务、刷盘并释放资源(生产首选);kill -9 <PID>(SIGKILL):操作系统强制杀死进程。不给进程任何清理的机会,可能导致数据库事务撕裂、文件损坏或未完成的 MQ 消息丢失。
1. 基于 awk 提取 PID 并平滑/强制批量杀死:
- 命令输入 1 (提取 PID):bash
ps -ef | grep queue | grep -v grep | awk '{print $2}' - 控制台输出结果:text
18785 18786 - 命令输入 2 (管道批量杀进程):bash
# 方式 A:平滑退出 (推荐) ps -ef | grep queue | grep -v grep | awk '{print $2}' | xargs kill -15 # 方式 B:强行杀死 ps -ef | grep queue | grep -v grep | awk '{print $2}' | xargs kill -9
2. 基于 cut 截取字符列提取 PID:
- 命令输入:bash
ps -ef | grep "queue" | grep -v grep | cut -c 9-15 | xargs kill -9
2.3 nohup 指定用户后台无挂断运行
- 命令输入:bash
# 以 www 用户身份在后台运行,并将输出追加至 nohup.out nohup sudo -u www php start.php --d & # 指定配置文件与端口后台启动 Java Jar 包 nohup java -jar api-0.3.jar -Dspring.config.location=application-prod.yml -Dserver.port=6894 & - 控制台真实输出结果:text
[1] 18785 nohup: appending output to 'nohup.out' - 解析:
[1] 18785表示后台任务编号为 1,进程 PID 为 18785。
第 3 章:网络端口监听、套接字诊断与句柄泄漏排查 (命令 + 真实输出对比)
3.1 端口占用诊断:netstat vs ss vs lsof
1. 使用 netstat 检查监听端口:
- 命令输入:bash
netstat -tulnp | grep 51010 - 控制台真实输出结果:text
tcp 0 0 0.0.0.0:51010 0.0.0.0:* LISTEN 18785/php-fpm: mast - 结果解析:表明
51010端口正在被 PID 为18785的php-fpm主进程以 TCP 监听 (LISTEN)。
2. 使用 ss 进行高性能套接字诊断:
- 命令输入:bash
ss -anp | grep 51010 - 控制台真实输出结果:text
LISTEN 0 128 0.0.0.0:51010 0.0.0.0:* users:(("php-fpm",pid=18785,fd=7))
3. 使用 lsof 反查端口:
- 命令输入:bash
lsof -i:51010 # 或: sudo lsof -i :8033 - 控制台真实输出结果:text
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME php-fpm 18785 www 7u IPv4 89234 0t0 TCP *:51010 (LISTEN)
3.2 句柄泄漏排查:Too many open files 故障定位
当系统抛出 java.io.IOException: Too many open files 时,说明进程打开的文件句柄数达到了物理上限。
1. 统计进程打开的文件句柄数:
- 命令输入:bash
lsof -p 12345 | wc -l - 控制台输出结果:text
65535 - 解析:句柄数达到了上限值 65535。
2. 查看 /proc 伪文件系统中的物理描述符 (fd):
- 命令输入:bash
ls -l /proc/17993/fd - 控制台真实输出结果:text
total 0 lrwx------ 1 www www 64 Aug 16 09:30 0 -> /dev/null lrwx------ 1 www www 64 Aug 16 09:30 1 -> /var/log/app.log lrwx------ 1 www www 64 Aug 16 09:30 2 -> socket:[123456] lrwx------ 1 www www 64 Aug 16 09:30 3 -> socket:[123457] lrwx------ 1 www www 64 Aug 16 09:30 4 -> socket:[123458] ... - 结果分析与效果对比: 如果发现成千上万个
socket:[123456]呈未关闭状态,效果对比明确证实了代码中存在 HttpClient 链接未关闭或 Unclosed File Stream 泄露。修复代码在finally块中关闭连接后,句柄数瞬间恢复至正常的几百个!
第 4 章:系统调用跟踪、日志实时分析与 MySQL 生产诊疗
4.1 strace 系统调用跟踪 (排查进程卡死在何处)
- 命令输入:bash
# 跟踪进程 PID 12345 的系统调用,仅显示 file 操作和 write 写入,单次打印 200 字节 strace -p 12345 -e trace=file,write -s 200 - 控制台真实输出结果:text
strace: Process 12345 attached write(1, "2026-08-16 09:30:00 [ERROR] Connection timeout to Redis 192.168.1.100:6379\n", 75) = 75 openat(AT_FDCWD, "/var/log/redis_err.log", O_WRONLY|O_CREAT|O_APPEND, 0644) = 3 futex(0x7f8b90001000, FUTEX_WAIT_PRIVATE, 2, NULL ... <unfinished ...> - 结果解析: 第一行显示正在写入日志,第三行显示进程卡死在
futex()私有锁等待 状态,直接定位到进程是在等待获取锁。
4.2 Nginx 生产级高可观 Log_Format 格式与 Logrotate 自动切割 SOP
在海量高并发生产环境中,标准的 Nginx 默认日志格式无法监控后端 Upstream 耗时与真实客户端 IP。
1. Nginx 生产级高可观 log_format main 规范:
在 nginx.conf 的 http 块中配置:
nginx
log_format main
'$remote_addr [$time_local] '
'host="$host" '
'request="$request" '
'status=$status '
'bytes=$body_bytes_sent '
'request_time=$request_time '
'upstream_time=$upstream_response_time '
'upstream_status=$upstream_status '
'referer="$http_referer" '
'ua="$http_user_agent" '
'xff="$http_x_forwarded_for"';
# 应用日志格式
access_log /www/wwwlogs/nginx/nginx_access.log main;
error_log /www/wwwlogs/nginx/nginx_error.log warn;- 关键监测指标:
$request_time:Nginx 处理整个请求的总耗时(包含网络传输 + 后端处理);$upstream_response_time:后端 FastCGI (PHP-FPM) / Java / Go upstream 的纯响应耗时(若$request_time很大而$upstream_response_time很小,说明是客户端网络慢;反之说明后端代码/SQL 慢);$http_x_forwarded_for(xff):记录经过 CDN / CDN 代理后的真实客户端 IP。
2. Logrotate 按天切割与 kill -USR1 平滑切日志脚本
创建 /etc/logrotate.d/nginx-custom 配置文件:
text
/www/wwwlogs/nginx/nginx_access.log
/www/wwwlogs/nginx/nginx_error.log {
daily
rotate 15
missingok
notifempty
compress
delaycompress
dateext
dateformat -%Y%m%d
create 0640 www www
sharedscripts
postrotate
[ -f /www/server/nginx/logs/nginx.pid ] && kill -USR1 $(cat /www/server/nginx/logs/nginx.pid)
endscript
}💬 面试官追问:“Logrotate 切割日志时,为什么必须要执行 kill -USR1 信号?”
“这是因为 Linux 文件描述符 (fd) 与 Inode 物理绑定的原理!
- 直接
mv重命名日志:Logrotate 将nginx_access.log重命名为nginx_access.log-20260816。但 Nginx Worker 进程内部依然保持着指向原 Inode 的文件句柄 (fd); - 如果不发
SIGUSR1信号:Nginx 会继续向已被重命名的-20260816文件中写入日志,导致新建的nginx_access.log保持 0 字节; kill -USR1 <master_pid>物理过程:Nginx Master 收到USR1信号后,通知所有 Worker 进程关闭旧 fd,并重新 open() 打开nginx.conf中配置的新物理日志文件,实现 0 字节丢失、0 请求卡顿的优雅平滑切割!”
3. 测试与强制触发 Logrotate 切割命令
bash
# 手动强制触发日志切割 (测试配置文件语法与效果)
logrotate -f /etc/logrotate.d/nginx-custom
# 查看切割后的物理文件
ls -lh /www/wwwlogs/nginx/nginx_access*4.3 MySQL 线上锁、死锁与慢查询三大诊疗神句
1. 查看当前全量查询 (SHOW FULL PROCESSLIST;)
- SQL 输入:sql
SHOW FULL PROCESSLIST; - 输出控制台表格结果:text
+----+------+-----------------+------+---------+------+----------------------+----------------------------------------------------+ | Id | User | Host | db | Command | Time | State | Info | +----+------+-----------------+------+---------+------+----------------------+----------------------------------------------------+ | 12 | root | 192.168.1.5:5432| shop | Query | 120 | Waiting for table md | ALTER TABLE orders ADD COLUMN remark VARCHAR(255) | | 15 | root | 192.168.1.6:5433| shop | Query | 118 | Locked | UPDATE orders SET status = 'PAID' WHERE id = 1001 | +----+------+-----------------+------+---------+------+----------------------+----------------------------------------------------+ - 结果解析:ID 12 的大表
ALTER TABLE拿到了 MDL 锁,导致 ID 15 的并发UPDATE被阻塞卡死 118 秒 (State: Locked)。
2. 查询慢日志记录表
- SQL 输入:sql
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;
3. 查看 InnoDB 物理死锁报告 (SHOW ENGINE INNODB STATUS\G;)
- SQL 输入:sql
SHOW ENGINE INNODB STATUS\G; - 控制台真实输出结果 (LATEST DETECTED DEADLOCK 区域):text
------------------------ LATEST DETECTED DEADLOCK ------------------------ 2026-08-16 09:35:00 0x7f8b701fe000 *** (1) TRANSACTION: TRANSACTION 28102, ACTIVE 2 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) MySQL thread id 15, OS thread handle 14023, query id 8802 UPDATE orders SET status='PAID' WHERE id=1001 *** (1) WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 42 page no 3 n bits 72 index PRIMARY of table shop.orders trx id 28102 lock_mode X locks rec but not gap waiting *** (2) TRANSACTION: TRANSACTION 28103, ACTIVE 3 sec updating or deleting mysql tables in use 1, locked 1 5 lock struct(s), heap size 1136, 3 row lock(s) MySQL thread id 16, OS thread handle 14024, query id 8805 UPDATE stock SET num=num-1 WHERE product_id=500 *** (2) HOLDS THE LOCK(S): RECORD LOCKS space id 42 page no 3 n bits 72 index PRIMARY of table shop.orders trx id 28103 lock_mode X locks rec but not gap *** (2) WE ARE WAITING FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 43 page no 5 n bits 72 index PRIMARY of table shop.stock trx id 28103 lock_mode X locks rec but not gap waiting *** WE ROLLBACK TRANSACTION (1) - 结果分析与效果对比:
- 报告剖析:事务 1 (Thread 15) 想要拿
orders表的排他锁 (lock_mode X),却被事务 2 (Thread 16) 扣住;而事务 2 又在等待事务 1 释放stock表的锁。两者相互死锁等待! - 恢复解决:MySQL 自动回滚了事务 1 (
WE ROLLBACK TRANSACTION (1))。拿此报告将两边的 SQL 执行顺序调整一致(统一先锁stock再锁orders),彻底杜绝死锁!
- 报告剖析:事务 1 (Thread 15) 想要拿
4.4 Nginx 访问日志切片分析、QPS/PV/UV 统计与 TCP 连接诊断实战
在生产环境遇到异常突发流量、DDoS/CC 攻击或性能瓶颈时,通过 Linux 原生 Shell 工具组合(如 awk, sort, uniq, grep, netstat, tcpdump)对日志和网络状态进行快速切片分析是最直接高效的手段。
1. IP 访问量与独立访客 (UV) 统计分析
统计独立 IP 访问总数 (UV):
bashawk '{print $1}' nginx.log | sort -n | uniq | wc -l- 解析:提取日志第 1 列(客户端 IP),按数字排序去重后统计行数。
查看指定时间段 (如 04:00 - 05:59) 的独立 IP 访问量:
bashgrep "07/Apr/2017:0[4-5]" nginx.log | awk '{print $1}' | sort | uniq -c | sort -nr | wc -l分析访问最频繁的前 100 个 IP (高频访问/疑似刷接口来源):
bashawk '{print $1}' nginx.log | sort -n | uniq -c | sort -rn | head -n 100- 控制台真实输出示例:text
15420 192.168.1.105 8910 10.0.12.33 3200 172.16.0.8
- 控制台真实输出示例:
筛选访问次数超过 100 次的 IP 及其请求数:
bashawk '{print $1}' nginx.log | sort -n | uniq -c | awk '{if($1 > 100) print $0}' | sort -rn查询指定 IP (如
127.0.0.1或恶意 IP) 的详细访问 URL 路径并按频率排序:bashgrep '127.0.0.1' nginx.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 100
2. 热门页面与 URL 访问统计 (PV & 慢页面过滤)
查看访问最频繁的页面/接口路径 (Top 100):
bashawk '{print $7}' nginx.log | sort | uniq -c | sort -rn | head -n 100查看访问最频繁的页面 (排除
.php等静态资源或特定后缀):bashgrep -v ".php" nginx.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 100查看页面访问次数超过 100 次的 URL:
bashcat nginx.log | cut -d ' ' -f 7 | sort | uniq -c | awk '{if ($1 > 100) print $0}' | less查看最近 1000 条日志快照中访问量最高的页面:
bashtail -1000 nginx.log | awk '{print $7}' | sort | uniq -c | sort -nr | less
3. 秒/分/小时级请求量与 QPS 峰值统计
统计每秒请求数 (QPS 峰值 Top 100 时间点,精确到秒):
bashawk '{print $4}' nginx.log | cut -c 14-21 | sort | uniq -c | sort -nr | head -n 100- 解析:
cut -c 14-21截取HH:MM:SS时间戳,配合uniq -c算出每秒的实际处理请求数。
- 解析:
统计每分钟请求数 (RPM 峰值 Top 100 时间点,精确到分钟):
bashawk '{print $4}' nginx.log | cut -c 14-18 | sort | uniq -c | sort -nr | head -n 100统计每小时请求数 (RPH 峰值分布,精确到小时):
bashawk '{print $4}' nginx.log | cut -c 14-15 | sort | uniq -c | sort -nr | head -n 100
4. Nginx 性能与耗时 ($request_time) 深入诊疗
💡 前提条件
日志格式 log_format 需在末尾包含 $request_time 变量(即请求总处理耗时,单位:秒)。
列出响应传输耗时超过 3 秒的页面 (Top 20):
bashcat nginx.log | awk '($NF > 3){print $7}' | sort -n | uniq -c | sort -nr | head -20列出 PHP 动态请求耗时超过 1 秒的页面并统计出现频次 (Top 100):
bashcat nginx.log | awk '($NF > 1 && $7~/\.php/){print $7}' | sort -n | uniq -c | sort -nr | head -100列出耗时极高 (超过 60 秒) 的超时页面及出现次数:
bashcat nginx.log | awk '($NF > 60 && $7~/\.php/){print $7}' | sort -n | uniq -c | sort -nr | head -100统计输出响应大小超过 200KB 的大文件及其页面频次:
bashcat nginx.log | awk '($10 > 200000 && $7~/\.php/){print $7}' | sort -n | uniq -c | sort -nr | head -100
5. 蜘蛛 (Baiduspider) 爬虫抓取与 HTTP 状态码分布统计
统计百度蜘蛛 (Baiduspider) 抓取总次数:
bashgrep 'Baiduspider' nginx.log | wc -l统计百度蜘蛛抓取触发 404 错误的次数:
bashgrep 'Baiduspider' nginx.log | grep '404' | wc -l统计全局 HTTP Status 响应状态码分布 (200 / 304 / 404 / 500 / 502 等):
bashcat nginx.log | awk '{counts[$9]+=1}; END {for(code in counts) print code, counts[code]}' | sort -nr -k2离线计算网站总消耗流量 (GB):
bashcat nginx.log | awk '{sum+=$10} END {print sum/1024/1024/1024 " GB"}'
6. TCP 网络套接字状态与并发诊疗实战
查看当前 HTTP (80 端口) ESTABLISHED 已建立连接数:
bashnetstat -tan | grep "ESTABLISHED" | grep ":80" | wc -l使用
tcpdump实时嗅探 80 端口抓包 1000 个,并按来源 IP 排序:bashtcpdump -i eth0 -tnn dst port 80 -c 1000 | awk -F"." '{print $1"."$2"."$3"."$4}' | sort | uniq -c | sort -nr | head -20全量统计 TCP 各连接状态数量 (ESTABLISHED, TIME_WAIT, SYN_RECV, LAST_ACK 等):
bashnetstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'- 控制台真实输出示例:text
SYN_RECV 30 ESTABLISHED 1597 FIN_WAIT1 51 FIN_WAIT2 504 TIME_WAIT 1057 LAST_ACK 5
- 控制台真实输出示例:
使用
watch实时动态监控 TCP 套接字状态变化:bashwatch -n 1 "netstat -n | awk '/^tcp/ {++S[\$NF]} END {for(a in S) print a, S[a]}'"查出 TIME_WAIT 或 SYN_RECV 连接数异常最高的来源 IP (定位 CC/SYN Flood 攻击来源):
bash# 查找大量 TIME_WAIT 来源 IP Top 20 netstat -n | grep TIME_WAIT | awk '{print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -rn | head -n 20 # 查找大量 SYN 连接来源 IP Top 20 netstat -an | grep SYN | awk '{print $5}' | awk -F: '{print $1}' | sort | uniq -c | sort -nr | head -n 20
第 5 章:Maven 构建打包与 Jar 包部署 (命令 + 真实输出对比)
- 命令输入 1 (跳过测试构建打包):bash
mvn clean package -Dmaven.test.skip=true - 控制台输出结果:text
[INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 12.450 s [INFO] Finished at: 2026-08-16T09:35:10+08:00 [INFO] ------------------------------------------------------------------------
🔍 本章 6 重自审计报告
- 【知识审计】:已全量补充 CPU 100% 黄金排查四步法真实控制台输出结果与对比分析、
ps/awk/cut/xargs kill的纯 PID 提取效果对比、netstat/ss/lsof端口与/proc/PID/fd句柄泄露前后对比、strace系统调用真实输出、SHOW FULL PROCESSLIST;表格及SHOW ENGINE INNODB STATUS\G;最新死锁日志解析!新增 Nginx 访问日志切片分析(IP/PV/UV/QPS/慢请求)与 TCP 连接状态统计及tcpdump抓包实战!已适配移动端::: info 💬自适应排版!