当前位置:首页>Linux>5个经典Linux深度问题:根因各不同

5个经典Linux深度问题:根因各不同

  • 2026-10-05 16:56:53
5个经典Linux深度问题:根因各不同

5个经典Linux深度问题:表面一个样,根因各不同

做运维这些年,最怕的不是报错,是"报错看着眼熟、按老经验一通操作、结果越搞越砸"。下面这 5 个问题,新手和老手都踩过,但真能讲清机制的没几个。每一个我都附了真实场景、排查命令、根因、止血和治本,以及怎么提前避开。


一、文件删了,磁盘还是满的:进程还攥着句柄

场景

有天晚上磁盘告警,/ 分区用到 95%。我 df -h / 一看用了 90G,可 du -sh /* 把所有目录加起来才 50G 出头。差的 40G 凭空消失了?

# df -h /
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda2       100G   90G   10G  90%

# du -shx /* 2>/dev/null
24G   /data
12G   /var
8G    /usr
... 全加起来不到 50G

df 和 du 对不上,这就是第一信号:有"看不见的文件"还占着空间。

排查

df 读的是文件系统超级块里的"已用块计数",实时更新;du 是顺着目录树一个个文件统计。两边不一致,基本就是"文件被删了,但还有进程开着它"。一句话定位:

lsof +L1 | head
# 或
lsof | grep deleted

+L1 意思是"link count 小于 1 的打开文件"——也就是目录项已经没了、但 fd 还开着的。果然抓到一个 java 进程:

java      18234  root  15w   REG  8,2  38765432100  /data/app/access.log (deleted)

一个 38G 的 access.log,早被 rm 了,但 java 还写着,空间一天没还。

根因(机制)

删除文件在 Linux 里只是 unlink:把目录项拿掉,inode 的硬链接数减 1。文件系统只在「硬链接数=0 且 打开计数=0」时才回收数据块。进程还开着 fd,打开计数 > 0,块就不还。df 按超级块记的已用块走(实时),du 顺着目录走(目录项没了,它数不到),所以 df 比 du 大。

解决

  • 止血(不重启):找到 fd 号,直接截断,空间立刻释放,进程继续往新位置写:  ```  # 上面 lsof 输出里 15w 的 15 就是 fd 号

    /proc/18234/fd/15  `` 或者用truncate -s 0 /proc/18234/fd/15`。这一下 38G 就回来了。

  • 治本:别对正在被写的日志直接 rm。用 logrotate 配 copytruncate(先拷再清空原文件,不需要 reload),或者让应用响应信号重新开文件(nginx -s reopen、Java 的 Log4j2 滚动)。

避坑

  • 删大文件前先 lsof +L1 确认没人开着;
  • 监控用 df 不要只盯 du;
  • 日志滚动一定要走应用能感知的方式,纯 rm 是给自己埋雷。

二、df 还有 200G,cp 却报 No space left on device:inode 耗尽了

场景

往 /data 拷个文件,直接炸:

$ cp app.tar /data/
cp: cannot create regular file '/data/app.tar': No space left on device
$ df -h /data
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1       500G  300G  200G  60%   # 明明还有 200G!

空间够,却写不进。新手第一反应是把磁盘扩了——纯浪费钱。

排查

df -i /data
Filesystem      Inodes   IUsed   IFree IUse% Mounted on
/dev/sdb1      3276800 3276800       0  100%   # inode 满了

df -i 一打,inode 100% 用完。块还有的是,但 inode 一个不剩,新文件建不出来。

根因(机制)

ext4/xfs 在 mkfs 时按磁盘大小和 inode ratio(ext4 默认每 16KB 一个 inode)预先分配固定数量的 inode。每个文件——哪怕内容只有 1 字节——都要吃掉一个 inode,但只占极少的 block。所以"海量小文件"能把 inode 池抽干,而磁盘块还空着一大半。我见过最典型的是 /var/lib/php/session/ 没人清理、邮件 spool、日志切割后残留的碎片文件。

解决

  • 定位谁占的 inode:  for d in /data/*/; do echo "$(find "$d" -xdev -type f 2>/dev/null | wc -l) $d" done | sort -rn | head  或者按一级目录聚合:  find /data -xdev -type f 2>/dev/null | awk -F/ '{print $2}' | sort | uniq -c | sort -rn | head
  • 止血:把确认无用的海量小文件批量删掉,inode 立刻回笼。
  • 治本:inode 数量是 mkfs 时定死的,扩容磁盘不会加 inode。要么重建分区时调小 inode ratio(mkfs.ext4 -i 8192 每 8KB 一个,小文件多就调密),要么——更现实——小文件别堆本地磁盘:session 放 Redis,附件放对象存储(FastDFS/MinIO),日志切割按大小并且删旧。

