☰
Linux命令实战:磁盘告警、服务器变慢与日志分析排查链路
2026/10/20 5:05:07 网站建设 项目流程

凌晨两点半,某台业务服务器的磁盘空间告警钉钉一直在响。我当时第一反应就是先登上机器执行df -h,紧接着du -sh /data/*一层层剥开目录,最后定位到某个应用日志目录下躺着一个 60 多 G 的 debug 日志文件,从文件末尾时间戳往前一推,发现已经连续写了一个半月没人管。整个过程不到三分钟,靠的正是几条再普通不过的 linux 命令。

linux命令这东西,说简单真不简单——它不是靠背出来的,而是靠一次次真实排查、处理问题锤出来的。网上各种命令大全、手册一搜一大把,但真遇到问题的时候,能条件反射般组合起来救命的,永远是你真正用过、踩过坑的那几条。

这篇文章我不打算再罗列什么“常用命令清单”,那玩意儿你收藏了也不会看。我想换个角度,专门聊聊命令行背后的底层逻辑、排查链路,以及我在真实环境里总结出来的一些经验和教训。无论你是刚入行的运维、被服务器折腾的后端开发,还是想系统提升效率的测试同学,这篇内容应该都能让你少走一些弯路。

1. 背了几百条命令还是不会用——问题不在记忆力

很多朋友学 linux 命令时有个误区:买了一堆书,或者收藏了无数篇“必学命令大全”,把ls、cd、cat、grep、awk、sed背得滚瓜烂熟,但真到了服务器上,脑子依然一片空白。

这不是记忆力的问题,而是学习路径的问题。命令本身不是一个独立的知识点,它是你解决问题的工具。就像你家里的工具箱,螺丝刀再好,你不会拆水龙头,照样修不了漏水。

1.1 用场景驱动代替参数堆砌

正确的方式是“场景驱动”。你不需要记全一条命令的三十个参数,你需要的是:在什么场景下,用哪几条命令组合起来,能最快定位和处理问题。

举一个很典型的例子。有个同事问我说:“为什么我的tar命令解压以后,文件都跑到当前目录外面去了?”我看着他说:你八成是没加-C指定目标目录,解压出来的相对路径直接撒在根目录下了。这个问题不是命令记不住的问题,而是他根本没意识到“解压到指定目录”本身是一个高频场景,应该养成本能。

我的建议是每次学命令前都先问自己四个问题:

  • 这个命令解决的是哪一类问题?(磁盘、网络、进程、日志?)
  • 它最常用的输出里有哪几个关键字段?(别全看,只看重点)
  • 它和哪几个命令通常会配合使用?
  • 我上次遇到类似问题的时候,是怎么排查的?

当你用这种方式去“攒经验”,你会慢慢建立起一套自己的排查习惯,而不是每次遇到问题都在搜索引擎里捞命令。

1.2 一条命令能搞定的事,别用两条

很多新手喜欢“表演式”写命令——用一堆管道、复杂参数,看起来很高大上,其实效率反而更低。经验老到的工程师通常反着来:先求能跑通,再求简洁。

比如想找/var/log下今天修改过且超过 100M 的日志文件,新手可能会说:

find /var/log -type f -name "*.log" | xargs ls -l | awk '$5 > 104857600 {print $9}'

看着很炫,但有一个明显的问题:一旦文件名里有空格,xargs ls这层就能把整个命令链带崩。换成工程师常用的写法:

find /var/log -type f -name "*.log" -size +100M -mtime -1 -printf "%s %p\n"

一招到位,还不用走管道,也没空格问题。这个差异说明一条核心原则:命令越少越好,管道越短越好,每一步都要考虑容错。

所以在正文开始之前,先把这个底层思路明确下来。接下来我分享的所有排查链路,都是从这个思路出发设计的。

2. 磁盘告警排查链:从告警短信到揪出大文件的完整动线

磁盘满是个特别经典也非常高频的问题。开头提到的凌晨告警,其实只是日常一景。很多人在df -h看到100%之后就开始慌了,看哪哪都像是罪魁祸首,最后一顿乱删,甚至误删了系统文件。这种时候最忌讳的就是“乱”,得有条理地一层层剥下去。

2.1 先用 df 看整体,分清楚是哪一层满了

第一步永远是df -h,看整个文件系统的挂载点和使用率:

df -h

输出大致长这样:

文件系统 大小 已用 可用 已用% 挂载点 /dev/vda1 20G 19G 1.2G 95% / tmpfs 3.9G 2.1G 1.9G 54% /dev/shm /dev/vdb1 200G 150G 50G 75% /data

看到/dev/vda1 95%,就明确了两件事:一是要清理的分区是根分区;二是这台机器的数据盘/data是独立的,磁盘空间告警很可能和业务数据没关系,而是系统盘里的小文件占了地方。

这里有个新手容易忽略的坑:df -h默认不显示 inode 使用情况。文件系统满了不只是“容量满了”,还有一种情况是“索引节点满了”,也就是小文件数量太多,每个文件都会消耗一个 inode,把整个文件系统的 inode 耗尽后,系统同样会提示“No space left on device”,哪怕可用容量还有很多。

遇到这种怪现象,执行:

df -i

如果 IUse% 接近 100%,那问题就变成了“找哪来的一堆小文件”,而不是“哪来的大文件”。处理方式侧重点完全不同。

2.2 用 du 顺藤摸瓜,从大到小找目录

确认磁盘满了之后,核心目标就是“找到大目录”。我习惯的做法是从根目录开始逐层往下“砍”:

du -sh /* 2>/dev/null | sort -hr

这条命令的作用是列出根目录下每个一级目录的占用大小,并排序,2>/dev/null 把权限拒绝无所谓的报错丢到一边,不打断输出。

输出里一眼能看到占用最大的目录,比如/var占了 10G。接着继续:

du -sh /var/* 2>/dev/null | sort -hr

依次往下,三级、四级目录一层层定位。这个“逐层剥洋葱”的过程看起来笨,但它是最稳的排查方式。生产环境里别用du -h /var这样的写法去扫整个目录树,目录一深、文件一多,du半天跑不完不说,还会额外消耗磁盘 IO,直接把你正在排查的服务器拖得更慢。

命令写法适用场景风险
du -sh /var只看总量无大额外耗时
du -h /var想看全部子目录文件多时非常慢
du -h --max-depth=1 /var只在快速定位下一层折中方案,推荐

用--max-depth=1而不是全量递归,是经验之谈。这样既不用一层层重复敲命令,又不会让du因为递归过深而卡死。

2.3 定位到文件后,别急着删,先确认是什么在写

假设你最终找到了一个 60G 的日志文件/data/app/log/app.log。这时候直接rm掉可能暂时解决问题,但你会发现过几天又满了。因为写这个日志的服务还在运行,文件句柄依然被进程持有,日志会继续往那个已被删除的文件里写,磁盘空间根本释放不出来。

正确做法是先看一眼谁在写它:

lsof +L1 | grep app.log

这条命令会列出所有已经被删除到只剩下句柄的文件,以及持有它的进程。看到对应 PID 之后,再决定是重启服务让句柄释放,还是先停服务、清空文件、再启动。

如果服务负载高不能随便重启,另一个替代方案是:

cat /dev/null > /data/app/log/app.log

相当于把文件内容直接清空而不释放文件本身,正在写的进程毫无感知,空间却立刻释放了。

2.4 查找大文件的高效配方

如果定位方向已经明确,目标就是“找某个目录下超过 1G 的文件”,用find直接搜:

find /data -type f -size +1G -exec ls -lh {} \; 2>/dev/null

把-size +1G换成-size +500M、-size +100M可以自由调整阈值。注意-exec ls -lh {} \;结尾的分号在 bash 里需要转义,如果不加,find 会报语法错误。这台命令能在几秒内把目标目录下所有庞然大物列出来,比用du一层层找快得多。

磁盘排查到这里,一个大致的完整链路就清晰了:

  1. df -h判断容量还是 inode 问题。
  2. du --max-depth=1逐层找大目录。
  3. find -size +1G直接定位大文件。
  4. lsof +L1发现被删除但仍占空间的句柄。
  5. 根据实际情况选择清空、重启或删树。

这一套流程走下来,效率极高,也极少误伤。磁盘空间告警几乎每天都有,但真正因为不会排查而把服务器搞挂的案例,我见过太多了,说到底就是没有建立起“从整体到局部”的排除思路。

3. 服务器变慢时的三板斧:进程、内存、IO 的组合排查

如果说磁盘告警是“显性病”,那服务器突然变慢就是“隐性病”,查起来更费劲。一个进程把 CPU 打满、内存吃光、或者磁盘 IO 飙升,症状都一样:用户访问变慢,接口超时。但病因千差万别。

我遇到过不少开发同学,一上来就top,看到某个 CPU 100% 就断定“这个进程出问题了”,其实未必。也可能是这个进程在等待 IO,CPU 空转把负载拉高了。得靠一套组合诊断才能下结论。

3.1 用 ps 找“嫌疑进程”,而不是用 top 翻来翻去

第一板斧,我喜欢用ps而不是top来快速抓嫌疑对象:

ps aux --sort=-%cpu | head -n 15

这行命令把当前所有进程按 CPU 占用排序,直接取前 15 个。输出里的关键字段不需要全看,重点关注:

  • %CPU:占了多少 CPU
  • %MEM:占了多少物理内存
  • STAT:进程当前状态

STAT里的状态字符很多人不理解,简单列几个常见值:

状态字符含义常见场景
R正在运行或可运行高负载计算任务
S可中断睡眠正常的等待事件
D不可中断睡眠通常意味着等待磁盘 IO
Z僵尸进程父进程未回收子进程资源
T停止被信号暂停

看到D状态,就要怀疑是不是磁盘 IO 出了问题;看到Z,重点就得放在它父进程上,看看是不是父进程有 bug 没接住子进程的退出。

3.2 top 的交互操作远比你想象的强大

ps抓到了头号嫌疑进程的 PID,接下来用top -p PID单独盯着它看:

top -p 12345

进入交互界面后,有几个按键必须会:

  • P:按 CPU 占用排序(大写)
  • M:按内存占用排序(大写)
  • k:杀掉选中进程
  • c:显示完整命令行
  • H:切换线程视图

按c展开完整命令行特别实用。有一次我在某台机器上看到有个进程 CPU 一直波动,但它的命令名很正常,后来按下c才发现启动参数里挂了个可疑的脚本路径。很多时候,问题不在进程名,而在于启动参数里藏着的脚本或配置。

3.3 free 的输出要会翻译,Buffer/Cache 不是“已用”

内存问题是新手的重灾区。每次一看free -h发现可用内存只有几百兆,就急得想去 kill 进程。实际上free的输出要正确理解:

free -h

输出中available这一列才是真正可用的内存,它已经把可回收的 cache 算进去了。Linux 内核的设计理念是“闲着也是闲着”,空闲内存会尽量用于缓存文件数据,以此来提升读写性能。当你看到内存“被占满”,其实很大一部分是 buffer/cache,它们会在应用真正需要内存时自动释放。

真正该注意的是 swap 的使用情况。如果si/so在持续增长,说明内存真的不够用了,系统在频繁换页,这种场景下面的性能会严重下降。

3.4 用 vmstat 看“整体忙不忙”和 IO 是否瓶颈

ps、top是把镜头对准单个进程,vmstat则是看全局:

vmstat 2 5

意思是每隔 2 秒采样一次,一共采 5 次。输出重点看三块:

  • r:运行队列长度,持续大于 CPU 核心数说明 CPU 忙不过来
  • wa:CPU 等待 IO 的时间占比,明显偏高说明磁盘是瓶颈
  • us、sy:用户态和内核态 CPU 占用

有一次某台机器wa高达 30%,但每个进程 CPU 占用都不高,这就是典型的磁盘 IO 瓶颈。当时查下来是一个业务在批量导入数据,把磁盘打满了,通过iostat -x 1能看到对应的/dev/vdb1的%util接近 100%。这种情况下你哪怕把进程 kill 了,其他进程也一样跑不快,因为根子在磁盘。

所以这“三板斧”的正确使用顺序是:ps找嫌疑进程 →top看细节 →free+vmstat判断是内存、CPU 还是 IO 的问题。顺序别乱,一乱就容易误判。

4. 日志分析里的组合拳:grep、awk、sed 怎么配合出奇迹

聊完系统问题,再聊一个更高频的场景——分析日志。无论你是做后端、运维还是数据分析,只要和服务器打交道,日志就是第一手的真相来源。

但日志文件动辄几百 MB、几个 G,用鼠标双击打开是不可能的。这时候grep、awk、sed这三把刀就能派上用场。

4.1 grep:先把需要的行捞出来

grep的核心作用只有一个:按条件过滤行。最基本的用法是搜关键词:

grep "ERROR" /data/app/log/app.log

但生产环境里的日志格式通常很规整,比如:

2025-06-12 10:23:45 ERROR [OrderService] 下单失败: timeout 2025-06-12 10:23:47 INFO [UserService] 查询用户成功 2025-06-12 10:24:01 ERROR [PaymentService] 支付回调超时: 3000ms

想找出所有下单失败的记录,可以直接组合多个关键词:

grep "下单失败" app.log

想同时排除掉某个干扰关键词,用-v反向过滤:

grep "ERROR" app.log | grep -v "健康检查"

想不区分大小写,加-i。想做正则,加-E。日常高频用的无非这几个参数,别去死记那种几十种参数的表格,工作中用到哪个查哪个,形成条件反射的始终是最常用的那几种。

4.2 awk:按列提取和简单统计

grep输出的是整行,有时候我们需要“只取某个字段”。用awk按空格或制表符分割就能轻松实现:

awk '{print $1, $2, $4}' app.log

但更让我觉得惊艳的是 awk 的统计能力。假设日志每行末尾都记录了一个耗时毫秒数,像这样:

2025-06-12 10:23:45 INFO [OrderService] 下单成功 235ms 2025-06-12 10:24:01 INFO [OrderService] 下单成功 892ms 2025-06-12 10:25:12 INFO [OrderService] 下单成功 456ms

想统计所有“下单成功”的平均耗时,一行搞定:

grep "下单成功" app.log | awk '{sum += $7; count++} END {printf "avg=%.2fms, total=%d\n", sum/count, count}'

先grep把行筛出来,再awk把第 7 列的值累加,最后在END里求平均值。如果你只想看耗时超过 500ms 的慢请求,再加个条件:

awk '$7 > 500 {print $0}' app.log

awk 语句里的$NF代表最后一列,用$(NF-1)可以取倒数第二列。这个比死记住“第7列”更灵活——日志格式一旦变动,列数不会跟着乱套。

4.3 sed:批量替换和不忍直视的危险操作

sed是处理行的利器,最常见的用法是替换:

sed 's/ERROR/WARN/g' app.log

但这只是把替换结果显示在屏幕上,并不会修改原文件。想真正改文件,加-i:

sed -i 's/ERROR/WARN/g' app.log

这里必须说一个血泪教训:-i在线上环境一定要慎用,最好先用不带-i的命令把结果预览一遍,确认没问题再加-i。如果非要原地替换,建议后面加个.bak先备份:

sed -i.bak 's/ERROR/WARN/g' app.log

这样执行后会自动生成一个app.log.bak,万一替换错了还能救回来。

另外,sed还有一个高频场景是删除指定行的内容,比如删掉日志里前 100 行:

sed '1,100d' app.log

注意我写的是'1,100d',不是'1,100 h'——d代表删除,很多新手会把 d 和 h 弄混,导致输出一堆空行。

4.4 三合一:日志聚合分析的一条龙配方

实际工作中,单用一个工具往往不够痛快,把它们组合起来能解决很复杂的需求。

比如我想从一段日志里分析出:“每天哪个接口的平均响应时间最长?”可以这样操作:

grep "接口" app.log | awk '{print $6, $NF}' | awk -F'ms' '{print $1}' | awk '{time[$1] += $2; count[$1]++} END {for (api in time) print api, time[api]/count[api]}' | sort -k2 -nr | head -n 10

这一串看着多,其实是把一个复杂的分析拆成了四步:

  1. grep "接口"捞到所有相关日志行。
  2. 第一段先取出接口名和耗时字段。
  3. 把耗时字段结尾的ms去掉变成纯数字。
  4. 按接口名分组求和、计数、算平均,最后按平均值降序取前 10 名。

日常分析接口性能、定位慢查询、统计订单失败量,几乎都能用这套组合拳搞定。学习这些命令的重点不在于记住复杂写法,而在于培养一种“结构化拆解”的能力——先把需求拆成几段,每段交给最擅长的那条命令去完成。

5. 命令越熟练,手越要稳:权限、软链与“慢操作”哲学

如果说前面几节是讲“怎么查”,那这一节讲的是“怎么改”。查错了顶多白费功夫,改错了就有可能导致线上事故。干了这么多年,我对“手稳”这件事的理解,比什么都深。

5.1 chmod 和 chown 容易踩的坑

权限操作最容易踩的坑是“给了不该给的权限”和“把权限改成了错误的所有者”。

我们常说 755、644,很多新手会问这串数字到底是什么。简单解释:

  • 权限分三段:所有者、所属组、其他人
  • 每段的值是 r(读)=4、w(写)=2、x(执行)=1 之和

所以 644 对文件来说是rw-r--r--,即所有者可以读写,组和其他人只能读;755 对目录来说是rwxr-xr-x,即所有者有完全权限,其他人只能进入和查看,不能创建或删除文件。

写代码和部署脚本时,最安全的原则是:尽量给最小的权限,目录用 755,文件用 644,只有明确需要执行的文件才加 755,别动不动chmod -R 777。真实线上环境里,我见过有人图省事给 Web 目录递归赋了 777 权限,结果被黑客上传了恶意脚本,整个站被人提权,教训极其惨痛。

chown同样是高风险区。把某个文件的所有者改错了,可能导致服务启动失败或者没有权限写日志。一个稳妥的检查方式是改完立刻用ls -l验证:

ls -l /data/app/config.yml

别小看这一条验证命令,它可以避免掉 90% 以上的“权限改错后半天找不到原因”的窘境。

5.2 软链接:部署回滚的保命符

ln -s是一条很简单的命令,但用好它,能在发布时少挂好多次。

我们的常规做法是发布系统时建好目录结构:

ln -s /data/app/releases/current /data/app/current

代码都在releases/20250612这种带时间戳的目录里,current始终是指向最新版本的软链接。发布新版本时,先复制完整代码到新目录,然后重新指向current,再配一个验证流程。

一旦新版本出了问题,回滚只需要一行:

ln -sfn /data/app/releases/20250530 /data/app/current

把软链接重新指回上一个版本目录,服务立刻恢复到旧版本。这里的-f是强制替换,-n是防止在已有目录链接的情况下产生嵌套链接,必须带上,否则第一次能成功,第二次会把链接变成一个目录嵌套,导致整个发布工具被玩坏。

这招对发布系统、配置文件切换、环境变量管理几乎是万能灵药。遇到需要“切换版本”的场景,优先考虑软链接,而不是直接覆盖文件。

5.3 rm 的自我修养:别练“快”,要练“慢”

聊权限和链接,自然绕不开删除。rm -rf是很多人的噩梦,也是每个运维说起来都后背发凉的命令。

我最想强调的一点是:在线上环境里,不要追求删得快,要追求删得稳。我见过不少年轻人用rm -rf /data/app/old/很顺手,结果某次把路径敲成了rm -rf /data/app/,整个应用目录灰飞烟灭。

我自己养成的习惯是:

  1. 先ls确认要删的路径内容对不对。
  2. 输入完路径之后,把整条命令念一遍,屏幕上的路径和脑中的意图对上再回车。
  3. 第一次删东西时,先加个-i交互确认,让系统逼你再想一遍。
  4. 能“移动”就不要“删除”,改成mv到回收目录,等几天确认没问题再清理。
mv /data/app/old /data/app/trash/20250612

这条命令和rm -rf相比只是一个字母的差别,但容错率高得多。犹豫的时候就选mv,确定不要了才rm。听起来很啰嗦,但踩过坑的人都知道,所有的事故,都起源于手比脑子快。

6. 让命令“长在手上”:别名、历史记录与自定义函数

前几节讲的都是单次执行的技巧,真正让 linux 命令变成自己“贴身工具”的,是环境里的习惯沉淀。我见过一些效率极高的同学,他们在终端里几乎不重复输入超过两个字的常用命令,因为在配置层面早就把这些事情解决了。

6.1 alias:把长命令缩成一个单词

最常用的是配置别名。打开~/.bashrc,加几行:

alias l='ls -lh' alias ll='ls -alF' alias du='du -sh' alias untar='tar -zxvf' alias rmgit='git clean -fd' alias dd='df -h'

保存后执行source ~/.bashrc生效。从此以后,输入l就能看到人类可读的详情列表,输入dd就能看磁盘。别小看这些小节省,一次少敲几个字母,一天能省几百次键盘敲击,长期积累下来的效率提升相当可观。

定义别名时有一个原则:别覆盖系统自带命令的原有意涵。比如别把cp别名成cp -i,虽然看起来更安全,但某天你在脚本里用cp时,意识不到它背后还带着-i,就会多出大量无谓交互,甚至脚本卡死。要方便就另起一个名字,别去污染原命令。

6.2 history:让历史记录变成你的第二大脑

history命令太强了,很多人却只用它来看刚才打了什么。

我最常用的四个操作:

  • !!:执行上一条命令
  • !$:上一条命令的最后一个参数
  • !string:执行最近一条以 string 开头的命令
  • Ctrl+R:反向搜索历史命令

比如刚才执行了kill -9 12345,想再杀一个 12346,可以这样:

kill -9 !!:k

这个组合看起来高级,但日常更实用的操作是Ctrl+R反向搜索。输入几个字母,终端会帮你匹配历史记录里最接近的命令,一键回车执行。长时间不用某些命令时,这个搜索功能几乎是我在终端里最有安全感的存在。

6.3 自定义 shell 函数:把复杂操作固化下来

比 alias 再进一步的是自定义函数。比如我有一个用于查看端口占用情况的习惯:先netstat -tlnp看到哪个端口被占,再根据 PID 去ps -ef里找进程。

这个操作其实可以被封装成一个函数:

find_by_port() { port=$1 netstat -tlnp 2>/dev/null | grep ":$port" pid=$(lsof -t -i :$port 2>/dev/null) echo "PID: $pid" ps -p $pid -o pid,cmd --no-headers 2>/dev/null }

把这个函数写进~/.bashrc,自定义函数和 alias 一样,保存后用source ~/.bashrc生效。以后输入find_by_port 8080,终端就会把占用 8080 端口的进程和 PID 完整列出来。

这个函数本身并不复杂,但它代表了一种工作方式:把重复三遍以上的操作,固化成自己的工具。长此以往,你的命令行使用体验会从“每次输入一串陌生命令”变成“像在操作自己的贴身工具”,又稳又快。

6.4 通配符和 Tab 补全:别当打字员

还有一个经常被忽视的效率点:表格补全。Bash 里按下 Tab 能自动补全命令名、路径、文件名。很多人总觉得这个太基础了没必要练,但实际上,在cd /data/app/releases/下面有六个版本号目录,想进入某历史目录,只需要输入cd /data/app/releases/2025然后按下 Tab,剩下的部分 shell 自动补全,根本不用一个个字符去敲。

通配符同样高效。清理所有.tmp文件:

rm -f /tmp/xx_*.tmp

但通配符也有风险——如果目录下没有匹配项,shell 会直接把xx_*.tmp原样传给命令,导致操作落到一个不存在的路径上。所以凡是涉及删除的通配符命令,我都建议先执行一遍:

ls /tmp/xx_*.tmp

看到真实列出的文件再执行上一条删除命令。命名的课程果然很值钱,值得养成习惯。

写在最后的一些体会

聊了这么多,其实最想说的一句话是:linux 命令不是“背”出来的,也不是“炫”出来的,它是解决问题的工具箱。不同的环境、不同的故障、不同的日志格式,永远有更适合的组合方式。今天分享的排查链路和习惯沉淀,都是我在实际工作中反复锤打后留下来的套路,希望能给你带来一些触发。下次再遇到磁盘告警、服务器变慢、日志分析困难的时候,不妨先把文章里的这套思路在脑子里过一遍,大概率会让你的排查过程顺畅很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询