第18章 Linux故障排查与恢复
写在前面的话
想象一下,你是一个城市的"消防队长"。城市里每天都有大大小小的"火灾"------服务器突然卡死了、网站打不开了、磁盘满了报错了......你的任务就是在最短的时间内找到火源、扑灭大火、恢复秩序。这就是Linux故障排查与恢复的核心使命。
这一章是整本书的"压轴大戏"。前面17章你学了怎么用Linux、怎么配置服务、怎么写脚本,而现在,你要学的是------当一切都不工作的时候,你该怎么办。这就像学开车一样,前面学的是怎么踩油门、怎么打方向盘,而这一章学的是------当车子在高速上突然爆胎了,你该怎么冷静应对。
本章面向零基础小白,我们会用大量的生活类比(医生诊断、消防救火、侦探破案等)来讲解每一个概念,确保你看得懂、学得会、用得上。每一个命令都会给出完整示例,每一个案例都来自真实的运维场景。
本章学习目标:
- 掌握系统化的故障排查方法论
- 能够独立诊断和修复系统启动失败
- 学会修复损坏的文件系统
- 熟练排查网络故障
- 诊断和解决性能瓶颈问题
- 处理内存泄漏、磁盘满、僵尸进程等常见问题
- 掌握日志分析技巧
- 熟练使用单用户模式和救援模式
- 制定数据备份与恢复策略
- 组织灾难恢复演练
18.1 故障排查方法论
18.1.1 什么是故障排查?------用医生看病来类比
你有没有去医院看病的经历?当你身体不舒服去看医生时,医生会怎么做?
- 先问症状:"哪里不舒服?什么时候开始的?疼了多久?"
- 再查体征:量体温、测血压、听心跳
- 做检查:验血、拍X光、做CT
- 下诊断:根据检查结果判断是什么病
- 开药方:针对诊断结果给出治疗方案
- 观察疗效:吃药后看看有没有好转,没好转就换方案
Linux故障排查的过程和医生看病简直一模一样!只不过"病人"换成了你的Linux系统,"医生"就是你自己。
| 医生看病 | Linux故障排查 |
|---|---|
| 问症状:哪里不舒服? | 收集故障现象:什么报错?什么时候开始的? |
| 量体温、测血压 | 查看系统资源:CPU、内存、磁盘使用率 |
| 验血、拍X光 | 查看日志文件、运行诊断命令 |
| 下诊断 | 定位根本原因 |
| 开药方 | 执行修复操作 |
| 观察疗效 | 验证故障是否消除 |
18.1.2 故障排查的黄金法则
在正式学习具体技巧之前,你必须牢记以下几条"黄金法则"。这些法则是无数运维工程师用血泪换来的经验,就像消防员进入火场前必须检查装备一样重要。
法则一:先冷静,再动手
当生产服务器出故障时,你的第一反应可能是慌张。但慌张是排障最大的敌人。就像消防员面对大火一样,越慌越容易出错。深呼吸三次,告诉自己:"Linux几乎所有的故障都是有迹可循的,我一定能找到原因。"
法则二:先收集信息,再下结论
很多新手一看到报错就急于修改,结果越改越乱。正确做法是先把故障信息收集完整:
- 报错信息是什么?(截屏、复制完整报错)
- 故障什么时候开始的?(精确到时间点)
- 影响范围多大?(一台机器还是多台?)
- 最近做过什么变更?(安装了什么?修改了什么配置?)
bash
# 查看系统最近的重启和关机记录
last reboot | head -20
last -x | head -30
# 查看系统启动时间,判断是否最近重启过
uptime
who -b
# 查看最近的登录记录,看有没有人操作过
last | head -20
法则三:一次只改一个地方
这是极其重要的一条规则!如果你同时改了三个地方,故障消失了,你根本不知道是哪个改动起了作用。如果故障变得更严重了,你也不知道是哪个改动导致的。所以,每次只做一个修改,然后观察效果。
法则四:先备份,再修改
在修改任何配置文件之前,先备份!这就像做手术前要先签知情同意书一样。万一改错了,你还能恢复回来。
bash
# 修改前先备份(加上日期标记)
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%Y%m%d)
# 更好的做法:用版本控制管理配置文件
cd /etc/nginx
git init
git add -A
git commit -m "修改前的备份状态"
法则五:从最可能的原因开始排查
医生看病不会一上来就让你做全身CT,而是先从最常见的病开始排除。Linux故障排查也一样,先检查最常见的故障原因,再逐步深入。
18.1.3 故障排查的系统化流程------OSI模型思维
我们可以把故障排查想象成"剥洋葱",从外到内一层一层地排查。这里介绍一个非常实用的"分层排查法":
第一层:物理层(硬件是否正常?)
- 服务器电源是否正常?
- 网线是否插好?
- 硬盘指示灯是否正常闪烁?
第二层:系统层(操作系统是否正常?)
- 系统能正常启动吗?
- 内核有没有报错?
- 文件系统是否正常?
第三层:网络层(网络是否通畅?)
- IP地址配置正确吗?
- 能ping通网关吗?
- DNS解析正常吗?
第四层:应用层(服务是否正常?)
- 服务进程是否在运行?
- 端口是否在监听?
- 配置文件是否正确?
第五层:用户层(用户体验如何?)
- 用户能正常访问吗?
- 响应速度是否正常?
- 有没有报错页面?
让我们用一个实际例子来演示这个流程:
bash
# ===== 第一层:物理层检查 =====
# 查看硬件信息
dmesg | grep -i error
dmesg | grep -i "hardware"
# 查看磁盘健康状态(需要smartmontools)
smartctl -a /dev/sda
# ===== 第二层:系统层检查 =====
# 查看系统负载
uptime
# 输出示例: 10:30:01 up 30 days, 5:20, 3 users, load average: 0.20, 0.18, 0.15
# 查看内存使用情况
free -h
# 查看磁盘使用情况
df -h
# ===== 第三层:网络层检查 =====
# 查看IP地址
ip addr show
# 测试网络连通性
ping -c 4 8.8.8.8
# 测试DNS解析
nslookup www.baidu.com
# ===== 第四层:应用层检查 =====
# 查看服务状态
systemctl status nginx
# 查看端口监听
ss -tlnp | grep :80
# 查看服务日志
journalctl -u nginx --since "1 hour ago"
# ===== 第五层:用户层检查 =====
# 模拟用户访问
curl -I http://localhost
18.1.4 常见故障排查误区
误区一:"重启能解决90%的问题"
很多人开玩笑说"重启大法好"。确实,重启有时能暂时解决问题,但它掩盖了真正的故障原因。就像你头疼吃止疼药,疼是不疼了,但病还在。在排查阶段,尽量不要急着重启,先把现场信息收集好。
bash
# 在重启前,先收集诊断信息
# 保存当前进程快照
ps aux > /tmp/ps_snapshot_$(date +%Y%m%d_%H%M%S).txt
# 保存当前网络连接
ss -tunap > /tmp/ss_snapshot_$(date +%Y%m%d_%H%M%S).txt
# 保存当前内存状态
free -h > /tmp/memory_snapshot_$(date +%Y%m%d_%H%M%S).txt
# 保存dmesg输出
dmesg > /tmp/dmesg_snapshot_$(date +%Y%m%d_%H%M%S).txt
误区二:盲目搜索引擎
遇到问题直接Google或问AI当然可以,但如果你连故障现象都没搞清楚,搜索到的答案可能南辕北辙。正确做法是:先搞清楚症状,提取关键词,再搜索。
误区三:忽视日志
日志是Linux系统留给你的"遗言"------出了问题,系统在日志里几乎都留下了线索。不查日志就排查故障,就像侦探不看现场证据就破案一样荒唐。
18.1.5 建立故障排查文档
每排障一次,都要记录下来。这就像医生的病历本,记录得越详细,以后遇到类似问题就越快解决。建议每个运维人员都维护一个"故障案例库":
故障案例记录模板:
==============================
故障编号:#2024-001
故障时间:2024-01-15 10:30
故障现象:Nginx网站无法访问
影响范围:所有用户
故障级别:P1(严重)
排查过程:
1. 检查nginx服务状态 -> 正在运行
2. 检查端口监听 -> 80端口正常监听
3. 检查防火墙 -> iptables规则异常,80端口被禁止
根本原因:防火墙规则被误修改
解决方案:恢复防火墙规则,开放80端口
经验总结:修改防火墙前必须备份,修改后必须验证
==============================
18.1.6 小结
故障排查方法论是整章的基础。记住核心要点:
- 像医生看病一样系统化排查
- 先收集信息再动手
- 一次只改一个地方
- 先备份再修改
- 从最可能的原因开始排查
- 分层排查(物理→系统→网络→应用→用户)
- 重视日志,建立故障案例库
掌握了这套方法论,你就有了"渔"的能力,后面的具体技术就是"鱼"了。
18.1.7 故障排查的常用工具箱
就像消防员有各种专业的救火工具一样,Linux运维工程师也需要掌握一套"工具箱"。下面介绍几个最常用的排障工具,你不需要一次全部记住,但要知道在什么场景下用什么工具。
bash
# ===== 系统信息类工具 =====
# uname - 查看内核版本和系统架构
uname -a
# Linux myserver 3.10.0-1160.el7.x86_64 #1 SMP ... x86_64 GNU/Linux
# hostnamectl - 查看主机详细信息(systemd系统)
hostnamectl
# Operating System: CentOS Linux 7 (Core)
# Kernel: Linux 3.10.0-1160.el7.x86_64
# Architecture: x86-64
# lscpu - 查看CPU信息
lscpu
# Architecture: x86_64
# CPU(s): 4 <-- 4个CPU核心
# Model name: Intel(R) Xeon(R) CPU E5-2680
# lsof - 列出打开的文件(Linux中一切皆文件,所以这个工具极其强大)
lsof -i :80 # 查看谁在使用80端口
lsof -i tcp # 查看所有TCP连接
lsof -u mysql # 查看mysql用户打开的文件
lsof /var/log/messages # 查看谁在写日志文件
lsof +D /tmp # 查看谁在使用/tmp目录下的文件
# strace - 追踪进程的系统调用(排障终极武器)
# 当程序行为异常但不知道原因时,strace可以看到程序到底在做什么
strace -p 1234 # 追踪正在运行的进程
strace -e trace=network ./program # 只追踪网络相关的系统调用
strace -e trace=file ./program # 只追踪文件相关的系统调用
# ===== 综合诊断脚本 =====
# Linux系统自带了一个综合诊断脚本,可以一键收集系统信息
# 部分系统可能需要安装:yum install sos 或 apt install sosreport
sosreport
# 会生成一个打包的诊断报告,在联系厂商技术支持时经常需要
18.1.8 故障排查的心态修炼
技术可以学,但心态需要修炼。一个优秀的运维工程师在面对故障时,心态就像经验丰富的外科医生------冷静、专注、有条不紊。
第一层心态:不要恐慌
生产环境出故障时,几十个用户在等着,老板在旁边看着,你手心冒汗------这是正常的。但恐慌只会让你犯错。记住:任何故障都有原因,任何原因都有解决方案。深呼吸,然后开始系统化排查。
第二层心态:不要急于甩锅
"这不是我的问题,是开发的问题"、"这是网络的问题"、"这是硬件的问题"------甩锅不会让故障消失。先解决问题,再追究责任。故障恢复永远是第一优先级。
第三层心态:不要隐瞒
如果你操作失误导致了故障,不要试图隐瞒。坦诚地告诉团队发生了什么,大家一起解决。隐瞒只会让问题更严重,失去团队的信任。
第四层心态:事后复盘
故障解决后,一定要做复盘(Post-Mortem)。不是追究谁的责任,而是找出:
- 故障是怎么发生的?
- 为什么没有更早发现?
- 怎么防止再次发生?
- 排障过程中哪些地方可以改进?
每一次故障都是一次学习的机会。善于复盘的工程师,会越来越强。
18.2 系统无法启动排查
18.2.1 Linux启动过程详解------用"早起上班"来类比
要排查启动故障,你必须先了解Linux是怎么启动的。让我们用"早起上班"的过程来类比Linux的启动流程:
BIOS/UEFI阶段 → 你被闹钟叫醒了
↓
GRUB阶段 → 你在考虑今天穿什么衣服、走哪条路
↓
内核加载阶段 → 你出门开始上班了
↓
init/systemd → 你到了公司,开始安排今天的工作
↓
服务启动 → 各项工作开始执行
↓
登录界面 → 工作就绪,可以开始干活了
第一步:BIOS/UEFI(闹钟响了)
当你按下电源键,电脑主板上有一块小芯片叫BIOS(或新的UEFI),它就像你的闹钟。BIOS做的第一件事是"自检"(POST, Power-On Self Test),检查CPU、内存、显卡等硬件是否正常。这就像你醒来后先揉揉眼睛、伸个懒腰,确认自己身体没毛病。
然后BIOS按照设定的顺序去找启动盘------先看看U盘能不能启动,再看看硬盘,这叫"启动顺序"(Boot Order)。
第二步:GRUB(选择穿什么衣服)
BIOS找到启动盘后,会把控制权交给GRUB(GRand Unified Bootloader)。GRUB就是那个让你选择"启动哪个系统"的菜单。你装过双系统的话,肯定见过这个菜单。
GRUB做的事情:
- 读取自己的配置文件
/boot/grub/grub.cfg(或/boot/grub2/grub.cfg) - 显示启动菜单(如果配置了的话)
- 根据你的选择,加载对应的内核
第三步:内核加载(出门上班)
GRUB加载内核(/boot/vmlinuz-xxx)和初始化内存盘(/boot/initramfs-xxx)到内存中。内核开始接管系统,就像你真正出门开始上班了。
内核做的事情:
- 初始化硬件驱动
- 挂载根文件系统(root filesystem)
- 启动第一个进程------init(或systemd),PID为1
第四步:systemd/init(安排今天的工作)
systemd是现代Linux的"大管家"(CentOS 7+、Ubuntu 16.04+都使用systemd)。它就像公司的部门经理,负责按顺序启动各种服务。
bash
# 查看systemd的启动过程
systemd-analyze
# 输出示例:Startup finished in 3.210s (kernel) + 12.530s (userspace) = 15.740s
# 查看每个服务的启动耗时
systemd-analyze blame | head -20
# 查看启动时的关键路径(哪些服务是串行启动的)
systemd-analyze critical-chain
第五步:登录界面(准备就绪)
所有服务启动完毕后,系统显示登录界面,你就可以输入用户名密码登录了。
18.2.2 启动故障的分类与排查
了解了启动过程,我们就知道故障可能出现在哪个环节。就像早起上班,可能是闹钟没响、可能是衣服找不到、可能是路上堵车、也可能是到了公司发现钥匙丢了。
故障一:BIOS阶段故障------"闹钟没响"
现象:按下电源键后屏幕全黑,没有任何显示。
可能原因:
- 电源问题(电源线没插好、电源坏了)
- 主板问题(BIOS电池没电了、主板坏了)
- 硬件故障(内存条松了、CPU故障)
排查方法:
- 听主板有没有"滴"一声------这是BIOS自检通过的声音
- 听有没有连续的"滴滴"报警声------不同报警声代表不同硬件故障
- 检查显示器连接是否正常
这类故障属于硬件层面,通常需要物理接触服务器,不在纯软件层面能解决的范围内。
故障二:GRUB阶段故障------"找不到路了"
现象 :屏幕显示 grub> 或 grub rescue> 提示符,或者显示 GRUB loading... 后卡住。
这是GRUB找不到配置文件或内核文件了。就像你出门后发现导航坏了,不知道该怎么走了。
排查与修复------grub rescue模式:
当看到 grub rescue> 提示符时,说明GRUB的基本程序还在,但找不到模块和配置文件了。这时候需要手动引导:
bash
# 第一步:查看有哪些硬盘和分区
grub rescue> ls
# 输出示例:(hd0) (hd0,msdos1) (hd0,msdos2) (hd1) (hd1,msdos1)
# 第二步:逐个查看分区,找到根文件系统所在分区
grub rescue> ls (hd0,msdos1)/
# 如果输出"Filesystem is unknown",说明这个分区不是我们要找的
# 如果输出文件列表(bin boot dev etc ...),说明找到了!
# 第三步:设置根分区和前缀
grub rescue> set root=(hd0,msdos1)
grub rescue> set prefix=(hd0,msdos1)/boot/grub
# 第四步:加载normal模块
grub rescue> insmod normal
# 第五步:进入正常的GRUB菜单
grub rescue> normal
# 进入GRUB菜单后选择系统启动
# 启动成功后,重新安装GRUB
sudo grub-install /dev/sda
sudo update-grub
排查与修复------重新安装GRUB:
有时候GRUB配置文件损坏,需要重新生成:
bash
# 方法一:在运行的系统中重新生成GRUB配置
sudo grub2-mkconfig -o /boot/grub2/grub.cfg # CentOS/RHEL
sudo update-grub # Ubuntu/Debian
# 方法二:使用Live CD/USB启动后修复
# 1. 用U盘启动Live系统
# 2. 挂载根分区
sudo mount /dev/sda1 /mnt
# 3. 如果有独立的/boot分区,也要挂载
sudo mount /dev/sda2 /mnt/boot
# 4. 挂载虚拟文件系统
sudo mount --bind /dev /mnt/dev
sudo mount --bind /proc /mnt/proc
sudo mount --bind /sys /mnt/sys
# 5. chroot到挂载的系统
sudo chroot /mnt
# 6. 重新安装GRUB
grub-install /dev/sda
grub2-mkconfig -o /boot/grub2/grub.cfg
# 7. 退出并重启
exit
sudo reboot
故障三:内核加载阶段故障------"路上出事了"
现象 :内核加载过程中卡住,或者显示 Kernel Panic(内核恐慌)。
Kernel Panic就像你上班路上突然发生了车祸,彻底走不动了。内核遇到了无法恢复的致命错误,只能停下来。
常见原因:
- 根文件系统找不到(最常见)
- 内核文件损坏
- 硬件不兼容
- 内核参数配置错误
排查------查看启动日志:
在启动时按 e 键编辑GRUB条目,可以在内核参数行添加调试参数:
# 在linux行末尾添加以下参数:
linux /boot/vmlinuz-xxx root=UUID=xxx ro quiet rd.debug systemd.log_level=debug
# 把quiet去掉,改成verbose,这样可以看到详细的启动信息
修复------根文件系统找不到:
这是最常见的Kernel Panic原因。通常是因为:
- 硬盘UUID变了
- root参数配置错误
- initramfs损坏
bash
# 查看当前硬盘的UUID
blkid
# 输出示例:
# /dev/sda1: UUID="a1b2c3d4-..." TYPE="ext4"
# /dev/sda2: UUID="e5f6g7h8-..." TYPE="swap"
# 在GRUB中查看root参数是否正确
cat /boot/grub/grub.cfg | grep root
# 重建initramfs(在Live CD中操作)
# 挂载系统分区后
chroot /mnt
dracut --force # CentOS/RHEL
update-initramfs -u # Ubuntu/Debian
故障四:systemd阶段故障------"到了公司但打不开门"
现象:内核加载完成,但系统卡在某个服务启动的地方,无法进入登录界面。
排查方法:
bash
# 方法一:在启动时按e编辑GRUB,在linux行末尾加上:
systemd.unit=rescue.target
# 这样会进入救援模式(单用户模式),只启动基本服务
# 方法二:进入系统后查看哪些服务启动失败
systemctl --failed
# 输出示例:
# UNIT LOAD ACTIVE SUB DESCRIPTION
# nginx.service loaded failed failed The nginx HTTP and reverse proxy server
# 查看失败服务的详细日志
systemctl status nginx.service
journalctl -u nginx.service
实际案例:fstab配置错误导致无法启动
这是一个非常经典的故障。假设你修改了 /etc/fstab 文件,加了一条错误的挂载项,重启后系统就起不来了。系统会提示你进入紧急模式(emergency mode)。
bash
# 系统提示进入emergency mode后,输入root密码进入命令行
# 然后编辑fstab文件
vi /etc/fstab
# 找到错误的行,注释掉或修正
# /dev/sdb1 /data ext4 defaults 0 0 <-- 这行有问题
# 改成正确的设备名或注释掉
# /dev/sdb1 /data ext4 defaults 0 0
# 保存后重启
reboot
18.2.3 启动故障排查的通用技巧
技巧一:活用GRUB编辑功能
启动时按 e 可以临时编辑启动项,这在排查启动问题时极其有用。修改后按 Ctrl+X 启动。注意,这种修改是临时的,不会永久保存。
技巧二:查看启动日志
bash
# 查看本次启动的日志
journalctl -b
# 查看上一次启动的日志(上次启动失败时极其有用)
journalctl -b -1
# 查看启动过程中的错误
journalctl -b -p err
# 查看内核启动日志
dmesg | more
技巧三:制作应急启动U盘
永远要准备一个Live USB!这就像家里要备着急救箱一样。当系统完全无法启动时,Live USB是你最后的救命稻草。
bash
# 在Linux中制作Live USB
# 先下载ISO镜像,然后:
sudo dd if=ubuntu-22.04.iso of=/dev/sdb bs=4M status=progress
# 注意:/dev/sdb是你的U盘设备名,千万别写错!
# 写错了会毁掉其他硬盘的数据!
# 在Windows中可以用Rufus工具制作
18.2.4 小结
系统启动故障是最让人紧张的故障之一,因为你连系统都进不去。但只要理解了启动流程,就能一步步定位问题所在:
- BIOS阶段------检查硬件
- GRUB阶段------修复GRUB配置
- 内核阶段------修复内核参数和initramfs
- systemd阶段------修复失败的服务和配置
记住:遇到启动故障不要慌,Live USB + chroot 是你的万能钥匙。
18.3 文件系统损坏修复
18.3.1 什么是文件系统?------用"图书馆"来类比
要理解文件系统损坏,我们先得理解什么是文件系统。
想象一下,你的硬盘是一个巨大的图书馆,里面存放着成千上万本书(文件)。如果这些书乱七八糟地堆在一起,你怎么找到你要的那本?你需要一个"图书管理系统"------记录每本书放在哪个书架、哪一层、哪个位置。
文件系统就是这个"图书管理系统"。它包括:
- 超级块(Superblock):相当于图书馆的总目录,记录了整个文件系统的基本信息
- inode(索引节点):相当于每本书的"图书卡片",记录了文件的属性和数据存放位置
- 数据块(Data Block):相当于书架,实际存放文件数据的地方
- 目录项(Dentry):相当于目录索引,把文件名和inode关联起来
常见的Linux文件系统类型:
| 文件系统 | 特点 | 适用场景 |
|---|---|---|
| ext4 | 最稳定、最成熟 | 通用场景 |
| xfs | 大文件性能好 | 大容量存储 |
| btrfs | 支持快照、压缩 | 高级需求 |
| zfs | 数据完整性极强 | 企业存储 |
18.3.2 文件系统为什么会损坏?
文件系统损坏就像图书馆的目录被弄乱了------书还在,但你找不到它们了。常见原因有:
- 非正常关机:就像你在图书馆正在登记新书时突然停电了,登记到一半的信息就乱了
- 硬盘坏道:书架本身坏了,放上去的书被损坏了
- 内存故障:内存中的数据写错了,导致写入硬盘的数据也是错的
- 软件bug:某个程序错误操作导致文件系统结构被破坏
- 电源故障:和突然断电类似,写入操作被打断
18.3.3 检测文件系统状态
bash
# 查看文件系统类型和挂载状态
df -Th
# 输出示例:
# Filesystem Type Size Used Avail Use% Mounted on
# /dev/sda1 ext4 50G 20G 28G 42% /
# /dev/sda2 ext4 100G 60G 35G 64% /home
# 查看文件系统的详细信息
dumpe2fs /dev/sda1 | head -30 # ext4文件系统
xfs_info /dev/sda1 # xfs文件系统
# 检查文件系统是否有错误(只读检查,不修复)
# 注意:必须在卸载状态下检查!
umount /dev/sda1
e2fsck -n /dev/sda1 # ext4只读检查
# -n 参数表示不修改任何东西,只检查
18.3.4 使用fsck修复文件系统
fsck(File System Consistency Check)是修复文件系统的主力工具,就像图书馆的"整理员",负责把乱了的目录重新整理好。
基本用法:
bash
# 第一步:卸载文件系统(必须!)
umount /dev/sda1
# 第二步:运行fsck检查并修复
fsck /dev/sda1
# fsck会自动识别文件系统类型并选择对应的工具
# 如果是ext4文件系统,可以更精确地使用:
e2fsck /dev/sda1
# 如果是xfs文件系统:
xfs_repair /dev/sda1
fsck的常用参数:
bash
# -y:对所有问题自动回答"yes"(自动修复所有问题)
fsck -y /dev/sda1
# 小白提示:第一次用建议先不加-y,手动确认每个修复操作
# -f:强制检查(即使文件系统标记为clean也检查)
fsck -f /dev/sda1
# -v:显示详细信息
fsck -v /dev/sda1
# -c:检查坏块(会比较慢,但对硬盘健康检查很有用)
fsck -c /dev/sda1
fsck交互过程示例:
# fsck /dev/sda1
fsck from util-linux 2.37.2
e2fsck 1.46.5 (30-Dec-2021)
/dev/sda1 contains a file system with errors, check forced.
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
Block bitmap differences: +12345 -23456
Fix<y>? y <-- 这里问你修不修,输入y
Free blocks count wrong (1234567, counted=1234555).
Fix<y>? y <-- 继续输入y
Free inodes count wrong (500000, counted=499998).
Fix<y>? y
/dev/sda1: ***** FILE SYSTEM WAS MODIFIED *****
18.3.5 修复超级块损坏------"图书馆总目录丢了"
超级块(Superblock)是文件系统的"总目录",如果它损坏了,整个文件系统都无法挂载。但聪明的Linux设计者早就想到了这个问题------他们在文件系统的不同位置保存了超级块的多个备份!这就像图书馆不仅有总目录,还在不同楼层放了备份目录。
bash
# 现象:尝试挂载时报错
mount /dev/sda1 /mnt
# 报错:mount: /mnt: can't read superblock on /dev/sda1.
# 第一步:查看超级块备份的位置
mke2fs -n /dev/sda1
# 注意:-n参数表示不实际格式化,只显示信息!千万别忘了-n!
# 输出示例:
# Superblock backups stored on blocks:
# 32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632
# 第二步:使用备份超级块修复
e2fsck -b 32768 /dev/sda1
# -b后面跟的是备份超级块所在的块号
# 第三步:如果修复成功,用备份超级块挂载
mount -o sb=32768 /dev/sda1 /mnt
# 第四步:恢复主超级块
# 先卸载
umount /dev/sda1
# 再次运行fsck,让它用修复后的信息重建主超级块
e2fsck /dev/sda1
18.3.6 修复只读文件系统
有时候你会发现文件系统突然变成只读了,无法写入任何文件。这是文件系统发现错误后,为了保护数据而自动切换到"只读模式"。就像图书馆发现书籍可能被损坏了,立刻关闭借阅,只允许看不允许借。
bash
# 现象:创建文件时报错
touch test.txt
# 报错:touch: cannot touch 'test.txt': Read-only file system
# 查看文件系统状态
mount | grep " / "
# /dev/sda1 on / type ext4 (ro,relatate) <-- ro表示只读
# 第一步:尝试重新挂载为读写模式
mount -o remount,rw /
# 如果上面失败,说明文件系统确实有错误,需要修复
# 第二步:卸载并修复(如果根分区无法卸载,需要进入救援模式)
umount /dev/sda1
fsck -y /dev/sda1
# 第三步:重新挂载
mount /dev/sda1 /mnt
18.3.7 xfs文件系统的修复
xfs文件系统在CentOS 7+中是默认文件系统,它的修复方式和ext4有所不同:
bash
# xfs修复前,如果可以挂载,先尝试用xfs_repair的只读模式检查
xfs_repair -n /dev/sda1
# -n表示no-op mode,只检查不修复
# 修复xfs文件系统
umount /dev/sda1
xfs_repair /dev/sda1
# 如果xfs_repair报错说log有脏数据,需要先清空日志
xfs_repair -L /dev/sda1
# 注意:-L会清空日志,可能导致最近的数据丢失,谨慎使用!
# 修复后如果xfs元数据损坏严重,可以重建元数据
# 这是最后的手段,会格式化元数据但保留数据
xfs_repair -L /dev/sda1
18.3.8 实际案例:服务器断电后无法启动
故障背景:一台运行MySQL数据库的服务器突然断电,恢复供电后重启,系统无法正常启动,卡在文件系统检查阶段。
排查过程:
bash
# 1. 用Live USB启动,打开终端
# 2. 查看硬盘分区
lsblk
# 输出:
# sda 8:0 0 500G 0 disk
# ├─sda1 8:1 0 1G 0 part /boot
# ├─sda2 8:2 0 50G 0 part /
# └─sda3 8:3 0 449G 0 part /data
# 3. 尝试挂载根分区
mount /dev/sda2 /mnt
# 报错:mount: /mnt: wrong fs type, bad option, bad superblock
# 4. 检查文件系统
fsck /dev/sda2
# 发现超级块损坏
# 5. 查找备份超级块
mke2fs -n /dev/sda2
# 6. 用备份超级块修复
e2fsck -b 32768 /dev/sda2
# 7. 修复成功后挂载
mount /dev/sda2 /mnt
# 8. 同样检查数据分区
fsck /dev/sda3
# 9. chroot进去重装GRUB
mount --bind /dev /mnt/dev
mount --bind /proc /mnt/proc
mount --bind /sys /mnt/sys
chroot /mnt
grub-install /dev/sda
grub2-mkconfig -o /boot/grub2/grub.cfg
# 10. 退出重启
exit
reboot
# 11. 系统启动后,检查MySQL数据完整性
mysqlcheck --all-databases --check
18.3.9 预防文件系统损坏
预防胜于治疗,以下措施可以大幅降低文件系统损坏的概率:
bash
# 1. 确保定期检查文件系统
# 编辑 /etc/fstab,确保最后一列(pass参数)设置正确
# /dev/sda1 / ext4 defaults 1 1 <-- 根分区设为1,优先检查
# /dev/sda2 /home ext4 defaults 0 2 <-- 其他分区设为2
# /dev/sda3 swap swap defaults 0 0 <-- swap分区设为0,不检查
# 2. 定期检查硬盘健康状态
smartctl -t short /dev/sda # 短测试
smartctl -t long /dev/sda # 长测试
smartctl -a /dev/sda # 查看健康状态
# 3. 使用tune2fs设置自动检查周期
tune2fs -c 30 /dev/sda1 # 每30次挂载检查一次
tune2fs -i 180d /dev/sda1 # 每180天检查一次
# 4. 配置UPS不间断电源,避免突然断电
# 硬件层面的预防,这里不展开
# 5. 使用journaling文件系统(ext4/xfs默认已开启日志功能)
dumpe2fs /dev/sda1 | grep 'features'
# 确认输出中包含 has_journal
18.3.10 小结
文件系统损坏是Linux运维中的常见问题,核心知识点:
- 文件系统就像图书馆的管理系统,记录文件的存放位置
- 非正常关机是损坏的主因
- fsck是修复的主力工具,修复前必须卸载分区
- 超级块损坏可以用备份恢复
- xfs文件系统用xfs_repair修复
- 预防为主:定期检查、配置UPS、使用日志文件系统
18.4 网络故障排查
18.4.1 网络排查思路------用"寄快递"来类比
网络通信就像寄快递。你(客户端)要把一个包裹(数据包)寄给远方的朋友(服务器),中间要经过快递公司分拣中心(路由器)、不同城市的转运站(网关)。
如果包裹寄不到,可能是哪个环节出了问题?
- 你自己写错地址了 → IP地址配置错误
- 快递公司不收你的包裹 → 防火墙拦截
- 分拣中心不知道怎么转发 → 路由配置错误
- 收件人地址不存在 → 目标服务器没开或端口没监听
- 收件人不在家 → 服务没运行
- 包裹被弄丢了 → 网络丢包
- 快递太慢了 → 网络延迟高
18.4.2 网络排查的分层方法
和前面讲的一样,网络排查也要分层进行。我们按照TCP/IP模型从下到上排查:
第一层:链路层------网线插好了没?
bash
# 查看网卡状态(Link detected: yes表示网线已连接)
ethtool eth0
# 关键输出:
# Speed: 1000Mb/s <-- 网速
# Link detected: yes <-- 网线是否连接
# 查看所有网络接口
ip link show
# 输出示例:
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 ...
# LOWER_UP表示物理链路已建立
# 如果网卡是down的状态,手动启用
ip link set eth0 up
第二层:网络层------IP配对了没?
bash
# 查看IP地址
ip addr show eth0
# 输出示例:
# 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc ...
# link/ether 00:0c:29:12:34:56 brd ff:ff:ff:ff:ff:ff
# inet 192.168.1.100/24 brd 192.168.1.255 scope global eth0
# 查看路由表(网关配置)
ip route show
# 输出示例:
# default via 192.168.1.1 dev eth0 <-- 默认网关
# 192.168.1.0/24 dev eth0 proto kernel <-- 本网段路由
# 测试网关是否可达
ping -c 4 192.168.1.1
# 如果ping不通网关,可能是:
# 1. IP地址配错了(不在同一个网段)
# 2. 网关设备故障
# 3. 网卡驱动问题
第三层:传输层------端口通不通?
bash
# 测试到目标服务器的TCP端口是否可达
# 使用telnet
telnet 192.168.1.200 80
# Trying 192.168.1.200...
# Connected to 192.168.1.200. <-- 表示端口通
# Escape character is '^]'.
# 使用nc(netcat)更灵活
nc -zv 192.168.1.200 80
# Connection to 192.168.1.200 80 port [tcp/http] succeeded!
# 测试一个范围的端口
nc -zv 192.168.1.200 20-30
# 查看本机监听的端口
ss -tlnp
# 输出示例:
# State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
# LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
# LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=5678,fd=6))
第四层:应用层------DNS解析对了没?
bash
# 测试DNS解析
nslookup www.example.com
# 输出示例:
# Server: 8.8.8.8
# Address: 8.8.8.8#53
# Non-authoritative answer:
# Name: www.example.com
# Address: 93.184.216.34
# 使用dig获取更详细的信息
dig www.example.com
# 关注 ANSWER SECTION 部分
# 如果DNS解析失败,尝试使用指定的DNS服务器
dig @8.8.8.8 www.example.com
# 查看当前配置的DNS服务器
cat /etc/resolv.conf
# nameserver 8.8.8.8
# nameserver 114.114.114.114
18.4.3 网络排查常用工具详解
ping------最基础的网络测试工具
ping就像你喊一声"有人吗?"然后等待回应。它使用ICMP协议,不涉及端口。
bash
# 基本用法:发送4个包
ping -c 4 8.8.8.8
# 输出:
# PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
# 64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=12.3 ms
# 64 bytes from 8.8.8.8: icmp_seq=2 ttl=117 time=12.1 ms
# 64 bytes from 8.8.8.8: icmp_seq=3 ttl=117 time=12.5 ms
# 64 bytes from 8.8.8.8: icmp_seq=4 ttl=117 time=12.2 ms
#
# --- 8.8.8.8 ping statistics ---
# 4 packets transmitted, 4 received, 0% packet loss <-- 0%丢包,网络好
# rtt min/avg/max/mdev = 12.123/12.285/12.521/0.156 ms
# 持续ping,直到手动停止(适合监控网络稳定性)
ping 8.8.8.8
# 按Ctrl+C停止
# 指定包的大小(测试大包是否被分片)
ping -c 4 -s 1400 8.8.8.8
# 快速ping(间隔100ms,适合大范围扫描)
ping -c 4 -i 0.1 8.8.8.8
traceroute/tracepath------追踪数据包路径
traceroute就像快递追踪------你寄出的包裹经过了哪些中转站,每个中转站花了多长时间。
bash
# 追踪到目标的路径
traceroute www.baidu.com
# 输出示例:
# traceroute to www.baidu.com (110.242.68.66), 30 hops max, 60 byte packets
# 1 192.168.1.1 (192.168.1.1) 0.526 ms <-- 第一跳:网关
# 2 10.0.0.1 (10.0.0.1) 5.231 ms <-- 第二跳:ISP路由器
# 3 202.97.12.45 (202.97.12.45) 12.451 ms <-- 第三跳:骨干网
# 4 110.242.68.66 (110.242.68.66) 15.621 ms <-- 目标
# 如果某一跳显示 * * *,说明那一跳不响应或被防火墙拦截
# 如果在某一跳之后全部是 * * *,说明故障就在那一跳
# tracepath不需要root权限,更方便
tracepath www.baidu.com
# mtr结合了ping和traceroute的优点,实时显示每一跳的丢包率
mtr www.baidu.com
# 这个工具在排查网络质量问题时非常有用
tcpdump------网络抓包利器
tcpdump就像在网络线路上装了一个"窃听器",可以看到所有经过的数据包。这是网络排障的终极武器。
bash
# 基本用法:抓取指定网卡的所有流量
tcpdump -i eth0
# 抓取指定主机的流量
tcpdump -i eth0 host 192.168.1.100
# 抓取指定端口的流量
tcpdump -i eth0 port 80
# 抓取指定主机和端口的流量
tcpdump -i eth0 host 192.168.1.100 and port 80
# 抓取TCP SYN包(连接建立请求)
tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0'
# 保存抓包结果到文件(用Wireshark分析)
tcpdump -i eth0 -w /tmp/capture.pcap port 80
# 读取抓包文件
tcpdump -r /tmp/capture.pcap
# 显示详细的内容(包括数据内容)
tcpdump -i eth0 -X port 80
# 只抓前10个包
tcpdump -i eth0 -c 10 port 80
ss------比netstat更强大的网络状态查看工具
bash
# 查看所有TCP连接
ss -t -a
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# LISTEN 0 128 0.0.0.0:ssh 0.0.0.0:*
# ESTAB 0 0 192.168.1.100:ssh 10.0.0.50:54321
# 查看监听的端口和对应进程
ss -tlnp
# 查看所有UDP连接
ss -u -a
# 查看已建立的TCP连接
ss -t state established
# 统计各状态的连接数(在排查连接数过多时有用)
ss -s
# Total: 150
# TCP: 30 (estab 5, closed 10, orphaned 0, timewait 10)
# 查看连接指定端口的客户端
ss -tn state established '( dport = :80 or sport = :80 )'
18.4.4 常见网络故障案例
案例一:服务器能ping通但无法访问Web服务
排查思路:能ping通说明网络层没问题,问题在传输层或应用层。
bash
# 1. 检查服务是否在运行
systemctl status nginx
# 如果没运行,启动它
systemctl start nginx
# 2. 检查端口是否在监听
ss -tlnp | grep :80
# 如果没有输出,说明nginx虽然启动了但没有监听80端口
# 可能是配置文件有问题
# 3. 检查防火墙是否放行了80端口
# iptables防火墙
iptables -L -n | grep 80
# 如果没有放行
iptables -I INPUT -p tcp --dport 80 -j ACCEPT
# firewalld防火墙(CentOS 7+)
firewall-cmd --list-ports
firewall-cmd --add-port=80/tcp --permanent
firewall-cmd --reload
# ufw防火墙(Ubuntu)
ufw status
ufw allow 80/tcp
# 4. 检查SELinux是否阻止了(CentOS)
getenforce
# 如果返回Enforcing,临时关闭测试
setenforce 0
# 如果关闭后可以访问,说明是SELinux的问题
# 永久解决方案:设置正确的SELinux上下文
semanage port -a -t http_port_t -p tcp 80
案例二:DNS解析失败
现象:ping IP地址可以,但ping域名不行。
bash
# 1. 测试DNS解析
nslookup www.baidu.com
# 如果报错:server can't find www.baidu.com: NXDOMAIN
# 2. 检查DNS配置
cat /etc/resolv.conf
# 如果为空或DNS服务器不对,添加正确的DNS
echo "nameserver 8.8.8.8" > /etc/resolv.conf
echo "nameserver 114.114.114.114" >> /etc/resolv.conf
# 3. 检查/etc/hosts文件是否覆盖了DNS
cat /etc/hosts
# 如果有错误的条目,删除或修正
# 4. 检查/etc/nsswitch.conf确保DNS查询顺序正确
cat /etc/nsswitch.conf | grep hosts
# 应该是:hosts: files dns (先查hosts文件,再查DNS)
# 5. 清除DNS缓存(有些系统有nscd缓存)
systemctl restart nscd # 如果安装了nscd
案例三:网络连接时断时续
现象:网络有时通有时不通,很不稳定。
bash
# 1. 使用mtr持续监控网络质量
mtr -r -c 100 8.8.8.8
# -r表示报告模式,-c 100表示发100个包
# 观察哪一跳丢包率高
# 2. 检查网卡是否有错误
ip -s link show eth0
# 关注RX errors(接收错误)和TX errors(发送错误)
# 3. 检查网卡速率和双工模式是否匹配
ethtool eth0
# Speed: 1000Mb/s
# Duplex: Full
# 如果和交换机不匹配,可能导致丢包
# 手动设置:
ethtool -s eth0 speed 1000 duplex full autoneg off
# 4. 检查网络内核参数
sysctl net.core.netdev_max_backlog
# 如果队列太小,网络繁忙时可能丢包
sysctl -w net.core.netdev_max_backlog=5000
# 5. 检查ARP表是否有冲突
arp -n
# 如果同一个IP对应两个不同的MAC地址,说明有IP冲突
18.4.5 网络配置文件
了解配置文件位置,才能在命令行修复时知道改哪里:
bash
# ===== CentOS/RHEL =====
# 网卡配置文件
cat /etc/sysconfig/network-scripts/ifcfg-eth0
# TYPE=Ethernet
# BOOTPROTO=static
# IPADDR=192.168.1.100
# NETMASK=255.255.255.0
# GATEWAY=192.168.1.1
# DNS1=8.8.8.8
# ONBOOT=yes
# 重启网络服务
systemctl restart network # CentOS 6/7
nmcli connection reload # CentOS 8+
# ===== Ubuntu/Debian =====
# 网卡配置文件(旧版)
cat /etc/network/interfaces
# 新版使用Netplan
cat /etc/netplan/01-netcfg.yaml
# network:
# version: 2
# ethernets:
# eth0:
# addresses: [192.168.1.100/24]
# gateway4: 192.168.1.1
# nameservers:
# addresses: [8.8.8.8, 8.8.4.4]
# 应用Netplan配置
netplan apply
18.4.6 小结
网络故障排查是运维工作中最常见的任务。核心要点:
- 分层排查:链路层→网络层→传输层→应用层
- ping测试连通性,traceroute/mtr追踪路径
- ss查看连接状态,tcpdump抓包分析
- 别忘了检查防火墙和SELinux
- DNS问题要先排查
/etc/resolv.conf和/etc/hosts
18.5 性能问题诊断
18.5.1 性能问题概述------用"交通堵塞"来类比
系统性能问题就像城市交通堵塞。一条路上有车(进程)、有红绿灯(CPU调度)、有停车场(内存)、有高速公路(磁盘I/O)。任何一个环节出问题都可能导致"堵车"(系统变慢)。
常见的性能瓶颈有四种:
- CPU瓶颈:路口红绿灯处理不过来,车排长队
- 内存瓶颈:停车场满了,车无处停只能等
- 磁盘I/O瓶颈:高速公路出入口太窄,车进出慢
- 网络瓶颈:道路太窄,车流量大时通行缓慢
18.5.2 CPU性能分析
top命令------系统监控的"瑞士军刀"
top是最常用的系统监控工具,就像汽车的仪表盘,一眼就能看到系统的整体状态。
bash
top
# 输出解读:
# top - 10:30:01 up 30 days, 5:20, 3 users, load average: 1.50, 1.20, 0.80
# Tasks: 150 total, 1 running, 149 sleeping, 0 stopped, 0 zombie
# %Cpu(s): 25.0 us, 10.0 sy, 0.0 ni, 63.0 id, 2.0 wa, 0.0 hi, 0.0 si, 0.0 st
# MiB Mem : 8000.0 total, 2000.0 free, 4000.0 used, 2000.0 buff/cache
# MiB Swap: 2000.0 total, 2000.0 free, 0.0 used. 3000.0 avail Mem
#
# PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
# 1234 mysql 20 0 2000000 500000 20000 S 85.0 6.2 1200:00 mysqld
# 5678 nginx 20 0 150000 20000 5000 S 15.0 0.2 50:00 nginx
关键指标解读:
| 指标 | 含义 | 类比 |
|---|---|---|
| load average | 系统负载(1/5/15分钟平均值) | 等待结账的顾客数量 |
| us | 用户态CPU使用率 | 顾客在买东西 |
| sy | 内核态CPU使用率 | 收银员在处理系统操作 |
| id | 空闲CPU百分比 | 收银员闲着 |
| wa | I/O等待百分比 | 收银员在等仓库取货 |
| VIRT | 虚拟内存大小 | 商店的总面积 |
| RES | 物理内存使用量 | 实际使用的面积 |
| SHR | 共享内存 | 公共区域面积 |
bash
# top的交互命令(在top界面中按键):
# P - 按CPU使用率排序
# M - 按内存使用率排序
# T - 按运行时间排序
# 1 - 显示所有CPU核心的使用情况
# H - 显示线程
# c - 显示完整命令行
# k - 杀死指定进程
# q - 退出
# load average解读:
# 如果load average = 1.0,表示1个CPU核心刚好满负荷
# 如果是4核CPU,load average = 4.0表示刚好满负荷
# load average持续高于CPU核心数,说明系统过载
# 比较三个数值(1分钟、5分钟、15分钟):
# 1.5 1.2 0.8 → 负载在上升,系统越来越忙
# 0.8 1.2 1.5 → 负载在下降,系统正在恢复
vmstat------系统虚拟内存统计
vmstat就像系统的"体检报告",可以全面了解CPU、内存、I/O的状态。
bash
# 每秒输出一次,共输出5次
vmstat 1 5
# 输出:
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
# r b swpd free buff cache si so bi bo in cs us sy id wa st
# 2 0 0 200000 500000 1500000 0 0 10 20 500 800 25 10 63 2 0
# 3 0 0 190000 500000 1510000 0 0 50 30 600 900 30 15 53 2 0
# 关键列解读:
# r - 运行队列中的进程数(持续大于CPU核心数说明CPU不够用)
# b - 等待I/O的进程数(持续大于0说明I/O有瓶颈)
# si - 每秒从swap读入内存的数据量(持续大于0说明内存不足)
# so - 每秒从内存写入swap的数据量(同上)
# bi - 每秒从块设备读取的块数(磁盘读)
# bo - 每秒向块设备写入的块数(磁盘写)
# us - 用户态CPU时间
# sy - 内核态CPU时间
# id - 空闲CPU时间
# wa - I/O等待时间(持续大于20%说明磁盘I/O是瓶颈)
找出CPU占用最高的进程
bash
# 方法一:ps命令
ps aux --sort=-%cpu | head -10
# 按CPU使用率从高到低排序,显示前10个
# 方法二:top一次性输出
top -b -n 1 -o %CPU | head -20
# 方法三:查看指定进程的CPU使用情况
pidstat 1 5
# 每秒输出一次,共5次,显示所有进程的CPU使用率
# 方法四:查看线程级别的CPU使用
top -H -p 1234
# 查看PID为1234的进程内各线程的CPU使用情况
# 这在排查多线程程序性能问题时非常有用
18.5.3 内存性能分析
bash
# free命令------查看内存使用概况
free -h
# total used free shared buff/cache available
# Mem: 7.8Gi 3.9Gi 1.2Gi 200Mi 2.7Gi 3.4Gi
# Swap: 2.0Gi 0B 2.0Gi
# 重要理解:
# free那一列不等于"可用内存"!
# available才是真正的可用内存
# buff/cache是系统用作缓存的内存,需要时会自动释放
# 所以判断内存是否不足,看available列
# /proc/meminfo------查看详细的内存信息
cat /proc/meminfo
# MemTotal: 8192000 kB
# MemFree: 1200000 kB
# MemAvailable: 3400000 kB <-- 这个最重要
# Buffers: 500000 kB
# Cached: 2000000 kB
# SwapTotal: 2000000 kB
# SwapFree: 2000000 kB
# 查看内存占用最高的进程
ps aux --sort=-%mem | head -10
# 查看指定进程的详细内存映射
pmap -x 1234 | tail -5
# 显示进程的内存映射,最后一行是总计
18.5.4 磁盘I/O性能分析
磁盘I/O往往是系统最大的瓶颈,因为磁盘比CPU和内存慢几个数量级。
bash
# iostat------查看磁盘I/O统计
iostat -x 1 5
# 每秒输出一次,共5次,显示扩展信息
# Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %util await
# sda 50.00 20.00 800.00 200.00 5.00 2.00 85.00 15.00
#
# 关键指标:
# r/s、w/s - 每秒读/写次数
# %util - 磁盘使用率(接近100%说明磁盘满负荷)
# await - I/O请求平均等待时间(毫秒,超过20ms需关注)
# iotop------查看哪个进程在读写磁盘(需要root)
iotop -o
# -o只显示有I/O操作的进程
# 类似top,但按I/O排序
# 如果没有iotop,可以用以下方法替代:
# 方法一:通过/proc查看
for pid in $(ls /proc | grep -E '^[0-9]+$'); do
io=$(cat /proc/$pid/io 2>/dev/null | grep read_bytes | awk '{print $2}')
if [ "$io" -gt 0 ] 2>/dev/null; then
name=$(cat /proc/$pid/comm 2>/dev/null)
echo "$pid $name $io"
fi
done | sort -k3 -n -r | head -10
# 方法二:使用pidstat
pidstat -d 1 5
# 每秒输出一次进程的I/O统计
18.5.5 综合性能分析------使用sar
sar(System Activity Reporter)是系统活动的"历史记录仪",它可以记录和回放系统的历史性能数据。
bash
# 安装sar(sysstat包)
yum install sysstat # CentOS
apt install sysstat # Ubuntu
# 查看今天的CPU使用历史
sar -u
# 输出每小时一条记录的CPU使用情况
# 查看指定日期的CPU使用历史
sar -u -f /var/log/sa/sa15
# 查看每月15号的CPU数据
# 查看内存使用历史
sar -r
# 查看磁盘I/O历史
sar -d
# 查看网络流量历史
sar -n DEV
# 实时监控(每秒一次)
sar -u 1 5
18.5.6 实际案例:网站响应缓慢
故障背景:用户反映网站访问很慢,页面加载需要10秒以上。
排查过程:
bash
# 第一步:查看系统整体负载
uptime
# load average: 8.50, 8.20, 8.00 <-- 负载很高!
# 第二步:查看CPU使用情况
top -b -n 1 | head -20
# 发现mysqld的CPU使用率高达200%
# 第三步:查看内存
free -h
# available只有200MB,内存不足
# 第四步:查看磁盘I/O
iostat -x 1 3
# %util达到95%,await达到50ms,磁盘I/O是瓶颈
# 第五步:查看swap使用情况
vmstat 1 3
# si和so都不为0,说明在频繁使用swap
# 这就是系统变慢的根本原因:内存不足→使用swap→磁盘I/O飙高→系统变慢
# 解决方案:
# 1. 查看哪个进程占用内存最多
ps aux --sort=-%mem | head -5
# 发现MySQL占用了5GB内存
# 2. 检查MySQL配置
# 可能是innodb_buffer_pool_size设置过大
# 调整MySQL配置,降低内存使用
# 3. 临时增加swap空间
dd if=/dev/zero of=/swapfile bs=1M count=4096
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
# 4. 长期方案:增加物理内存
18.5.7 小结
性能问题诊断的核心要点:
- 四大瓶颈:CPU、内存、磁盘I/O、网络
- top看整体,vmstat看细节,iostat看磁盘
- sar看历史数据
- load average持续高于CPU核心数说明过载
- 频繁使用swap说明内存不足
- 磁盘%util接近100%说明I/O瓶颈
- 性能问题往往相互关联(内存不足→swap→磁盘I/O飙高→系统变慢)
18.6 内存泄漏排查
18.6.1 什么是内存泄漏?------用"漏水的水桶"来类比
想象你有一个水桶(内存),你往里面倒水(分配内存),用完水后应该把水倒掉(释放内存)。但如果你用完水后忘了倒掉,水桶里的水就会越来越多,最终溢出来------这就是内存泄漏。
在程序中,内存泄漏指的是程序分配了内存但使用完后没有释放,导致可用内存越来越少,最终系统不得不使用swap甚至触发OOM Killer(内存杀手)杀掉进程。
内存泄漏的典型表现:
- 进程的内存使用量持续增长,不下降
- 系统可用内存越来越少
- 系统开始频繁使用swap
- 最终进程被OOM Killer杀掉,或者系统变慢
18.6.2 检测内存泄漏
bash
# 方法一:定期监控进程内存使用
# 使用ps记录某进程的内存使用
while true; do
ps -p 1234 -o pid,rss,vsz,cmd --no-headers
sleep 60
done >> /tmp/mem_monitor.log
# rss是实际使用的物理内存(KB)
# vsz是虚拟内存大小(KB)
# 如果rss持续增长不下降,很可能是内存泄漏
# 方法二:使用pidstat监控
pidstat -r -p 1234 60
# 每60秒输出一次PID 1234的内存使用情况
# 关注RSS列是否持续增长
# 方法三:查看/proc信息
cat /proc/1234/status | grep -i vm
# VmRSS: 500000 kB <-- 实际物理内存
# VmSize: 2000000 kB <-- 虚拟内存
# VmPeak: 2500000 kB <-- 峰值虚拟内存
# VmHWM: 600000 kB <-- 峰值物理内存
# 方法四:使用smem工具查看更准确的内存使用
smem -p -k | grep process_name
# smem使用PSS(Proportional Set Size)来计算内存
# 比RSS更准确地反映进程实际使用的内存
18.6.3 系统级内存监控脚本
bash
#!/bin/bash
# 内存监控脚本,保存为 /tmp/mem_monitor.sh
# 当可用内存低于阈值时报警
THRESHOLD=500000 # 500MB,单位KB
LOGFILE=/var/log/memory_monitor.log
while true; do
# 获取可用内存(KB)
MEM_AVAILABLE=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
if [ "$MEM_AVAILABLE" -lt "$THRESHOLD" ]; then
echo "[$TIMESTAMP] WARNING: Low memory! Available: ${MEM_AVAILABLE}KB" >> $LOGFILE
echo "[$TIMESTAMP] Top 5 memory consumers:" >> $LOGFILE
ps aux --sort=-%mem | head -6 >> $LOGFILE
echo "----------------------------------------" >> $LOGFILE
fi
sleep 60
done
18.6.4 OOM Killer详解
当系统内存严重不足时,Linux的OOM Killer(Out Of Memory Killer)会自动杀掉一些进程来释放内存。这就像救生艇上人太多了要扔掉一些行李来减轻重量。
bash
# 查看OOM Killer的日志
dmesg | grep -i "out of memory"
dmesg | grep -i "oom-killer"
journalctl -k | grep -i oom
# OOM Killer日志示例:
# [12345.678] Out of memory: Kill process 5678 (java) score 850 or sacrifice child
# [12345.678] Killed process 5678 (java) total-vm:5000000kB, anon-rss:4000000kB
# OOM Killer选择杀谁的标准是"oom_score"
# 查看进程的oom_score
cat /proc/1234/oom_score
# 数值越高,越可能被杀
# 查看oom_score_adj(可以调整)
cat /proc/1234/oom_score_adj
# 范围-1000到1000
# -1000表示禁止被OOM Killer杀掉
# 1000表示优先被杀掉
# 保护重要进程不被OOM Killer杀掉
echo -1000 > /proc/1234/oom_score_adj
# 或者在/etc/sysctl.conf中配置
# vm.overcommit_memory = 1 # 允许过度分配
# vm.panic_on_oom = 1 # OOM时内核panic而不是杀进程(不推荐生产环境用)
18.6.5 使用valgrind排查C/C++程序的内存泄漏
如果是C/C++程序,valgrind是排查内存泄漏的神器。它就像一个"内存审计员",记录每一块内存的分配和释放。
bash
# 安装valgrind
yum install valgrind # CentOS
apt install valgrind # Ubuntu
# 使用valgrind检查程序的内存泄漏
valgrind --leak-check=full --show-leak-kinds=all ./your_program
# 输出示例:
# ==12345== HEAP SUMMARY:
# ==12345== in use at exit: 500 bytes in 5 blocks
# ==12345== total heap usage: 100 allocs, 95 frees, 10,000 bytes allocated
# ==12345==
# ==12345== 500 bytes in 5 blocks are definitely lost in loss record 1 of 1
# ==12345== at 0x4C29F73: malloc (vg_replace_malloc.c:309)
# ==12345== by 0x1086A8: create_buffer (main.c:42)
# ==12345== by 0x1086C4: main (main.c:15)
# ==12345==
# ==12345== LEAK SUMMARY:
# ==12345== definitely lost: 500 bytes in 5 blocks
# 上面的输出告诉我们:在main.c第42行的create_buffer函数中分配了内存但没释放
# 注意:valgrind会使程序运行变慢20-30倍,不要在生产环境使用
18.6.6 Java程序内存泄漏排查
Java程序是内存泄漏的重灾区。JVM自带的工具可以帮助排查:
bash
# 查看Java进程
jps -l
# 12345 com.example.MyApp
# 查看Java堆内存使用情况
jmap -heap 12345
# 输出堆的配置和使用详情
# 查看内存中各类对象的数量和大小
jmap -histo 12345 | head -20
# num #instances #bytes class name
# 1: 1000000 100000000 java.lang.String
# 2: 500000 40000000 java.util.HashMap$Node
# 导出堆dump文件用于离线分析
jmap -dump:format=b,file=/tmp/heapdump.hprof 12345
# 然后用MAT (Memory Analyzer Tool) 或 VisualVM 分析
# 使用jstat监控GC情况
jstat -gcutil 12345 1000 10
# 每秒输出一次,共10次
# S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
# 0.00 45.00 60.00 85.00 90.00 85.00 20 0.500 3 1.200 1.700
# 如果O(Old区)持续增长不下降,且FGC(Full GC)频繁,说明内存泄漏
18.6.7 实际案例:Python程序内存泄漏
故障背景:一个Python数据处理脚本运行一段时间后被OOM Killer杀掉。
排查过程:
bash
# 1. 确认是内存泄漏
ps aux | grep python
# 发现RSS持续增长
# 2. 使用tracemalloc(Python内置)追踪内存分配
# 在Python代码中加入:
# import tracemalloc
# tracemalloc.start()
# # ... 你的代码 ...
# snapshot = tracemalloc.take_snapshot()
# top_stats = snapshot.statistics('lineno')
# for stat in top_stats[:10]:
# print(stat)
# 3. 使用memory_profiler
pip install memory_profiler
# 在代码中加入:
# from memory_profiler import profile
# @profile
# def my_func():
# ...
# 4. 使用objgraph查看对象引用
pip install objgraph
# 在代码中:
# import objgraph
# objgraph.show_most_common_types()
# 常见Python内存泄漏原因:
# 1. 全局列表/字典不断追加但不清理
# 2. 闭包引用了不需要的变量
# 3. 缓存没有大小限制
# 4. 数据库连接/文件句柄未关闭
# 5. 循环引用(虽然有GC,但有些情况会漏掉)
18.6.8 小结
内存泄漏排查的核心要点:
- 内存泄漏就像水桶漏水,内存只增不减
- 用ps/pidstat/proc监控内存增长趋势
- OOM Killer会在内存耗尽时杀进程,可以调整oom_score保护重要进程
- C/C++用valgrind排查,Java用jmap/jstat,Python用tracemalloc/memory_profiler
- 生产环境要注意监控,在OOM之前发现问题
18.7 磁盘空间不足处理
18.7.1 磁盘空间不足------用"衣柜塞满了"来类比
磁盘空间不足就像你的衣柜塞满了衣服,再也没法放新衣服了。这时候你有两个选择:一是清理掉不需要的旧衣服(删除文件),二是换个更大的衣柜(扩容磁盘)。
但有时候你会发现一个诡异的现象:明明删了很多文件,但磁盘空间还是满的!这就像你把旧衣服扔了,但它们还堆在衣柜里没被收走。我们后面会解释原因。
18.7.2 查看磁盘空间使用情况
bash
# df命令------查看文件系统整体使用情况
df -h
# Filesystem Size Used Avail Use% Mounted on
# /dev/sda1 50G 45G 2G 96% / <-- 96%!快满了!
# /dev/sda2 100G 60G 35G 60% /home
# tmpfs 2.0G 0 2.0G 0% /tmp
# -h: 人类可读格式(GB、MB)
# -i: 查看inode使用情况(有时候inode满了但空间没满)
df -i
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/sda1 500000 490000 10000 98% / <-- inode满了!
# du命令------查看目录大小
du -sh /var/log
# 5.0G /var/log
# 查看当前目录下各子目录的大小,按大小排序
du -sh /* 2>/dev/null | sort -rh | head -10
# 30G /var
# 20G /home
# 10G /usr
# 5G /tmp
# 查找大于1G的文件
find / -type f -size +1G 2>/dev/null
# /var/log/mysql/slow.log 2.5G
# /home/user/backup.tar.gz 1.8G
# /tmp/big_temp_file 1.2G
# 查找最近7天修改的大文件
find / -type f -size +500M -mtime -7 2>/dev/null
18.7.3 清理磁盘空间------按"从安全到激进"的顺序
第一步:清理日志文件(最安全)
bash
# 查看日志目录大小
du -sh /var/log
# 如果日志很大,可以清理
# 方法一:使用journalctl清理systemd日志
journalctl --disk-usage
# 查看日志占用空间
# 只保留最近7天的日志
journalctl --vacuum-time=7d
# 只保留500MB的日志
journalctl --vacuum-size=500M
# 方法二:清空日志文件内容(保留文件但清空内容)
# 注意:不要直接删除日志文件!有些服务会一直写同一个文件
# 正确做法是清空内容:
> /var/log/messages # 清空文件内容
cat /dev/null > /var/log/messages # 另一种写法
# 方法三:使用logrotate自动管理日志
# logrotate会自动压缩、轮转、删除旧日志
cat /etc/logrotate.conf
cat /etc/logrotate.d/syslog
# 手动执行logrotate
logrotate -f /etc/logrotate.d/syslog
第二步:清理软件包缓存
bash
# CentOS/RHEL
yum clean all # 清理所有缓存
package-cleanup --oldkernels --count=2 # 只保留2个旧内核
# Ubuntu/Debian
apt clean # 清理下载的deb包
apt autoremove # 删除不需要的依赖包
# 查看旧内核占用的空间
rpm -qa kernel | sort -V
# kernel-3.10.0-1160.el7.x86_64
# kernel-3.10.0-1160.45.1.el7.x86_64
# kernel-3.10.0-1160.66.1.el7.x86_64
# 删除旧内核(保留最新的)
yum remove kernel-3.10.0-1160.el7
第三步:清理临时文件
bash
# 清理/tmp目录
# 注意:不要直接删除/tmp下的文件,有些程序正在使用
# 安全做法:只删除7天前的临时文件
find /tmp -type f -atime +7 -delete
# 清理用户缓存
rm -rf ~/.cache/*
# 清理系统缓存
# 注意:buff/cache不需要手动清理,系统会自动管理
# 但如果确实需要强制清理:
sync && echo 3 > /proc/sys/vm/drop_caches
# 1: 清空pagecache
# 2: 清空dentries和inodes
# 3: 清空所有缓存(包括1和2)
第四步:查找并删除大文件
bash
# 查找前20个最大的文件
find / -type f -exec du -h {} + 2>/dev/null | sort -rh | head -20
# 查找特定目录下的大文件
find /var -type f -size +100M -exec ls -lh {} \;
# 查找大文件并按大小排序
du -ah / | sort -rh | head -20
18.7.4 "删了文件但空间没释放"的谜题
这是一个极其经典的故障场景,几乎每个运维人员都遇到过。
现象 :你用 rm 删了一个大文件,df 显示磁盘还是满的,但 ls 已经看不到那个文件了。
原因:有进程正在使用这个文件。在Linux中,如果一个文件被进程打开,即使你删除了文件名,文件的实际数据块不会被释放,直到所有打开该文件的进程都关闭它。这就像你把一份文件从文件柜中取出来(删除文件名),但有人还在看这份文件(进程还在使用),你没法把它扔掉(释放空间)。
排查与解决:
bash
# 第一步:找出哪些被删除的文件还被进程占用
lsof | grep deleted
# COMMAND PID USER FD TYPE SIZE/OFF NODE NAME
# mysqld 1234 mysql 5w REG 8,1 5000000000 12345 /var/log/mysql/slow.log (deleted)
# nginx 5678 nginx 3w REG 8,1 2000000000 67890 /var/log/nginx/access.log (deleted)
# 上面的输出说明:
# mysqld进程(PID 1234)还在写一个已删除的5GB日志文件
# nginx进程(PID 5678)还在写一个已删除的2GB日志文件
# 第二步:解决方法
# 方法一:重启相关服务(最简单)
systemctl restart mysql
systemctl restart nginx
# 方法二:不重启服务,通过/proc恢复
# 找到进程的文件描述符
ls -la /proc/1234/fd/ | grep deleted
# lrwx------ 1 mysql mysql 64 ... 5w -> /var/log/mysql/slow.log (deleted)
# 清空文件内容(不需要关闭文件描述符)
cat /dev/null > /proc/1234/fd/5
# 这样文件内容被清空,空间被释放,但进程还能继续写
# 方法三: truncate命令
truncate -s 0 /proc/1234/fd/5
18.7.5 inode耗尽问题
有时候 df -h 显示磁盘空间还很多,但就是无法创建新文件,报错 No space left on device。这很可能是inode耗尽了。
inode就像衣柜里的衣架------衣服(数据)不多,但衣架(inode)用完了,也没法挂新衣服了。
bash
# 查看inode使用情况
df -i
# Filesystem Inodes IUsed IFree IUse% Mounted on
# /dev/sda1 500000 499990 10 100% / <-- inode满了!
# 找出哪个目录下的文件数量最多
for d in /*; do
echo $(find $d -type f 2>/dev/null | wc -l) $d
done | sort -rn | head -10
# 通常/var/spool或/tmp下会有大量小文件
# 常见原因:
# 1. 邮件队列堆积(/var/spool/postfix/maildrop/)
# 2. session文件堆积(/tmp/或/var/lib/php/session/)
# 3. 定时任务产生的日志碎片
# 清理大量小文件
# 警告:如果文件数量非常多(百万级),rm可能很慢甚至失败
# 使用rsync删除大量文件更快:
mkdir /tmp/empty_dir
rsync --delete /tmp/empty_dir/ /path/to/dir/with/many/files/
# 或者使用find批量删除
find /path/to/dir -type f -delete
18.7.6 磁盘扩容
当清理空间不够用时,就需要扩容磁盘了。
bash
# ===== LVM扩容(推荐方式) =====
# LVM(Logical Volume Manager)允许动态调整分区大小
# 就像衣柜可以随时加隔板扩大空间
# 第一步:查看当前LVM状态
lvdisplay
# LV Path: /dev/centos/root
# LV Size: 50.00 GiB
# 第二步:查看是否有空闲空间
vgdisplay
# Free PE / Size: 0 / 0 <-- 没有空闲空间,需要先扩展物理卷
# 第三步:添加新硬盘后,创建物理卷
pvcreate /dev/sdb
# 第四步:扩展卷组
vgextend centos /dev/sdb
# 第五步:扩展逻辑卷(使用所有空闲空间)
lvextend -l +100%FREE /dev/centos/root
# 或者指定大小
lvextend -L +20G /dev/centos/root
# 第六步:扩展文件系统
# ext4文件系统:
resize2fs /dev/centos/root
# xfs文件系统:
xfs_growfs /
# 验证
df -h
18.7.7 磁盘空间监控脚本
bash
#!/bin/bash
# 磁盘空间监控脚本
# 保存为 /usr/local/bin/disk_monitor.sh
THRESHOLD=80 # 使用率阈值(%)
EMAIL="admin@example.com"
df -h | awk 'NR>1' | while read line; do
# 提取使用率(去掉%号)
usage=$(echo $line | awk '{print $5}' | sed 's/%//')
partition=$(echo $line | awk '{print $6}')
if [ "$usage" -gt "$THRESHOLD" ]; then
echo "警告:分区 $partition 使用率已达 ${usage}%!"
echo "详细信息:$line"
# 发送邮件通知
# echo "磁盘空间告警" | mail -s "磁盘告警" $EMAIL
fi
done
18.7.8 小结
磁盘空间不足处理的核心要点:
- df查看整体空间,du查看目录大小,find查找大文件
- 清理顺序:日志→缓存→临时文件→大文件
- "删了文件但空间没释放"------用lsof找被占用的已删除文件
- inode耗尽------df -i查看,清理大量小文件
- 空间不够时用LVM扩容
- 设置监控脚本,提前预警
18.8 进程僵死与处理
18.8.1 进程状态详解------用"办公室工作人员"来类比
Linux中的进程有多种状态,就像办公室里的工作人员有不同的工作状态:
| 状态 | 代码 | 类比 |
|---|---|---|
| 运行 | R (Running) | 正在工作 |
| 可中断睡眠 | S (Sleeping) | 在等快递,快递来了就继续工作 |
| 不可中断睡眠 | D (Disk wait) | 在等电梯,不能打断 |
| 停止 | T (Stopped) | 暂停工作(比如按了Ctrl+Z) |
| 僵尸 | Z (Zombie) | 人已经走了但档案还在 |
bash
# 查看进程状态
ps aux
# STAT列就是进程状态
# R - 运行
# S - 可中断睡眠
# D - 不可中断睡眠
# T - 停止
# Z - 僵尸
# < - 高优先级
# N - 低优先级
# s - 会话首进程
# l - 多线程
# + - 前台进程组
# 查看所有进程的详细状态
ps -eo pid,ppid,stat,cmd | more
18.8.2 僵尸进程------"走了但没被销户"
僵尸进程(Zombie Process)是最让人困惑的进程状态。要理解它,需要先理解父子进程的关系。
在Linux中,每个进程都有一个父进程。当子进程完成任务退出时,它会变成"僵尸"------进程已经死了,但它的"退出信息"还保留着,等待父进程来读取。如果父进程没有来读取这个退出信息,子进程就会一直保持僵尸状态。
这就像员工离职了,但HR没有办理离职手续(销户),这个员工的档案就一直挂在公司里。虽然员工不会再消耗工资(CPU、内存),但档案占着名额(进程号PID)。
bash
# 查找僵尸进程
ps aux | grep 'Z'
# 或者更精确地查找
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/'
# 输出示例:
# PID PPID STAT CMD
# 12345 12340 Z [myapp] <defunct>
# 上面的输出说明:
# PID 12345 是僵尸进程
# 它的父进程是 PID 12340
# 统计僵尸进程数量
ps aux | awk '{print $8}' | grep -c Z
18.8.3 清理僵尸进程
僵尸进程本身不能被kill直接杀掉(因为它已经死了!),你需要"杀"的是它的父进程,或者让父进程去回收它。
bash
# 第一步:找到僵尸进程的父进程
ps -eo pid,ppid,stat,cmd | awk '$3 ~ /Z/'
# 得到父进程PID(PPID列)
# 第二步:尝试让父进程回收子进程
# 发送SIGCHLD信号给父进程,提醒它去回收子进程
kill -CHLD <父进程PID>
# 第三步:如果父进程没有响应,只能杀掉父进程
kill <父进程PID>
# 如果普通kill无效,使用SIGKILL强制杀掉
kill -9 <父进程PID>
# 注意:杀掉父进程后,僵尸进程会被init(PID 1)接管
# init会自动回收僵尸进程
# 第四步:如果父进程是init(PID 1)且僵尸进程还在
# 这通常意味着系统有问题,可能需要重启
# 但在重启前,检查是不是有内核问题
dmesg | tail -20
18.8.4 不可中断睡眠进程(D状态)
比僵尸进程更棘手的是D状态(Uninterruptible Sleep)的进程。这种进程正在等待I/O操作完成,不能被信号中断------连 kill -9 都杀不掉!
bash
# 查找D状态的进程
ps aux | awk '$8 ~ /D/'
# D状态进程通常在等待磁盘I/O
# 常见原因:
# 1. 磁盘故障导致I/O操作卡住
# 2. NFS挂载的服务器无响应
# 3. 存储网络断开
# 解决方法:
# 1. 修复底层的I/O问题(如恢复NFS服务器)
# 2. 如果是NFS问题,可以强制卸载
umount -f /mnt/nfs
umount -l /mnt/nfs # lazy unmount
# 3. 如果以上都不行,可能只能重启系统
# 但D状态进程可能导致重启也卡住
# 这时只能从硬件层面强制重启
# 预防方法:
# 1. 使用NFS时加soft选项,避免无限等待
# mount -o soft,timeo=30 server:/share /mnt/nfs
# 2. 定期检查磁盘健康
# 3. 配置存储多路径
18.8.5 孤儿进程
孤儿进程(Orphan Process)和僵尸进程相反------父进程先退出了,子进程还在运行。这些子进程会被init(PID 1)"收养",成为孤儿进程。
孤儿进程本身不是问题,init会负责管理它们。但如果一个程序产生了大量孤儿进程且不清理,就会占用系统资源。
bash
# 查找孤儿进程(父进程是init/PID 1的进程)
ps -eo pid,ppid,cmd | awk '$2 == 1'
# 孤儿进程通常不需要特别处理
# 但如果数量过多,说明程序设计有问题
18.8.6 进程管理常用命令
bash
# kill命令------发送信号给进程
kill -l # 列出所有信号
kill 1234 # 发送SIGTERM(优雅退出),默认信号
kill -9 1234 # 发送SIGKILL(强制杀掉),不可被忽略
kill -15 1234 # 发送SIGTERM,和kill 1234一样
kill -HUP 1234 # 发送SIGHUP,通常用于让进程重新加载配置
kill -USR1 1234 # 发送SIGUSR1,自定义信号
kill -STOP 1234 # 暂停进程(不是终止)
kill -CONT 1234 # 恢复暂停的进程
# killall命令------按进程名杀进程
killall nginx # 杀掉所有名为nginx的进程
killall -9 nginx # 强制杀掉
killall -u mysql # 杀掉mysql用户的所有进程
# pkill命令------更灵活的按名杀进程
pkill nginx # 和killall nginx类似
pkill -u mysql # 杀掉mysql用户的进程
pkill -f "python script.py" # 按完整命令行匹配
# 常用信号说明:
# 1 SIGHUP - 挂起信号,常用于重新加载配置
# 2 SIGINT - 中断信号,相当于Ctrl+C
# 3 SIGQUIT - 退出信号,相当于Ctrl+\
# 9 SIGKILL - 强制杀掉,不可被捕获或忽略
# 15 SIGTERM - 优雅终止,默认信号
# 18 SIGCONT - 恢复运行
# 19 SIGSTOP - 暂停运行
18.8.7 实际案例:进程卡死导致服务不可用
故障背景:Nginx服务无法响应请求,重启命令也卡住不动。
排查过程:
bash
# 1. 查看nginx进程状态
ps aux | grep nginx
# 发现nginx worker进程都是D状态(不可中断睡眠)
# 2. 查看进程在等待什么
cat /proc/<nginx_pid>/stack
# 或者
cat /proc/<nginx_pid>/wchan
# 发现都在等待磁盘I/O
# 3. 检查磁盘状态
iostat -x 1 3
# %util 100%,磁盘完全卡住了
# 4. 检查dmesg
dmesg | tail -20
# 发现大量磁盘错误:
# sd 0:0:0:0: [sda] tag#0 FAILED Result: hostbyte=DID_ERROR
# 这说明磁盘硬件故障
# 5. 解决方案
# 短期:强制杀掉nginx进程
kill -9 <nginx_pid>
# 如果kill -9也无效(因为进程在D状态),只能等待I/O超时
# 长期:更换故障磁盘,恢复数据
18.8.8 小结
进程僵死与处理的核心要点:
- 理解进程状态:R(运行)、S(睡眠)、D(不可中断睡眠)、T(停止)、Z(僵尸)
- 僵尸进程是子进程已死但父进程没有回收,需要杀父进程来清理
- D状态进程无法被kill,需要修复底层I/O问题
- 常用信号:SIGTERM(15)优雅退出,SIGKILL(9)强制杀掉
- kill/pkill/killall是进程管理三剑客
18.9 系统日志分析技巧
18.9.1 日志的重要性------用"侦探破案"来类比
系统日志就是Linux留给你的"案发现场"。当故障发生后,系统会在日志中留下大量线索。优秀的运维工程师就像优秀的侦探,能从海量日志中找到关键证据。
Linux系统的日志就像城市的监控摄像头网络:
/var/log/messages(或/var/log/syslog)------城市总监控/var/log/secure(或/var/log/auth.log)------门禁系统记录/var/log/dmesg------系统启动时的监控/var/log/nginx/access.log------某个建筑的进出记录journalctl------可以查询所有监控的智能系统
18.9.2 日志系统架构
现代Linux使用两种日志系统:
传统syslog:
bash
# 日志配置文件
cat /etc/rsyslog.conf
cat /etc/rsyslog.d/*.conf
# 日志级别(从高到低):
# emerg - 紧急:系统不可用
# alert - 警报:必须立即处理
# crit - 严重:严重情况
# err - 错误:错误信息
# warn - 警告:警告信息
# notice - 通知:正常但重要
# info - 信息:一般信息
# debug - 调试:调试信息
# 日志位置说明:
# /var/log/messages - 系统主日志(CentOS)
# /var/log/syslog - 系统主日志(Ubuntu)
# /var/log/secure - 安全日志(CentOS)
# /var/log/auth.log - 认证日志(Ubuntu)
# /var/log/maillog - 邮件日志
# /var/log/cron - 定时任务日志
# /var/log/boot.log - 启动日志
# /var/log/dmesg - 内核环缓冲日志
systemd journal:
bash
# journalctl是systemd的日志查询工具,功能非常强大
# 查看所有日志
journalctl
# 查看最近的日志(类似tail -f)
journalctl -f
# 查看今天的日志
journalctl --since today
# 查看指定时间范围的日志
journalctl --since "2024-01-15 10:00:00" --until "2024-01-15 12:00:00"
journalctl --since "1 hour ago"
journalctl --since "30 min ago"
# 查看指定服务的日志
journalctl -u nginx.service
journalctl -u nginx.service --since today
# 查看指定进程的日志
journalctl _PID=1234
# 按日志级别过滤
journalctl -p err # 只看error及以上
journalctl -p err -p warning # 错误和警告
# 查看本次启动的日志
journalctl -b
# 查看上一次启动的日志
journalctl -b -1
# 查看内核日志
journalctl -k
# 以JSON格式输出(方便程序处理)
journalctl -o json
# 显示日志尾部
journalctl -n 50 # 最后50条
18.9.3 日志分析实战技巧
技巧一:grep过滤关键信息
bash
# 在系统日志中查找错误
grep -i error /var/log/messages
grep -i "fail" /var/log/messages
grep -i "panic" /var/log/messages
# 查找指定时间段的错误
awk '/Jan 15 10:/' /var/log/messages | grep -i error
# 统计各种错误出现的次数
grep -i error /var/log/messages | awk '{print $5,$6,$7}' | sort | uniq -c | sort -rn | head -20
# 查找包含多个关键词的行
grep -E "error|fail|critical" /var/log/messages
# 排除某些关键词
grep -v "INFO" /var/log/app.log | grep -i error
# 显示匹配行及上下文
grep -C 5 "error" /var/log/messages # 显示匹配行前后各5行
grep -B 5 "error" /var/log/messages # 显示匹配行前5行
grep -A 5 "error" /var/log/messages # 显示匹配行后5行
技巧二:awk提取和统计
bash
# 提取日志中的IP地址并统计访问次数
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 统计HTTP状态码分布
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 50000 200 <-- 200状态码50000次
# 1000 404 <-- 404状态码1000次
# 500 500 <-- 500状态码500次
# 统计每小时的访问量
awk '{print $4}' /var/log/nginx/access.log | cut -c 14-15 | sort | uniq -c
# $4是时间字段,截取第14-15位(小时部分)
# 找出访问量最大的URL
awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -20
# 计算请求的平均响应时间(假设日志中有响应时间字段)
awk '{sum += $NF; count++} END {print "平均响应时间:", sum/count, "ms"}' /var/log/nginx/access.log
技巧三:实时监控日志
bash
# tail -f 实时监控日志
tail -f /var/log/messages
# 同时监控多个日志文件
tail -f /var/log/messages /var/log/secure
# 只显示包含error的行
tail -f /var/log/messages | grep --line-buffered -i error
# 使用journalctl实时监控
journalctl -f
journalctl -f -u nginx
# 多功能日志监控工具 multitail
# 安装:yum install multitail 或 apt install multitail
multitail /var/log/messages /var/log/secure
技巧四:日志轮转管理
bash
# logrotate配置
cat /etc/logrotate.conf
# 全局配置
cat /etc/logrotate.d/nginx
# /var/log/nginx/*.log {
# daily # 每天轮转
# rotate 30 # 保留30个旧日志
# compress # 压缩旧日志
# delaycompress # 延迟压缩(最近的旧日志不压缩)
# missingok # 日志不存在不报错
# notifempty # 日志为空不轮转
# create 644 nginx nginx # 创建新日志的权限和属主
# postrotate # 轮转后执行的命令
# if [ -f /var/run/nginx.pid ]; then
# kill -USR1 `cat /var/run/nginx.pid`
# fi
# endscript
# }
# 手动测试logrotate
logrotate -d /etc/logrotate.d/nginx # 调试模式,不实际执行
logrotate -f /etc/logrotate.d/nginx # 强制执行轮转
18.9.4 分析登录和安全日志
bash
# 查看成功登录的记录
last
# root pts/0 192.168.1.50 Mon Jan 15 10:30 still logged in
# user pts/1 10.0.0.100 Mon Jan 15 09:00 - 10:00 (01:00)
# 查看失败登录的记录
lastb
# 或者查看安全日志
grep "Failed password" /var/log/secure # CentOS
grep "Failed password" /var/log/auth.log # Ubuntu
# 统计失败登录的IP(防暴力破解)
grep "Failed password" /var/log/secure | awk '{print $11}' | sort | uniq -c | sort -rn | head -20
# 100 192.168.1.200 <-- 这个IP尝试了100次!
# 50 10.0.0.99
# 30 172.16.0.5
# 查看SSH登录记录
journalctl -u sshd --since today
# 查看谁在使用sudo
grep "sudo" /var/log/secure
# 查看用户创建和删除记录
grep "useradd\|userdel" /var/log/secure
18.9.5 实际案例:通过日志定位Web服务502错误
故障背景:用户访问网站时频繁出现502 Bad Gateway错误。
排查过程:
bash
# 1. 查看Nginx错误日志
tail -100 /var/log/nginx/error.log
# 发现大量类似错误:
# connect() to unix:/var/run/php-fpm.sock failed (11: Resource temporarily unavailable)
# 2. 说明PHP-FPM无法处理更多请求了
# 查看PHP-FPM状态
systemctl status php-fpm
# 3. 查看PHP-FPM日志
tail -100 /var/log/php-fpm/error.log
# 发现:WARNING: [pool www] server reached pm.max_children setting (50)
# 4. 说明PHP-FPM的子进程数达到了上限
# 查看当前配置
grep "max_children" /etc/php-fpm.d/www.conf
# pm.max_children = 50
# 5. 查看PHP-FPM子进程的内存使用
ps -C php-fpm -o pid,rss,cmd --sort=-rss | head
# 发现每个子进程占用100MB+,50个就是5GB
# 6. 解决方案:优化PHP-FPM配置
# 增加max_children或减少每个进程的内存使用
# 修改配置后重启
systemctl restart php-fpm
# 7. 验证:继续观察日志
tail -f /var/log/nginx/error.log
# 502错误消失
18.9.6 集中式日志管理
当服务器数量增多时,逐台查看日志效率太低。这时需要集中式日志管理:
bash
# 常见的集中式日志方案:
# 1. ELK Stack (Elasticsearch + Logstash + Kibana)
# 2. Graylog
# 3. Loki + Grafana
# 简单的远程日志转发(通过rsyslog)
# 在客户端配置
cat /etc/rsyslog.d/remote.conf
# *.* @@logserver.example.com:514 # TCP转发
# 或
# *.* @logserver.example.com:514 # UDP转发
# 重启rsyslog
systemctl restart rsyslog
18.9.7 小结
系统日志分析的核心要点:
- 日志是破案的关键证据,出了问题第一时间看日志
- journalctl是systemd日志的瑞士军刀,学会各种过滤参数
- grep+awk+sort+uniq是日志分析的黄金组合
- 设置合理的日志轮转策略,防止日志占满磁盘
- 服务器多时使用集中式日志管理(ELK等)
18.10 单用户模式与救援模式
18.10.1 什么是单用户模式?------用"急诊室"来类比
当系统出了严重问题无法正常启动时,你需要一个"急诊室"------这就是单用户模式和救援模式。
单用户模式就像医院的急诊室:
- 只有一个医生值班(单用户)
- 不对外开放(没有网络服务)
- 大部分设备关闭(只启动基本服务)
- 可以做紧急处理(修改配置、修复文件系统)
救援模式就像医疗队的野外急救------当系统自身已经无法启动时,用外部工具(Live CD/U盘)来抢救。
18.10.2 进入单用户模式
方法一:通过GRUB菜单进入(CentOS 7 / RHEL 7+)
bash
# 1. 重启系统,在GRUB菜单界面按 e 键编辑启动项
# 2. 找到以 linux16 或 linux 开头的行
# 3. 在该行末尾添加:
# rd.break
# 或者
# systemd.unit=rescue.target
# 或者(更早期的单用户模式)
# 1 或 single
# 4. 按 Ctrl+X 启动
# 使用 rd.break 进入的步骤:
# 系统会停在一个紧急维修shell
# 此时根文件系统以只读方式挂载在 /sysroot
# 重新挂载为读写
switch_root:/# mount -o remount,rw /sysroot
# chroot到根文件系统
switch_root:/# chroot /sysroot
# 现在你可以修改任何文件了
# 例如:重置root密码
sh-4.2# passwd root
# 输入新密码
# 如果SELinux是enforcing模式,需要创建标记文件
sh-4.2# touch /.autorelabel
# 退出并重启
sh-4.2# exit
switch_root:/# exit
# 系统会自动重启
方法二:修改GRUB进入单用户模式(Ubuntu/Debian)
bash
# 1. 重启系统,在GRUB菜单界面按 e 键
# 2. 找到以 linux 开头的行
# 3. 将 ro(只读)改为 rw(读写)
# 4. 在行末添加:
# init=/bin/bash
# 或者
# systemd.unit=rescue.target
# 5. 按 Ctrl+X 或 F10 启动
# 使用 init=/bin/bash 的步骤:
# 系统直接进入bash shell,不需要密码
# 重新挂载根文件系统为读写
root@(none):/# mount -o remount,rw /
# 修改需要的文件
# 例如重置root密码
root@(none):/# passwd root
# 重启
root@(none):/# exec /sbin/init
# 或者
root@(none):/# reboot -f
18.10.3 救援模式------使用Live CD/USB
当系统完全无法启动,连GRUB都进不去时,就需要用Live CD/USB来救援了。
bash
# 第一步:用Live USB启动,选择"Try Ubuntu"或类似选项
# 第二步:打开终端,查看磁盘分区
sudo fdisk -l
# 或者
lsblk
# 找到原来的根分区,比如 /dev/sda1
# 第三步:挂载原系统分区
sudo mount /dev/sda1 /mnt
# 如果有独立的/boot分区,也要挂载
sudo mount /dev/sda2 /mnt/boot
# 如果有/boot/efi分区(UEFI系统)
sudo mount /dev/sda3 /mnt/boot/efi
# 第四步:挂载虚拟文件系统(chroot需要)
sudo mount --bind /dev /mnt/dev
sudo mount --bind /dev/pts /mnt/dev/pts
sudo mount --bind /proc /mnt/proc
sudo mount --bind /sys /mnt/sys
sudo mount --bind /run /mnt/run
# 第五步:chroot到原系统
sudo chroot /mnt
# 现在你就在原系统的环境中了!
# 可以执行任何修复操作:
# - 修改配置文件
# - 修复GRUB
# - 修复文件系统
# - 安装/卸载软件包
# 第六步:修复完成后退出
exit
# 第七步:卸载所有挂载
sudo umount /mnt/run
sudo umount /mnt/sys
sudo umount /mnt/proc
sudo umount /mnt/dev/pts
sudo umount /mnt/dev
sudo umount /mnt/boot/efi # 如果挂载了
sudo umount /mnt/boot # 如果挂载了
sudo umount /mnt
# 第八步:重启
sudo reboot
18.10.4 使用CentOS/RHEL安装盘进入救援模式
bash
# 1. 用CentOS安装光盘/U盘启动
# 2. 在安装菜单选择 "Troubleshooting"
# 3. 选择 "Rescue a CentOS system"
# 4. 选择继续模式(Continue)
# - Continue: 读写模式挂载,最常用
# - Read-Only: 只读模式
# - Skip: 不挂载,直接进入shell
# - Advanced: 高级选项
# 5. 系统会自动挂载原系统到 /mnt/sysimages
# 6. 提示你执行 chroot /mnt/sysimages
# 执行chroot
chroot /mnt/sysimages
# 现在可以进行修复了
# 常见修复操作:
# 修复GRUB
grub2-install /dev/sda
grub2-mkconfig -o /boot/grub2/grub.cfg
# 重建initramfs
dracut --force
# 修复文件系统
fsck /dev/sda1
# 重置root密码
passwd root
# 修复fstab
vi /etc/fstab
18.10.5 实际案例:忘记root密码
这是最常见的需求------忘记了root密码无法登录系统。
bash
# CentOS 7+ 重置root密码:
# 1. 重启,在GRUB菜单按e
# 2. 在linux16行末添加 rd.break
# 3. Ctrl+X启动
# 4. 依次执行:
mount -o remount,rw /sysroot
chroot /sysroot
passwd root
# 输入新密码两次
touch /.autorelabel # SELinux需要
exit
exit
# Ubuntu重置root密码:
# 1. 重启,在GRUB菜单按e
# 2. 找到linux行,把ro改成rw,末尾加 init=/bin/bash
# 3. Ctrl+X或F10启动
# 4. 执行:
passwd root
# 或者
passwd <你的用户名>
# 5. 重启:
exec /sbin/init
# 或者强制重启
reboot -f
18.10.6 实际案例:GRUB损坏修复
bash
# 场景:安装双系统后,Linux的GRUB被Windows覆盖了
# 1. 用Live USB启动
# 2. 挂载Linux分区
sudo mount /dev/sda2 /mnt
# 如果有独立的/boot分区
sudo mount /dev/sda1 /mnt/boot
# 3. 挂载虚拟文件系统
sudo mount --bind /dev /mnt/dev
sudo mount --bind /proc /mnt/proc
sudo mount --bind /sys /mnt/sys
# 4. chroot
sudo chroot /mnt
# 5. 重新安装GRUB
# BIOS系统:
grub-install /dev/sda
# UEFI系统:
grub-install --target=x86_64-efi --efi-directory=/boot/efi --bootloader-id=centos
# 6. 重新生成GRUB配置
grub2-mkconfig -o /boot/grub2/grub.cfg # CentOS
update-grub # Ubuntu
# 7. 退出并重启
exit
sudo reboot
18.10.7 单用户模式的安全风险
需要注意的是,任何人只要能物理接触服务器,就能通过单用户模式重置root密码。这就像任何人只要能进入医院急诊室,就能做治疗一样。
bash
# 保护GRUB,防止未授权进入单用户模式
# 第一步:设置GRUB密码
grub2-setpassword
# 输入密码后会生成 /boot/grub2/user.cfg
# 第二步:重新生成GRUB配置
grub2-mkconfig -o /boot/grub2/grub.cfg
# 这样以后编辑GRUB启动项就需要密码了
# Ubuntu下设置GRUB密码:
# 编辑 /etc/grub.d/40_custom
# 添加:
# set superusers="admin"
# password admin your_password
# 然后 update-grub
# 更安全的做法:使用密码哈希
grub-mkpasswd-pbkdf2
# 生成哈希后写入配置文件
18.10.8 小结
单用户模式与救援模式的核心要点:
- 单用户模式是系统的"急诊室",只启动基本服务
- 通过GRUB编辑进入单用户模式(rd.break / init=/bin/bash)
- 救援模式用Live CD/USB启动,通过chroot修复原系统
- 常见用途:重置密码、修复GRUB、修复文件系统、修改配置
- 注意保护GRUB,防止未授权访问
18.11 数据备份与恢复策略
18.11.1 备份的重要性------用"保险"来类比
数据备份就像买保险------没出事时你觉得浪费钱,出了事时你庆幸买了。但和保险不同的是,数据丢失不是"可能"发生,而是"迟早"会发生。硬盘会坏、人会误操作、系统会被黑、机房会断电......唯一的问题是:你的数据备份好了吗?
数据丢失的常见原因:
- 硬件故障:硬盘坏道、服务器烧毁
- 人为错误:误删文件、误执行命令
- 软件bug:程序错误导致数据损坏
- 恶意攻击:勒索软件加密数据
- 自然灾害:火灾、水灾、地震
- 电力故障:突然断电导致数据损坏
18.11.2 备份的"3-2-1"黄金法则
备份领域有一个著名的"3-2-1"法则,就像买保险要买够保额一样:
-
3 份数据副本(1份原始数据 + 2份备份)
-
2 种不同的存储介质(如硬盘 + 磁带 / 本地 + 云)
-
1 份异地备份(防止机房整体灾难)
原始数据(本地) ──→ 备份1(本地其他磁盘) ──→ 备份2(异地/云端)
18.11.3 备份的分类
| 类型 | 说明 | 恢复速度 | 存储空间 | 类比 |
|---|---|---|---|---|
| 完全备份 | 备份所有数据 | 最快 | 最大 | 给整栋楼拍照 |
| 增量备份 | 只备份上次备份后变化的数据 | 最慢 | 最小 | 只记录新搬进来的东西 |
| 差异备份 | 备份上次完全备份后变化的数据 | 中等 | 中等 | 记录从上次拍照后所有变化 |
完全备份策略示例:
周一:完全备份(100GB)
周二:差异备份(10GB)------ 周一以来变化的数据
周三:差异备份(20GB)------ 周一以来变化的数据
周四:差异备份(30GB)------ 周一以来变化的数据
周五:完全备份(105GB)------ 新的一轮开始
增量备份策略示例:
周一:完全备份(100GB)
周二:增量备份(5GB)------ 周一以来变化的数据
周三:增量备份(5GB)------ 周二以来变化的数据
周四:增量备份(5GB)------ 周三以来变化的数据
周五:增量备份(5GB)------ 周四以来变化的数据
18.11.4 常用备份工具
tar------最基础的备份工具
bash
# 完全备份
tar -czvf /backup/full_$(date +%Y%m%d).tar.gz /home /etc /var/www
# 参数说明:
# -c: 创建归档
# -z: 使用gzip压缩
# -v: 显示过程
# -f: 指定文件名
# -p: 保留权限
# 增量备份(使用gzip+tar的增量功能)
# 第一次完全备份
tar -czvf /backup/full.tar.gz -g /backup/snapshot.snar /home
# 第二次增量备份
tar -czvf /backup/incr_$(date +%Y%m%d).tar.gz -g /backup/snapshot.snar /home
# -g参数指定快照文件,tar会根据快照判断哪些文件变化了
# 恢复
# 恢复完全备份
tar -xzvf /backup/full.tar.gz -C /
# 恢复增量备份(按顺序)
tar -xzvf /backup/full.tar.gz -C /
tar -xzvf /backup/incr_20240116.tar.gz -C /
tar -xzvf /backup/incr_20240117.tar.gz -C /
# 查看备份内容(不解压)
tar -tzvf /backup/full.tar.gz | head -20
rsync------增量同步的利器
rsync是Linux下最强大的备份工具之一,它只传输变化的数据,效率极高。
bash
# 基本同步
rsync -avz /home/ /backup/home/
# 参数说明:
# -a: 归档模式(保留权限、时间等)
# -v: 显示详细信息
# -z: 压缩传输
# 注意:源路径末尾的/很重要!
# /home/ 表示同步/home目录下的内容
# /home 表示把/home目录本身同步过去
# 删除目标中源端已删除的文件(完全镜像)
rsync -avz --delete /home/ /backup/home/
# 远程备份(通过SSH)
rsync -avz -e ssh /home/ user@backup-server:/backup/home/
# 使用SSH非标准端口
rsync -avz -e "ssh -p 2222" /home/ user@backup-server:/backup/home/
# 显示传输进度
rsync -avz --progress /home/ /backup/home/
# 排除不需要备份的文件
rsync -avz --exclude='*.tmp' --exclude='cache/' /home/ /backup/home/
# 使用exclude文件
echo "*.tmp" > /tmp/exclude.txt
echo "cache/" >> /tmp/exclude.txt
rsync -avz --exclude-from='/tmp/exclude.txt' /home/ /backup/home/
# 限制带宽(避免占满网络)
rsync -avz --bwlimit=10000 /home/ user@backup-server:/backup/
# --bwlimit单位是KB/s,10000表示10MB/s
dd------磁盘/分区级别的备份
dd可以备份整个磁盘或分区,就像给硬盘"拍X光片"。
bash
# 备份整个磁盘到镜像文件
dd if=/dev/sda of=/backup/sda_image.img bs=4M status=progress
# if: 输入文件(源)
# of: 输出文件(目标)
# bs: 块大小(4M表示每次读写4MB)
# status=progress: 显示进度
# 备份MBR(主引导记录,前512字节)
dd if=/dev/sda of=/backup/mbr_backup bs=512 count=1
# 恢复MBR
dd if=/backup/mbr_backup of=/dev/sda bs=512 count=1
# 克隆磁盘(磁盘到磁盘)
dd if=/dev/sda of=/dev/sdb bs=4M status=progress
# 创建磁盘压缩镜像
dd if=/dev/sda bs=4M | gzip -c > /backup/sda_image.img.gz
# 恢复压缩镜像
gunzip -c /backup/sda_image.img.gz | dd of=/dev/sda bs=4M
# 注意:dd命令非常危险!
# if和of千万不能写反,写反了会毁掉数据!
# 使用前一定要确认好源和目标!
mysqldump------数据库备份
bash
# 备份单个数据库
mysqldump -u root -p mydb > /backup/mydb_$(date +%Y%m%d).sql
# 备份所有数据库
mysqldump -u root -p --all-databases > /backup/all_db_$(date +%Y%m%d).sql
# 备份时压缩
mysqldump -u root -p mydb | gzip > /backup/mydb_$(date +%Y%m%d).sql.gz
# 备份指定表
mysqldump -u root -p mydb table1 table2 > /backup/tables.sql
# 仅备份表结构(不包含数据)
mysqldump -u root -p --no-data mydb > /backup/schema.sql
# 仅备份数据(不包含表结构)
mysqldump -u root -p --no-create-info mydb > /backup/data.sql
# 恢复数据库
mysql -u root -p mydb < /backup/mydb_20240115.sql
# 恢复压缩的备份
gunzip < /backup/mydb_20240115.sql.gz | mysql -u root -p mydb
18.11.5 自动化备份脚本
bash
#!/bin/bash
# 自动备份脚本
# 保存为 /usr/local/bin/auto_backup.sh
# 加入crontab自动执行:0 2 * * * /usr/local/bin/auto_backup.sh
# ===== 配置部分 =====
BACKUP_DIR="/backup"
DATE=$(date +%Y%m%d)
DAY_OF_WEEK=$(date +%u) # 1=周一,7=周日
LOG_FILE="/var/log/backup.log"
RETENTION_DAYS=7 # 本地备份保留天数
REMOTE_SERVER="backup@example.com"
REMOTE_DIR="/remote_backup"
# ===== 日志函数 =====
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> $LOG_FILE
}
# ===== 开始备份 =====
log "========== 开始备份 =========="
# 1. 备份配置文件
log "备份配置文件..."
tar -czf $BACKUP_DIR/etc_$DATE.tar.gz /etc 2>>$LOG_FILE
log "配置文件备份完成"
# 2. 备份数据库
log "备份数据库..."
mysqldump -u root -ppassword --all-databases | gzip > $BACKUP_DIR/mysql_$DATE.sql.gz 2>>$LOG_FILE
log "数据库备份完成"
# 3. 备份网站文件
log "备份网站文件..."
rsync -a /var/www/ $BACKUP_DIR/www_$DATE/ 2>>$LOG_FILE
log "网站文件备份完成"
# 4. 同步到远程服务器
log "同步到远程服务器..."
rsync -az $BACKUP_DIR/*_$DATE.* $REMOTE_SERVER:$REMOTE_DIR/ 2>>$LOG_FILE
log "远程同步完成"
# 5. 清理旧备份
log "清理超过${RETENTION_DAYS}天的旧备份..."
find $BACKUP_DIR -name "*_*" -mtime +$RETENTION_DAYS -delete 2>>$LOG_FILE
log "旧备份清理完成"
log "========== 备份完成 =========="
18.11.6 备份验证
备份了不等于备份成功了!一定要验证备份的完整性。就像买了保险要确认保单有效一样。
bash
# 验证tar备份
tar -tzvf /backup/full_20240115.tar.gz > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "备份文件完整"
else
echo "备份文件损坏!"
fi
# 验证压缩文件
gzip -t /backup/full_20240115.tar.gz
# -t表示测试,如果没输出说明文件完好
# 验证数据库备份
# 检查SQL文件末尾是否有完成标记
tail -5 /backup/mysql_20240115.sql
# 应该看到类似:-- Dump completed on ...
# 定期做恢复测试
# 最好每月做一次完整的恢复演练
# 在测试环境中恢复备份,验证数据是否正确
18.11.7 数据恢复
当数据丢失时,如何恢复:
bash
# 从tar备份恢复
tar -xzvf /backup/full_20240115.tar.gz -C /restore/
# 从rsync备份恢复
rsync -avz /backup/home/ /home/
# 从数据库备份恢复
mysql -u root -p < /backup/mysql_20240115.sql
# 误删文件恢复(如果文件系统支持)
# ext4文件系统可以使用extundelete
extundelete /dev/sda1 --restore-file path/to/deleted/file
# 如果文件被rm删除但进程还持有文件句柄
# 参考18.7.4节的方法,通过/proc恢复
# 专业数据恢复工具:testdisk
# 可以恢复被删除的分区
testdisk /dev/sda
18.11.8 小结
数据备份与恢复的核心要点:
- 遵循3-2-1法则:3份数据、2种介质、1份异地
- 完全+增量/差异备份组合使用
- tar适合小量备份,rsync适合大量增量同步,dd适合磁盘级备份
- 自动化备份脚本 + crontab实现无人值守
- 必须验证备份完整性
- 定期做恢复演练
18.12 灾难恢复演练
18.12.1 为什么要做演练?------用"消防演习"来类比
你可能经常看到新闻报道"某公司服务器宕机,数据全部丢失",然后你心想:"这家公司怎么没有备份?"但现实是,很多公司确实有备份,但出了事还是恢复不了------因为他们的备份从来没测试过!
这就像学校每学期都要做消防演习一样。如果你从来没跑过消防通道,万一真的着火了,你能在浓烟中找到正确的路吗?
灾难恢复演练就是给你的系统做"消防演习"------模拟真实的灾难场景,验证你的备份和恢复方案是否真的有效。
没有做过演练的备份 = 没有备份!
18.12.2 灾难恢复计划的制定
在开始演练之前,必须先有一份完整的灾难恢复计划(DRP, Disaster Recovery Plan)。
灾难恢复计划模板:
==============================
一、组织架构
- 恢复团队负责人:张三(手机:138xxxx)
- 系统管理员:李四(手机:139xxxx)
- 数据库管理员:王五(手机:137xxxx)
- 网络管理员:赵六(手机:136xxxx)
- 通讯协调人:钱七(手机:135xxxx)
二、系统分类与恢复优先级
P0级别(必须最先恢复):
- 核心数据库
- 主网站
P1级别(2小时内恢复):
- 用户认证系统
- 支付系统
P2级别(8小时内恢复):
- 内部管理系统
- 报表系统
P3级别(24小时内恢复):
- 日志系统
- 监控系统
三、RTO和RPO目标
- RTO(Recovery Time Objective)恢复时间目标:核心系统4小时内恢复
- RPO(Recovery Point Objective)恢复点目标:数据丢失不超过1小时
四、备份策略
- 核心数据库:每天完全备份 + 每小时增量备份
- 网站文件:每天rsync同步
- 配置文件:每次修改后立即备份
- 异地备份:每天同步到远程机房
五、恢复步骤
(针对每种故障场景,写出详细的恢复步骤)
六、联系方式
- 硬件供应商:400-xxx-xxxx
- 云服务商:400-xxx-xxxx
- ISP运营商:10000
==============================
18.12.3 演练场景设计
场景一:单台服务器故障
模拟:一台Web服务器突然宕机,硬件损坏无法修复。
演练步骤:
bash
# 1. 通知相关人员
# "演练开始:Web服务器web01硬件故障,需要在新服务器上恢复"
# 2. 准备新服务器
# 安装相同版本的操作系统
# 3. 恢复配置文件
scp backup@backup-server:/backup/etc_20240115.tar.gz /tmp/
tar -xzvf /tmp/etc_20240115.tar.gz -C /tmp/restore/
cp /tmp/restore/etc/nginx/nginx.conf /etc/nginx/
cp -r /tmp/restore/etc/nginx/conf.d/* /etc/nginx/conf.d/
# 4. 恢复网站文件
rsync -avz backup@backup-server:/backup/www_20240115/ /var/www/
# 5. 恢复数据库
gunzip < backup@backup-server:/backup/mysql_20240115.sql.gz | mysql -u root -p
# 6. 安装和配置服务
yum install nginx php-fpm mysql-server
systemctl start nginx php-fpm mysql
# 7. 验证
curl -I http://localhost
# 确保返回200
# 8. 修改DNS指向新服务器
# 或者修改负载均衡配置
# 9. 记录恢复时间
# 从开始到恢复完成用了多长时间?是否满足RTO?
# 10. 演练总结
# 哪些环节顺利?哪些环节有问题?需要改进什么?
场景二:数据库误操作
模拟 :DBA误执行了 DELETE FROM users(没有WHERE条件),删除了所有用户数据。
演练步骤:
bash
# 1. 立即停止应用,防止更多操作
systemctl stop webapp
# 2. 记录误操作的时间
# 假设误操作发生在 2024-01-15 14:30:00
# 3. 查看可用的备份
ls -la /backup/mysql/
# mysql_20240115.sql.gz -- 今天的完全备份(凌晨2点)
# mysql_incr_14.sql.gz -- 14点的增量备份
# 4. 方案A:从备份恢复
# 恢复凌晨2点的完全备份
gunzip < /backup/mysql_20240115.sql.gz | mysql -u root -p mydb
# 恢复增量备份到误操作前
gunzip < /backup/mysql_incr_14.sql.gz | mysql -u root -p mydb
# 问题:会丢失14点到14:30之间的数据
# 5. 方案B:使用MySQL binlog恢复
# 查看binlog
mysqlbinlog --start-datetime="2024-01-15 02:00:00" \
--stop-datetime="2024-01-15 14:29:59" \
/var/lib/mysql/mysql-bin.000123 | mysql -u root -p mydb
# 这样可以恢复到误操作前一秒的状态
# 6. 验证数据
mysql -u root -p -e "SELECT COUNT(*) FROM users"
# 确认数据恢复正确
# 7. 重启应用
systemctl start webapp
# 8. 记录RPO
# 数据丢失了多少?是否满足RPO要求?
场景三:整个机房故障
模拟:机房停电且UPS故障,所有服务器宕机。
演练步骤:
bash
# 这是最极端的灾难场景,需要异地灾备
# 1. 启用异地灾备机房
# 2. 修改DNS指向灾备机房
# 3. 从异地备份恢复数据
# 4. 验证所有服务
# 5. 通知用户
# 详细步骤取决于具体的灾备架构(冷备/热备/双活)
# 冷备:灾备机房只有备份,需要从头恢复
# 热备:灾备机房有实时数据同步,可以快速切换
# 双活:两个机房同时运行,一个挂了另一个自动接管
18.12.4 演练评估与改进
每次演练后,必须进行评估和改进。这就像消防演习后要开会总结一样。
演练评估报告模板:
==============================
演练名称:Web服务器故障恢复演练
演练日期:2024-01-20
演练时长:3小时
一、演练目标
- 验证Web服务器在硬件故障后能在4小时内恢复(RTO=4h)
- 验证数据丢失不超过1小时(RPO=1h)
二、演练结果
- RTO实际值:3小时15分钟 ✓ 达标
- RPO实际值:30分钟 ✓ 达标
三、发现的问题
1. 备份文件下载太慢(花了30分钟)
原因:备份服务器带宽不够
改进:升级备份服务器带宽或使用专用备份网络
2. 恢复步骤文档不够详细
原因:某些步骤依赖个人经验
改进:完善恢复文档,做到任何人按文档操作都能完成
3. 新服务器环境与旧服务器不一致
原因:系统版本和依赖包版本有差异
改进:使用自动化部署工具(Ansible/Docker)保证环境一致性
4. 没有监控告警
原因:恢复过程中没有监控,不知道是否成功
改进:在恢复过程中加入健康检查
四、改进计划
1. 2月底前升级备份服务器带宽
2. 1月底前完善恢复文档
3. 3月底前实现自动化部署
4. 持续改进
==============================
18.12.5 自动化恢复------使用脚本和工具
bash
#!/bin/bash
# 自动化恢复脚本示例
# 保存为 /usr/local/bin/auto_recover.sh
# ===== 配置 =====
BACKUP_SERVER="backup@example.com"
BACKUP_DIR="/remote_backup"
WEB_ROOT="/var/www"
DB_NAME="mydb"
DB_USER="root"
DB_PASS="password"
# ===== 恢复函数 =====
recover_web() {
echo "恢复网站文件..."
# 找到最新的网站备份
LATEST_WWW=$(ssh $BACKUP_SERVER "ls -t $BACKUP_DIR/www_* | head -1")
# 恢复
rsync -avz $BACKUP_SERVER:$LATEST_WWW/ $WEB_ROOT/
echo "网站文件恢复完成"
}
recover_db() {
echo "恢复数据库..."
# 找到最新的数据库备份
LATEST_DB=$(ssh $BACKUP_SERVER "ls -t $BACKUP_DIR/mysql_*.sql.gz | head -1")
# 恢复
ssh $BACKUP_SERVER "gunzip -c $LATEST_DB" | mysql -u $DB_USER -p$DB_PASS $DB_NAME
echo "数据库恢复完成"
}
recover_config() {
echo "恢复配置文件..."
LATEST_ETC=$(ssh $BACKUP_SERVER "ls -t $BACKUP_DIR/etc_*.tar.gz | head -1")
scp $BACKUP_SERVER:$LATEST_ETC /tmp/
tar -xzvf /tmp/$(basename $LATEST_ETC) -C /tmp/restore/
cp /tmp/restore/etc/nginx/nginx.conf /etc/nginx/
echo "配置文件恢复完成"
}
health_check() {
echo "健康检查..."
# 检查Nginx
if systemctl is-active --quiet nginx; then
echo "Nginx: 正常"
else
echo "Nginx: 异常"
return 1
fi
# 检查PHP-FPM
if systemctl is-active --quiet php-fpm; then
echo "PHP-FPM: 正常"
else
echo "PHP-FPM: 异常"
return 1
fi
# 检查MySQL
if systemctl is-active --quiet mysqld; then
echo "MySQL: 正常"
else
echo "MySQL: 异常"
return 1
fi
# 检查Web访问
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost)
if [ "$HTTP_CODE" = "200" ]; then
echo "Web访问: 正常"
else
echo "Web访问: 异常 (HTTP $HTTP_CODE)"
return 1
fi
return 0
}
# ===== 主流程 =====
START_TIME=$(date +%s)
echo "========================================="
echo "自动化恢复开始: $(date)"
echo "========================================="
recover_config
recover_db
recover_web
# 重启服务
systemctl restart mysqld php-fpm nginx
# 健康检查
if health_check; then
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
echo "========================================="
echo "恢复成功!耗时: ${DURATION}秒"
echo "========================================="
else
echo "========================================="
echo "恢复失败!请检查日志"
echo "========================================="
exit 1
fi
18.12.6 演练频率建议
| 演练类型 | 频率 | 参与人员 | 说明 |
|---|---|---|---|
| 桌面推演 | 每月 | 运维团队 | 只在纸面上模拟,不实际操作 |
| 单系统恢复 | 每季度 | 运维团队 | 选一个系统实际恢复 |
| 全链路恢复 | 每半年 | 全体人员 | 模拟完整灾难场景 |
| 机房级演练 | 每年 | 全体人员+管理层 | 模拟机房整体故障 |
18.12.7 小结
灾难恢复演练的核心要点:
- 没做过演练的备份等于没有备份
- 制定完整的灾难恢复计划(DRP),包括RTO和RPO目标
- 设计真实的演练场景(单机故障、数据误删、机房故障)
- 每次演练后评估并改进
- 尽可能自动化恢复流程
- 定期演练,形成制度
全章总结
核心知识回顾
恭喜你读完了整章内容!让我们回顾一下本章的核心知识点:
18.1 故障排查方法论:像医生看病一样系统化排查,先收集信息再动手,一次只改一个地方,先备份再修改,分层排查。
18.2 系统无法启动排查:理解BIOS→GRUB→内核→systemd的启动流程,逐段排查。Live USB + chroot是万能修复手段。
18.3 文件系统损坏修复:fsck是主力工具,修复前必须卸载。超级块损坏可以用备份恢复。预防为主:UPS+日志文件系统+定期检查。
18.4 网络故障排查:分层排查(链路→网络→传输→应用)。ping/traceroute/mtr/ss/tcpdump是网络排障五件套。别忘了防火墙和SELinux。
18.5 性能问题诊断:四大瓶颈(CPU/内存/磁盘I/O/网络)。top看整体,vmstat看细节,iostat看磁盘,sar看历史。
18.6 内存泄漏排查:内存只增不减是典型表现。OOM Killer会自动杀进程。C/C++用valgrind,Java用jmap,Python用tracemalloc。
18.7 磁盘空间不足处理:df看整体,du看目录,find找大文件。删了文件但空间没释放用lsof查。inode耗尽用df -i查。
18.8 进程僵死与处理:僵尸进程杀父进程来清理,D状态进程要修复底层I/O。kill -15优雅退出,kill -9强制杀掉。
18.9 系统日志分析技巧:日志是破案的关键证据。journalctl是systemd日志的瑞士军刀。grep+awk+sort+uniq是日志分析黄金组合。
18.10 单用户模式与救援模式:单用户模式是"急诊室"。通过GRUB编辑进入,Live USB用于系统完全无法启动时。chroot是修复核心操作。
18.11 数据备份与恢复策略:3-2-1法则(3份数据/2种介质/1份异地)。tar/rsync/dd/mysqldump是四大备份工具。必须验证备份完整性。
18.12 灾难恢复演练:没做过演练的备份等于没有备份。制定DRP,设计真实场景,定期演练,持续改进。
故障排查速查表
最后,送你一份"故障排查速查表",遇到问题时可以快速查阅:
================= 故障排查速查表 =================
【系统无法启动】
1. 有显示吗? → 无:检查电源/硬件 → 有:继续
2. 有GRUB菜单吗? → 无:修复GRUB → 有:继续
3. 内核能加载吗? → 卡住/Kernel Panic:检查root参数/initramfs
4. 能到登录界面吗? → 不能:用systemctl --failed查服务
5. 终极武器:Live USB + chroot修复
【文件系统损坏】
1. fsck检查 → umount后执行fsck -y /dev/sdX
2. 超级块损坏 → mke2fs -n查备份位置,e2fsck -b恢复
3. 变只读了 → mount -o remount,rw / 或 fsck修复
【网络不通】
1. 网线/网卡:ethtool / ip link show
2. IP配置:ip addr show
3. 网关:ping 网关IP
4. DNS:nslookup 域名
5. 端口:telnet/nc 目标IP 端口
6. 防火墙:iptables -L / firewall-cmd --list-all
7. 抓包:tcpdump -i eth0
【系统变慢】
1. top看负载和CPU/内存大户
2. vmstat看swpd/wa/r列
3. iostat看%util和await
4. 流程:内存不足→swap→磁盘I/O飙高→系统变慢
【内存泄漏】
1. ps/pidstat监控RSS是否持续增长
2. C/C++:valgrind --leak-check=full
3. Java:jmap -histo + jstat -gcutil
4. OOM:dmesg | grep oom
【磁盘满了】
1. df -h看哪个分区满
2. du -sh /*找大目录
3. find / -size +1G找大文件
4. 删了没释放?lsof | grep deleted
5. inode满了?df -i检查
【进程问题】
1. 僵尸进程:杀父进程 kill -CHLD PPID 或 kill PPID
2. D状态进程:修复I/O问题,无法kill
3. 优雅退出:kill PID 或 kill -15 PID
4. 强制杀掉:kill -9 PID
【日志查看】
1. 系统日志:journalctl -u 服务名 --since "1 hour ago"
2. 错误过滤:journalctl -p err
3. 上次启动:journalctl -b -1
4. 实时监控:journalctl -f 或 tail -f
【数据恢复】
1. 有备份:从备份恢复
2. 误删文件被进程占用:/proc/PID/fd恢复
3. 数据库:mysqlbinlog按时间点恢复
4. 终极方案:专业数据恢复工具testdisk/extundelete
===================================================
写在最后
恭喜你完成了第18章的学习!这一章是整本书中最长、也最实用的一章。
故障排查与恢复是一项需要大量实践的技能。仅仅读懂本章内容是不够的,你需要在日常工作中不断积累经验。这里给初学者几条建议:
-
在自己的虚拟机上多做实验。故意制造一些故障(比如填满磁盘、杀掉关键进程、修改错误的配置),然后尝试自己排查和修复。虚拟机的好处是随时可以快照恢复,不怕搞坏。
-
养成查看日志的习惯。每次遇到问题,第一反应应该是去看日志,而不是去搜索引擎。日志中往往有最直接的线索。
-
记录每一次排障过程。不管问题大小,都记录下来。时间长了,你就拥有了一本属于自己的"运维手册"。
-
模拟灾难,练习恢复。定期在自己的环境中模拟数据丢失、系统崩溃等场景,练习恢复操作。只有练过了,真出事时才不会慌。
-
备份!备份!备份! 重要的事情说三遍。再好的故障排查技术,都不如有一份可靠的备份。记住:数据是无价的,硬盘是有价的。
希望这一章能成为你Linux运维之路上的"急救手册"。当系统出故障时,翻开它,冷静分析,你一定能找到解决方案。
"最好的故障排查,是预防故障的发生。"
愿你的服务器永远稳定运行,愿你永远用不上这一章的知识------但万一需要,你已经准备好了。