避坑

  • 监控要加一条 df -i,光看 df -h 发现不了;
  • session、缓存、临时小文件必须有过期清理(tmpwatch/tmpreaper、PHP gc);
  • 存储大量小文件的场景,分区时就把 inode ratio 算进去。

三、TIME_WAIT 几万个,连接建不上:你乱调内核参数反而更惨

场景

一个高并发短连接服务,每秒新建几千连接。跑着跑着客户端开始连不上,ss -ant | grep -c TIME_WAIT 一数几万。新手搜一圈,照着帖子把 net.ipv4.tcp_tw_recycle=1 开了——然后 NAT/容器后面的客户连接随机大面积超时,比之前还糟。

根因(机制)

TIME_WAIT 是主动关闭方进入的状态,固定持续 2MSL(Linux 默认 60 秒)。它不是 bug,是 TCP 正确性的两道保险:

  1. 保证最后那个 ACK 可靠到达——万一对方 FIN 重传,你能响应,连接干净关闭;
  2. 让网络里滞留的旧报文段在新连接建立前彻底消亡,避免串数据。

所以 TIME_WAIT 多,本质是"你主动关连接太频繁",比如 PHP 每次请求 new 一个 MySQL/Redis 连接不复用、HTTP 客户端不用连接池。

至于 tcp_tw_recycle 为什么是坑:它靠 PAWS(时间戳防回绕)快速回收 TIME_WAIT,但 NAT 后面所有客户端共用一个公网 IP、各自 timestamp 乱序,服务端一算对不上直接丢连接。4.12 之后内核干脆把它删了。开着它,客户随机掉线查半年查不到。

解决

  • 治本(唯一正解):连接复用。MySQL/Redis 用连接池、HTTP 用 keep-alive、PHP 用 pconnect。把短连接变长连接,TIME_WAIT 从源头减少。
  • 客户端安全参数:net.ipv4.tcp_tw_reuse=1——只对出向连接复用 TIME_WAIT 槽,安全,不影响入向。
  • 端口不够用:扩 net.ipv4.ip_local_port_range="1024 65535",让客户端有更多本地端口可分配。
  • 别动:tcp_tw_recycle(已移除且危险)、tcp_max_tw_buckets 调大只是延缓症状。

避坑

  • 永远别在生产碰 tcp_tw_recycle;
  • tcp_tw_reuse=1 只对 client 安全,服务端别指望它;
  • 真问题是连接模式,调参只是给破车换轮胎;
  • 监控 TIME_WAIT 数量趋势,暴涨就去看是不是谁在狂建短连接。

四、进程 kill -9 都杀不死,系统假死:D 状态卡在 I/O

场景

一台机器突然"假死":SSH 能连,但命令卡住半天不出。top 里有个进程 STAT 列显示 D,kill -9 发过去毫无反应,它还在那。

根因(机制)

D = uninterruptible sleep(不可中断睡眠,TASK_UNINTERRUPTIBLE)。进程卡在内核态等一个不能中断的 I/O:通常是块设备(磁盘坏道)、或者网络文件系统 NFS 的服务端没响应。信号(包括 SIGKILL)只在进程回到可运行/可中断态才被处理,D 态期间内核不理你,所以 kill -9 无效。常见触发:NFS 服务端 hang、网络存储超时配置不当、RAID 卡卡住、底层存储抽风。

排查

# 看进程卡在哪个内核函数
cat /proc/<pid>/stack

# 开 sysrq 后打印所有被阻塞的任务栈
sysctl kernel.sysrq=1
echo w > /proc/sysrq-trigger
dmesg | grep -A20 "Show Blocked State"

