故障排查实战

生产环境故障排查需要系统化的方法和丰富的实战经验。本章介绍常见故障的排查思路和解决方案。

故障排查流程

1. 故障现象确认
   ├─ 收集错误信息
   ├─ 复现问题
   └─ 确定影响范围

2. 初步诊断
   ├─ 检查系统日志
   ├─ 查看监控指标
   └─ 排除常见原因

3. 深入分析
   ├─ 使用诊断工具
   ├─ 分析根本原因
   └─ 验证假设

4. 解决问题
   ├─ 实施解决方案
   ├─ 验证修复效果
   └─ 文档记录

5. 预防措施
   ├─ 完善监控
   ├─ 更新文档
   └─ 制定预案

系统无法启动

案例1:Grub 损坏

现象:重启后显示 "grub rescue>"

解决步骤

# 1. 查找 Linux 分区
grub rescue> ls
(hd0) (hd0,msdos1) (hd0,msdos2)

# 2. 逐个检查分区
grub rescue> ls (hd0,msdos1)/
error: unknown filesystem

grub rescue> ls (hd0,msdos2)/
boot/ etc/ home/ ...

# 3. 设置正确的分区
grub rescue> set root=(hd0,msdos2)
grub rescue> set prefix=(hd0,msdos2)/boot/grub

# 4. 加载模块
grub rescue> insmod normal
grub rescue> normal

# 5. 进入系统后重新安装 grub
sudo grub-install /dev/sda
sudo update-grub

案例2:文件系统损坏

现象:系统启动失败,提示文件系统错误

解决步骤

# 1. 进入救援模式
# 在 GRUB 菜单添加参数:systemd.unit=rescue.target

# 2. 以只读方式挂载根分区
mount -o remount,ro /

# 3. 检查并修复文件系统
fsck -y /dev/sda1

# 4. 如果是 XFS 文件系统
xfs_repair /dev/sda1

# 5. 重新挂载为读写
mount -o remount,rw /

# 6. 重启
reboot

案例3:忘记 root 密码

解决步骤

# 1. GRUB 菜单按 'e' 编辑
# 2. 找到 linux 行,在末尾添加:init=/bin/bash
# 3. Ctrl+X 启动

# 4. 重新挂载根分区为读写
mount -o remount,rw /

# 5. 修改密码
passwd root

# 6. 更新 SELinux 上下文(如果启用)
touch /.autorelabel

# 7. 正常启动
exec /sbin/init

CPU 问题排查

案例:CPU 使用率持续 100%

排查脚本

#!/bin/bash
# diagnose-high-cpu.sh

echo "=== CPU 使用率分析 ==="
echo "时间: $(date)"
echo ""

# 1. 查看整体 CPU 使用
echo "--- 系统负载 ---"
uptime
echo ""

# 2. 找出 CPU 占用最高的进程
echo "--- Top 10 CPU 进程 ---"
ps aux --sort=-%cpu | head -11
echo ""

# 3. 获取最高 CPU 进程的详细信息
HIGH_CPU_PID=$(ps aux --sort=-%cpu | awk 'NR==2 {print $2}')
echo "--- 进程 $HIGH_CPU_PID 详细信息 ---"
ps -p $HIGH_CPU_PID -o pid,ppid,cmd,pcpu,pmem,vsz,rss,stat,start,time
echo ""

# 4. 查看进程的线程
echo "--- 进程 $HIGH_CPU_PID 的线程 CPU 使用 ---"
ps -L -p $HIGH_CPU_PID -o pid,tid,pcpu,pmem,comm
echo ""

# 5. 查看进程打开的文件
echo "--- 进程 $HIGH_CPU_PID 打开的文件 ---"
lsof -p $HIGH_CPU_PID | head -20
echo ""

# 6. 系统调用统计(采样 5 秒)
echo "--- 系统调用统计 ---"
timeout 5 strace -c -p $HIGH_CPU_PID 2>&1 | grep -v "Process"
echo ""

# 7. CPU 核心使用分布
echo "--- CPU 核心使用情况 ---"
mpstat -P ALL 1 3

常见原因和解决方法

# 1. 死循环代码
# 解决:使用 gdb 或 perf 分析热点函数

# 2. 正则表达式性能问题
grep -P 'complex.*regex' huge_file.txt
# 优化:使用更简单的正则或其他工具

# 3. 进程未正常退出
# 检查僵尸进程
ps aux | grep Z

# 清理僵尸进程(杀死父进程)
kill -9 $(ps -o ppid= -p <zombie_pid>)

内存问题排查

案例:系统内存不足

诊断脚本

#!/bin/bash
# diagnose-memory.sh

echo "=== 内存使用分析 ==="

# 1. 内存概览
echo "--- 内存使用情况 ---"
free -h
echo ""

# 2. 详细内存信息
echo "--- 详细内存统计 ---"
cat /proc/meminfo | grep -E 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab'
echo ""

# 3. 内存占用 Top 10
echo "--- Top 10 内存占用进程 ---"
ps aux --sort=-%mem | head -11
echo ""

# 4. 每个进程的内存详情
echo "--- 进程内存详细信息 ---"
for pid in $(ps aux --sort=-%mem | awk 'NR>1 && NR<=11 {print $2}'); do
    echo "PID: $pid"
    cat /proc/$pid/status 2>/dev/null | grep -E 'Name|VmSize|VmRSS|VmSwap'
    echo ""
done

# 5. Slab 内存使用
echo "--- Slab 内存占用 Top 10 ---"
sudo slabtop -o | head -20
echo ""

# 6. OOM Killer 历史
echo "--- OOM Killer 事件 ---"
dmesg | grep -i 'out of memory'

内存泄漏检测

#!/bin/bash
# detect-memory-leak.sh

PID=$1
INTERVAL=5
COUNT=12

if [ -z "$PID" ]; then
    echo "用法: $0 <PID>"
    exit 1
fi

echo "监控进程 $PID 的内存使用(每 $INTERVAL 秒)"
echo "时间,VSZ(KB),RSS(KB),内存%"

for i in $(seq 1 $COUNT); do
    ps -p $PID -o pid,vsz,rss,%mem --no-headers | \
        awk -v time="$(date +%H:%M:%S)" '{printf "%s,%s,%s,%s\n", time,$2,$3,$4}'
    sleep $INTERVAL
done

OOM Killer 分析

# 查看 OOM 日志
journalctl -k | grep -i 'killed process'

# 配置 OOM 分数调整
echo -1000 > /proc/<PID>/oom_score_adj  # -1000 禁用 OOM killer
echo 1000 > /proc/<PID>/oom_score_adj   # 1000 优先被杀

# 查看进程的 OOM 分数
cat /proc/<PID>/oom_score

磁盘问题排查

案例:磁盘空间不足

快速定位大文件

#!/bin/bash
# find-large-files.sh

echo "=== 磁盘使用情况 ==="
df -h
echo ""

echo "=== 各目录占用空间 Top 10 ==="
du -h --max-depth=1 / 2>/dev/null | sort -hr | head -10
echo ""

echo "=== 查找大于 100MB 的文件 ==="
find / -type f -size +100M -exec ls -lh {} \; 2>/dev/null | \
    awk '{ print $9 ": " $5 }' | sort -k2 -hr | head -20
echo ""

echo "=== 查找大量小文件的目录 ==="
find / -type d -exec sh -c 'echo "$(find "{}" -maxdepth 1 -type f | wc -l) {}"' \; 2>/dev/null | \
    sort -rn | head -10

清理空间

# 1. 清理日志文件
journalctl --vacuum-time=7d    # 保留最近 7 天
journalctl --vacuum-size=1G    # 限制最大 1GB

# 2. 清理包管理器缓存
# Debian/Ubuntu
apt clean
apt autoclean

# RHEL/CentOS
yum clean all

# 3. 清理旧内核
# Ubuntu
apt autoremove --purge

# 4. 查找并删除已删除但未释放的文件
lsof | grep deleted
kill -9 <PID>  # 重启持有文件的进程

案例:磁盘 I/O 性能下降

诊断脚本

#!/bin/bash
# diagnose-disk-io.sh

echo "=== 磁盘 I/O 分析 ==="

# 1. I/O 统计
echo "--- 磁盘 I/O 使用率 ---"
iostat -x 1 5
echo ""

# 2. I/O 密集型进程
echo "--- I/O 密集型进程 ---"
iotop -b -n 1 -o
echo ""

# 3. 磁盘健康状态
echo "--- 磁盘健康检查 ---"
for disk in /dev/sd[a-z]; do
    if [ -b "$disk" ]; then
        echo "检查 $disk"
        smartctl -H $disk 2>/dev/null
    fi
done
echo ""

# 4. 挂载点 I/O 统计
echo "--- 文件系统 I/O ---"
cat /proc/diskstats

网络问题排查

案例:网络连接故障

网络诊断脚本

#!/bin/bash
# diagnose-network.sh

TARGET_HOST="8.8.8.8"
TARGET_PORT=443

echo "=== 网络诊断 ==="

# 1. 网络接口状态
echo "--- 网络接口 ---"
ip addr show
echo ""

# 2. 路由表
echo "--- 路由表 ---"
ip route show
echo ""