# 看是不是 NFS / I/O error
dmesg | grep -i "nfs\|I/O error\|task hung\|blocked"
# iowait 高不高
top   # 看 wa% 一列

dmesg 里要是看到 nfs: server xxx not responding, timed out 或者 task xxx blocked for more than 120 seconds,基本就坐实了。

解决

  • D 态进程只能等 I/O 回来或重启,信号杀不掉。
  • NFS 场景:把挂载选项从 hard 改成 soft,intr,timeo=30,retrans=3,超时后返回错误而不是无限等;卡死的挂载 umount -f 或 umount -l(lazy,等忙完卸)。
  • 磁盘场景:smartctl -a /dev/sdX 看坏道,该换盘换盘。
  • 最狠:真卡死连 reboot 都挂,只能 echo b > /proc/sysrq-trigger 强制重启(有数据风险,最后才用)。

避坑

  • 关键路径(数据库、高并发写日志)别挂不可靠的网络存储;
  • NFS 一律 soft + 明确 timeo/retrans,禁止 hard 无超时;
  • 监控 iowait 和 D 状态进程数,比等假死强。

五、Too many open files,但 ulimit -n 明明是 65535:systemd 的坑

场景

服务跑几天开始刷 Too many open files,新连接拒绝。你 ulimit -n 一查是 65535,够啊?改了 /etc/security/limits.conf 重启服务,还是 1024。

根因(机制)——三层限制,你改错层了

  1. 进程级:/proc/<PID>/limits 里的 Max open files,由启动它的 shell 的 ulimit -n 或 systemd 的 LimitNOFILE= 决定;
  2. 系统级:fs.file-max,内核全局上限(sysctl fs.file-max);
  3. systemd 经典坑:你在 limits.conf 写了 * soft nofile 65535,但服务是 systemd 起的——systemd 不走 PAM,根本不读 limits.conf,默认值还是 1024。必须在 .service 文件里加 LimitNOFILE=65535 才生效。这是 90% 人卡住的地方。

更深的坑:多数时候是 fd 泄漏——代码里打开文件/socket 没关(没放 finally/with),或者日志 handler 不释放。fd 只增不减,几天涨到上限。

排查

# 进程实际开了多少 fd
ls /proc/<PID>/fd | wc -l
# 它的上限是多少(看 Max open files 那行)
cat /proc/<PID>/limits | grep "Max open files"
# 系统级已分配/上限
cat /proc/sys/fs/file-nr
# 慢但全:lsof -p <PID> | wc -l

如果 ls /proc/PID/fd | wc -l 一直涨不回落,那就是泄漏,不是限额小。

解决

  • 止血(不改代码):prlimit --pid <PID> --nofile=65535:65535 临时把进程上限拉大;或者重启进程。
  • 治本:修代码关 fd(用 with/try-finally);把三层限制都改对——尤其 systemd 服务加 LimitNOFILE=65535;系统级 sysctl -w fs.file-max=1000000 写进 /etc/sysctl.conf。

避坑

  • 连接/文件用 with 或 try-finally 关,别指望 GC;
  • 监控 fd 数趋势,暴涨就是泄漏前兆;
  • 改完 limits 一定 cat /proc/PID/limits 验证生效,别只看 ulimit -n;
  • systemd 起的服务,limits.conf 没用,认准 LimitNOFILE。

速查表

问题 一眼信号 关键命令 根因一句话
删文件空间不释放 df > du lsof +L1 进程还开着已删文件的 fd
inode 耗尽 df -h 有空间,df -i 满 df -i / find ... \| wc -l 海量小文件抽干 inode 池
TIME_WAIT 堆积 连接建不上,ss 几万 TW ss -ant \| grep -c TIME_WAIT 短连接太频繁,不是参数问题
D 状态假死 kill -9 无效,STAT=D cat /proc/PID/stack / dmesg 卡在不可中断的 I/O(NFS/磁盘)
fd 耗尽 Too many open files ls /proc/PID/fd \| wc -l fd 泄漏 + systemd 不读 limits.conf

这 5 个的共同点:报错都像常识,根因都在机制层。能讲清"为什么"的人,排查才快;只会背命令的,迟早在第三个问题上把生产搞挂。

最新文章

随机文章