# 3. DNS 解析
echo "--- DNS 解析测试 ---"
nslookup google.com
echo ""

# 4. 连通性测试
echo "--- Ping 测试 ---"
ping -c 4 $TARGET_HOST
echo ""

# 5. 路由追踪
echo "--- 路由追踪 ---"
traceroute -n $TARGET_HOST
echo ""

# 6. 端口连接测试
echo "--- 端口连接测试 ---"
nc -zv $TARGET_HOST $TARGET_PORT
echo ""

# 7. 防火墙规则
echo "--- 防火墙规则 ---"
iptables -L -n -v
echo ""

# 8. 网络统计
echo "--- 网络连接统计 ---"
ss -s

案例:网络丢包

# 查看网络错误统计
netstat -i
ip -s link show

# 检查丢包
ping -c 100 -i 0.2 $TARGET | grep loss

# 查看网络缓冲区
sysctl net.core.rmem_max
sysctl net.core.wmem_max

# 调整缓冲区大小
sysctl -w net.core.rmem_max=134217728
sysctl -w net.core.wmem_max=134217728

服务不可用排查

Web 服务故障

Nginx 故障排查

#!/bin/bash
# diagnose-nginx.sh

echo "=== Nginx 诊断 ==="

# 1. 服务状态
echo "--- Nginx 服务状态 ---"
systemctl status nginx
echo ""

# 2. 配置测试
echo "--- 配置文件语法检查 ---"
nginx -t
echo ""

# 3. 端口监听
echo "--- 端口监听状态 ---"
ss -tuln | grep :80
ss -tuln | grep :443
echo ""

# 4. 进程信息
echo "--- Nginx 进程 ---"
ps aux | grep nginx
echo ""

# 5. 错误日志
echo "--- 最近错误日志 ---"
tail -50 /var/log/nginx/error.log
echo ""

# 6. 访问日志
echo "--- 最近访问日志 ---"
tail -20 /var/log/nginx/access.log
echo ""

# 7. 连接数统计
echo "--- 当前连接数 ---"
ss -ant | grep :80 | wc -l

数据库故障

MySQL 故障排查

#!/bin/bash
# diagnose-mysql.sh

echo "=== MySQL 诊断 ==="

# 1. 服务状态
systemctl status mysql

# 2. 连接测试
mysql -u root -p -e "SELECT 1;"

# 3. 查看进程列表
mysql -u root -p -e "SHOW PROCESSLIST;"

# 4. 查看锁等待
mysql -u root -p -e "SHOW ENGINE INNODB STATUS\G" | grep -A 10 "LATEST DETECTED DEADLOCK"

# 5. 慢查询日志
tail -100 /var/log/mysql/slow-query.log

# 6. 错误日志
tail -100 /var/log/mysql/error.log

# 7. 连接数
mysql -u root -p -e "SHOW STATUS LIKE 'Threads_connected';"
mysql -u root -p -e "SHOW STATUS LIKE 'Max_used_connections';"

故障预防

监控脚本

#!/bin/bash
# health-check.sh

ALERT_EMAIL="admin@example.com"
ALERT_THRESHOLD_CPU=80
ALERT_THRESHOLD_MEM=90
ALERT_THRESHOLD_DISK=85

# CPU 检查
CPU_USAGE=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
if (( $(echo "$CPU_USAGE > $ALERT_THRESHOLD_CPU" | bc -l) )); then
    echo "警告: CPU 使用率 ${CPU_USAGE}% 超过阈值 ${ALERT_THRESHOLD_CPU}%" | \
        mail -s "CPU Alert" $ALERT_EMAIL
fi

# 内存检查
MEM_USAGE=$(free | grep Mem | awk '{printf("%.0f", $3/$2 * 100)}')
if [ $MEM_USAGE -gt $ALERT_THRESHOLD_MEM ]; then
    echo "警告: 内存使用率 ${MEM_USAGE}% 超过阈值 ${ALERT_THRESHOLD_MEM}%" | \
        mail -s "Memory Alert" $ALERT_EMAIL
fi

# 磁盘检查
df -h | awk 'NR>1 {print $5 " " $6}' | while read output; do
    usage=$(echo $output | awk '{print $1}' | sed 's/%//g')
    mount=$(echo $output | awk '{print $2}')
    if [ $usage -gt $ALERT_THRESHOLD_DISK ]; then
        echo "警告: 磁盘 $mount 使用率 ${usage}% 超过阈值 ${ALERT_THRESHOLD_DISK}%" | \
            mail -s "Disk Alert" $ALERT_EMAIL
    fi
done

总结:故障排查是一个系统工程,需要扎实的基础知识、丰富的实战经验和科学的排查方法。