找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

6027

积分

0

好友

771

主题
发表于 4 天前 | 查看: 9| 回复: 0

一、问题背景

在 Linux 运维中,经常遇到这样的场景:磁盘空间告警触发, df -h 显示某个分区使用率已达 90% 以上,但用 du -sh /* 统计各目录占用空间时,加起来的总和远小于 df 显示的已用空间。更诡异的是,明明删除了几十 GB 的日志文件, df 显示的可用空间却没有增加。

这种现象在生产环境中非常常见,尤其是以下场景:

  1. 应用日志持续写入,日志文件被删除后空间未释放
  2. 数据库 binlog、redo log 被删除,但空间仍然占用
  3. 容器或进程 crash 后留下僵尸文件描述符
  4. 文件系统元数据占用大量空间
  5. 挂载点覆盖导致底层文件不可见

如果不理解这些现象背后的原理,很容易做出错误的判断和操作,比如:

  • 误以为是磁盘硬件故障,申请更换硬盘
  • 反复删除文件但空间不释放,最终只能重启服务
  • 强制 umount 或 reboot,导致数据丢失或服务中断

本文从文件系统原理出发,系统梳理 dudf 不一致的常见原因、排查方法、解决方案和预防措施。

二、适用场景

本文适用于以下运维场景:

  1. 磁盘空间告警,但找不到占用空间的大文件
  2. 删除文件后,df 显示的可用空间未增加
  3. du 统计的空间总和小于 df 显示的已用空间
  4. 应用日志、数据库日志被删除,但空间未释放
  5. 需要理解 Linux 文件系统的 inode、文件描述符、挂载点机制

本文的排查方法和命令适用于常见 Linux 发行版 (CentOS、Ubuntu、Debian) 和常见文件系统 (ext4、xfs)。

三、核心知识点

3.1 du 和 df 的区别

du (Disk Usage):

  • 统计文件和目录占用的磁盘空间
  • 通过遍历目录树,读取每个文件的大小 (st_size)
  • 只统计当前可见的文件
  • 单位:KB、MB、GB

df (Disk Free):

  • 统计文件系统的整体使用情况
  • 通过读取超级块 (superblock) 获取文件系统的总大小、已用大小、可用大小
  • 包括所有已分配的 block,无论文件是否可见
  • 单位:KB、MB、GB,也显示 inode 使用情况

关键区别:

维度 du df
数据来源 遍历目录树,统计文件大小 读取文件系统超级块
统计范围 当前可见的文件 所有已分配的 block
是否包括已删除但未释放的文件
是否包括文件系统元数据
性能 慢 (需要遍历目录) 快 (读取超级块)

3.2 Linux 文件删除机制

Linux 文件系统使用 inode 存储文件元数据,文件内容存储在 data block 中。

文件删除的两个条件:

  1. 硬链接计数为 0 :没有目录项指向该 inode
  2. 文件描述符计数为 0 :没有进程打开该文件

只有两个条件同时满足,文件系统才会真正释放 inode 和 data block。

典型场景:

# 进程A打开文件
exec 3< /var/log/app.log

# 进程B删除文件
rm /var/log/app.log

# 此时du不统计该文件(目录项已删除)
# 但df仍然统计该文件(进程A持有文件描述符)

只有进程 A 关闭文件描述符 (或进程退出),空间才会真正释放。

3.3 文件系统元数据开销

文件系统需要额外的空间存储元数据:

ext4 元数据:

  • 超级块 (superblock):存储文件系统基本信息
  • inode 表:存储文件元数据 (权限、所有者、时间戳、block 指针)
  • block bitmap:标记哪些 block 已使用
  • inode bitmap:标记哪些 inode 已使用
  • 日志 (journal):存储文件系统操作日志

xfs 元数据:

  • AG (Allocation Group):xfs 将磁盘分成多个 AG,每个 AG 有独立的元数据
  • inode B+树:存储 inode 分配信息
  • extent B+树:存储文件数据块分配信息

元数据开销通常占总容量的 1%-5%,对于小文件很多的场景,开销更大。

3.4 挂载点覆盖

如果在一个已有文件的目录上挂载新的文件系统,原目录中的文件会被"隐藏", du 无法统计,但 df 仍然统计原文件系统的空间。

示例:

# /data目录下已有100GB文件
ls -lh /data
# total 100G

# 在/data上挂载新磁盘
mount /dev/sdb1 /data

# 此时du统计的是新磁盘的使用情况
du -sh /data
# 0

# 但原文件系统(/)的df仍然统计那100GB
df -h /
# 已用空间包括被覆盖的100GB

3.5 稀疏文件

稀疏文件 (sparse file) 是指文件中有大量空洞 (未分配实际 block) 的文件。

创建稀疏文件:

# 创建一个10GB的稀疏文件
dd if=/dev/zero of=sparse.img bs=1 count=0 seek=10G

# du显示实际占用空间(接近0)
du -h sparse.img
# 0

# ls显示文件大小(10GB)
ls -lh sparse.img
# 10G

# df统计实际占用的block
df -h .
# 只增加了inode,几乎不占用block

虚拟机镜像、数据库文件常使用稀疏文件节省空间。

四、常见原因与排查

4.1 原因1:已删除文件仍被进程打开

现象:

# 删除日志文件
rm /var/log/app.log

# df仍然显示空间已用
df -h /var
# 使用率90%

# du统计不到该文件
du -sh /var/log
# 总计80GB,但df显示90GB

根因:

应用进程仍然持有该文件的文件描述符,文件 inode 和 data block 未释放。

判断方法:

# 查看哪些进程持有已删除文件的描述符
lsof | grep deleted

# 输出示例
java  12345  root  3w  REG  8,1  10737418240  /var/log/app.log (deleted)

关键字段解释:

  • 3w :文件描述符编号 3,w 表示以写模式打开
  • REG :普通文件
  • 8,1 :设备号
  • 10737418240 :文件大小 (10GB)
  • (deleted) :文件已被删除

解决方法:

方法1:重启进程 (推荐)

# 找到进程PID
lsof | grep deleted | awk '{print $2}' | sort -u

# 重启进程
systemctl restart app.service

方法2:清空文件描述符 (不重启进程)

# 找到文件描述符路径
lsof -p 12345 | grep deleted
# java  12345  root  3w  /var/log/app.log (deleted)

# 清空文件内容
: > /proc/12345/fd/3

# 或使用truncate
truncate -s 0 /proc/12345/fd/3

注意:

  • 清空文件描述符后,文件内容丢失,但空间立即释放
  • 应用可能因为日志丢失而出现异常,需要评估影响

统计已删除但未释放的总空间:

# 统计所有已删除文件占用的空间
lsof | grep deleted | awk '{sum+=$7} END {print sum/1024/1024/1024 " GB"}'

4.2 原因2:文件系统元数据占用

现象:

# df显示使用90%
df -h /var
# 已用900GB,可用100GB

# du统计只有850GB
du -sh /var
# 850GB

# 差异50GB是元数据开销

根因:

文件系统元数据 (inode 表、block bitmap、journal) 占用空间。

判断方法:

# 查看inode使用情况
df -i /var

# 输出示例
Filesystem  Inodes    IUsed   IFree  IUse%
/dev/sda1   62914560 55000000 7914560  88%

如果 inode 使用率很高,说明有大量小文件,元数据开销大。

查看文件系统元数据占用:

# ext4
dumpe2fs /dev/sda1 | grep -E "Block count|Free blocks|Inode count|Free inodes"

# 输出示例
Block count:              262144000  # 总block数
Free blocks:              26214400   # 可用block数
Inode count:              62914560   # 总inode数
Free inodes:              7914560    # 可用inode数

计算元数据开销:

  • 总容量 = Block count × Block size (通常 4KB)
  • 实际可用 = 总容量 - 元数据开销
  • 元数据开销 ≈ 总容量 - (Free blocks × Block size + 已用文件大小)

解决方法:

方法1:清理小文件

# 找出小文件最多的目录
find /var -type f -size -1k | awk -F/ '{print $2}' | sort | uniq -c | sort -rn | head -10

# 删除不需要的小文件(如临时文件、缓存)
find /var/tmp -type f -mtime +7 -delete

方法2:调整文件系统参数 (重新格式化时)

# ext4减少inode数量
mkfs.ext4 -i 65536 /dev/sdb1  # 每64KB分配一个inode(默认16KB)

# xfs调整inode大小
mkfs.xfs -i size=512 /dev/sdb1  # inode大小512字节(默认256)

4.3 原因3:挂载点覆盖

现象:

# 在/data已有文件的情况下,挂载新磁盘
mount /dev/sdb1 /data

# du统计新磁盘
du -sh /data
# 10GB

# df统计根文件系统(包括被覆盖的文件)
df -h /
# 已用空间包括原/data下的100GB

根因:

挂载点覆盖后,原目录中的文件不可见,但仍然占用空间。

判断方法:

# 查看挂载点
mount | grep /data
# /dev/sdb1 on /data type ext4 (rw)

# 卸载挂载点
umount /data

# 查看原目录内容
ls -lh /data
# total 100G

# du统计原目录
du -sh /data
# 100GB

解决方法:

方法1:清理被覆盖的文件

# 卸载挂载点
umount /data

# 清理原目录
rm -rf /data/*

# 重新挂载
mount /dev/sdb1 /data

方法2:使用 bind mount 避免覆盖

# 创建新目录
mkdir /data_new

# 挂载到新目录
mount /dev/sdb1 /data_new

# 使用bind mount
mount --bind /data_new /data

检查所有挂载点是否覆盖:

# 查看所有挂载点
mount | grep -v "^proc\|^sysfs\|^devpts\|^tmpfs" | awk '{print $3}' | while read mp; do
echo "=== Checking $mp ==="
# 临时卸载并检查
if umount "$mp" 2>/dev/null; then
du -sh "$mp"
    mount "$mp"
fi
done

注意: 此操作会短暂卸载挂载点,生产环境需在维护窗口执行。

4.4 原因4:稀疏文件

现象:

# 创建稀疏文件
qemu-img create -f qcow2 vm.img 100G

# ls显示100GB
ls -lh vm.img
# 100G

# du显示接近0
du -h vm.img
# 196K

# df统计实际占用
df -h .
# 几乎未增加

根因:

稀疏文件只分配了文件头部的 metadata block,数据 block 未实际分配。

判断方法:

# 查看文件的实际block数
stat vm.img | grep Blocks
# Blocks: 8  # 只分配了8个block(8 × 512B = 4KB)

# 对比文件大小
stat vm.img | grep Size
# Size: 107374182400  # 100GB

解决方法:

如果需要预分配空间,使用 fallocate

# 预分配100GB
fallocate -l 100G vm.img

# 此时du和ls显示一致
du -h vm.img
# 100G

ls -lh vm.img
# 100G

找出所有稀疏文件:

# 找出ls大小 > du大小的文件
find /var -type f -exec sh -c '
  ls_size=$(stat -c %s "$1")
  du_size=$(du -b "$1" | cut -f1)
  if [ $ls_size -gt $((du_size * 2)) ]; then
    echo "$1: ls=$ls_size du=$du_size"
  fi
' _ {} \;

4.5 原因5:文件系统损坏

现象:

# df显示空间已满
df -h /var
# 100% used

# du统计很少
du -sh /var
# 50GB

# 删除文件后空间仍未释放

根因:

文件系统元数据损坏,superblock 或 bitmap 不一致。

判断方法:

# ext4检查文件系统错误
dmesg | grep -i "ext4.*error"

# xfs检查文件系统错误
dmesg | grep -i "xfs.*error"

# 查看文件系统日志
journalctl -k | grep -i "filesystem"

解决方法:

# ext4修复文件系统(需要umount或只读模式)
umount /dev/sda1
fsck.ext4 -f /dev/sda1

# xfs修复文件系统
umount /dev/sda1
xfs_repair /dev/sda1

注意:

  • fsck 可能导致数据丢失,修复前务必备份
  • 生产环境建议先以只读模式 umount,快照后再修复

五、完整排查流程

5.1 第一步:确认差异范围

# 查看df统计
df -h /var
# 已用900GB

# 查看du统计
du -sh /var
# 850GB

# 计算差异
# 差异 = 900GB - 850GB = 50GB

如果差异超过 10%,需要继续排查。

5.2 第二步:排查已删除但未释放的文件

# 查找所有已删除但未释放的文件
lsof | grep deleted | awk '{sum+=$7} END {print sum/1024/1024/1024 "GB"}'

# 或者更详细的统计
lsof | grep deleted | awk '{print $1,$2,$7}' | while read proc pid size; do
  size_gb=$(echo "scale=2; $size/1024/1024/1024" | bc)
echo "$proc (PID: $pid): ${size_gb}GB"
done | sort -t: -k2 -rn

如果这部分占用超过差异的 50%,重启相关进程即可解决。

5.3 第三步:排查挂载点覆盖

# 查看所有挂载点
mount | grep -v "^proc\|^sysfs\|^devpts\|^tmpfs"

# 对每个挂载点,检查是否覆盖了已有文件
for mp in $(mount | awk '{print $3}'); do
echo "=== $mp ==="
# 注意:此操作会短暂卸载挂载点
  umount $mp 2>/dev/null && du -sh $mp && mount $mp
done

注意:

  • 此方法会短暂卸载挂载点,影响服务
  • 生产环境建议在维护窗口执行,或使用只读方式

5.4 第四步:排查稀疏文件

# 找出所有稀疏文件(ls大小 > du大小)
find /var -type f -exec sh -c '
  ls_size=$(stat -c %s "$1" 2>/dev/null || echo 0)
  du_size=$(du -b "$1" 2>/dev/null | cut -f1)
  if [ $ls_size -gt $((du_size * 2)) ]; then
    echo "$1: ls=$ls_size du=$du_size"
  fi
' _ {} \; 2>/dev/null

稀疏文件在虚拟化、数据库场景常见,通常不需要处理。

5.5 第五步:检查 inode 使用情况

# 查看inode使用率
df -i /var

# 如果inode使用率>80%,查找小文件最多的目录
find /var -type f | awk -F/ '{print $2}' | sort | uniq -c | sort -rn | head -10

5.6 第六步:检查文件系统是否损坏

# 查看文件系统错误
dmesg | grep -E "ext4|xfs" | grep -i error

# 查看系统日志
journalctl -k --since "1 hour ago" | grep -i filesystem

如果有错误,考虑在维护窗口运行 fsck。

六、预防措施

6.1 日志轮转

配置 logrotate,避免日志文件无限增长。

# /etc/logrotate.d/app
/var/log/app/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifempty
    create 0644 app app
    postrotate
        systemctl reload app.service > /dev/null 2>&1 || true
    endscript
}

关键参数:

  • daily :每天轮转
  • rotate 7 :保留 7 天
  • compress :压缩旧日志
  • delaycompress :延迟一天压缩 (避免进程仍在写入)
  • postrotate :轮转后重新加载应用 (释放旧文件描述符)

6.2 监控磁盘空间和 inode

Prometheus 监控规则:

# 磁盘空间告警
- alert: DiskSpaceLow
expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) < 0.1
for: 5m
annotations:
summary: "磁盘可用空间低于10%"

# inode使用率告警
- alert: InodeUsageHigh
expr: (node_filesystem_files_free / node_filesystem_files) < 0.2
for: 5m
annotations:
summary: "inode使用率超过80%"

# du与df差异告警
- alert: DiskUsageMismatch
expr: |
    abs(
      (node_filesystem_size_bytes - node_filesystem_avail_bytes) -
      sum(du_bytes) by (mountpoint)
    ) > 10 * 1024^3
for: 10m
annotations:
summary: "du与df差异超过10GB"

6.3 定期清理临时文件

# 清理/tmp下7天未访问的文件
find /tmp -type f -atime +7 -delete

# 清理日志目录下30天前的压缩文件
find /var/log -name "*.gz" -mtime +30 -delete

配置 cron 定时执行:

# /etc/cron.daily/cleanup-tmp
#!/bin/bash
find /tmp -type f -atime +7 -delete
find /var/log -name "*.gz" -mtime +30 -delete

6.4 应用优雅重启

应用重启时,应先关闭所有文件描述符,再退出进程。

Java 应用示例:

// 注册shutdown hook
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
// 关闭日志文件
    LogManager.shutdown();
// 关闭数据库连接
    dataSource.close();
}));

Python 应用示例:

import atexit
import logging

def cleanup():
# 关闭日志文件
    logging.shutdown()

atexit.register(cleanup)

6.5 避免挂载点覆盖

挂载前检查目标目录是否为空:

# 检查目录是否为空
if [ "$(ls -A /data)" ]; then
echo "Error: /data is not empty"
exit 1
fi

mount /dev/sdb1 /data

或使用 bind mount:

mkdir /mnt/data
mount /dev/sdb1 /mnt/data
mount --bind /mnt/data /data

七、实战案例

案例1:MySQL binlog 未释放

现象:

MySQL 服务器磁盘空间告警,删除 binlog 后空间未释放。

排查:

# df显示90%使用率
df -h /var/lib/mysql
# 已用900GB

# du统计只有500GB
du -sh /var/lib/mysql
# 500GB

# 查找已删除文件
lsof | grep deleted | grep mysql
# mysqld  1234  mysql  10w  /var/lib/mysql/mysql-bin.000001 (deleted)  400GB

根因:

MySQL 进程持有已删除 binlog 的文件描述符。

解决:

# 方法1:重启MySQL(影响业务)
systemctl restart mysql

# 方法2:清空文件描述符(不重启)
lsof -p 1234 | grep deleted | awk '{print $4}' | sed 's/w$//' | while read fd; do
  : > /proc/1234/fd/$fd
done

# 或使用MySQL命令删除
mysql -e "PURGE BINARY LOGS BEFORE NOW();"

预防:

配置 binlog 自动清理:

# my.cnf
[mysqld]
expire_logs_days = 7

案例2:Docker 容器日志占满磁盘

现象:

Docker 宿主机磁盘空间告警,但找不到大文件。

排查:

# df显示90%
df -h /var/lib/docker
# 已用900GB

# du统计只有600GB
du -sh /var/lib/docker
# 600GB

# 查找容器日志
du -sh /var/lib/docker/containers/*/
# 某个容器日志300GB

根因:

容器日志未限制大小,持续写入导致磁盘占满。

解决:

# 清理容器日志
find /var/lib/docker/containers/ -name "*-json.log" -exec truncate -s 0 {} \;

# 或重启容器
docker restart <container-id>

预防:

配置 Docker 日志限制:

# /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}

案例3:XFS 文件系统元数据占用

现象:

XFS 文件系统使用率高,但找不到大文件。

排查:

# df显示90%
df -h /data
# 已用900GB

# du统计800GB
du -sh /data
# 800GB

# 查看inode使用率
df -i /data
# 85%

# 查看小文件数量
find /data -type f | wc -l
# 50000000  (5千万个文件)

根因:

大量小文件导致元数据开销大 (每个文件至少占用 512 字节 inode)。

解决:

# 清理小文件
find /data -type f -size -1k -mtime +30 -delete

# 或归档到tar
find /data -type f -size -1k -mtime +30 -print0 | tar -czf /backup/small_files.tar.gz --null -T -
find /data -type f -size -1k -mtime +30 -delete

八、总结

dudf 不一致的根本原因是两者统计方式不同:

du 统计可见文件,df 统计文件系统超级块。

常见原因和解决方法:

原因 现象 检测命令 解决方法
已删除文件未释放 删除文件后空间未增加 lsof | grep deleted 重启进程或清空文件描述符
元数据占用 du 和 df 差异 5%-10% df -i 清理小文件
挂载点覆盖 du 统计为 0 但 df 显示已用 mount + umount 清理被覆盖的文件
稀疏文件 ls 显示大 du 显示小 stat + du 预分配空间
文件系统损坏 删除文件空间不释放 dmesg | grep error fsck 修复

核心排查步骤:

  1. 确认 du 和 df 差异范围
  2. 检查已删除但未释放的文件
  3. 检查挂载点覆盖
  4. 检查稀疏文件
  5. 检查 inode 使用率
  6. 检查文件系统错误

预防措施:

  1. 配置日志轮转
  2. 监控磁盘空间和 inode
  3. 定期清理临时文件
  4. 应用优雅重启,关闭文件描述符
  5. 避免挂载点覆盖

掌握这些知识后,遇到磁盘空间问题时,能够快速定位根因,采取正确的解决措施,避免误操作导致的数据丢失或服务中断。如果还想深入了解更多 Linux 文件系统与内核机制 ,可以延伸到 TCP/IP、内核调度、Socket 等系统底层知识,对整体排查思路会更有帮助。

九、深入排查技巧

9.1 快速定位占用空间的进程

# 按进程统计已删除文件占用空间
lsof | grep deleted | awk '{process[$1]+=$7} END {for (p in process) print p, process[p]/1024/1024/1024 "GB"}' | sort -k2 -rn

9.2 定位大文件

# 查找大于1GB的文件
find /var -type f -size +1G -exec ls -lh {} \; | awk '{print $9, $5}'

# 查找TOP 20大文件
find /var -type f -printf '%s %p\n' | sort -nr | head -20 | awk '{print $2}' | xargs ls -lh

9.3 分析目录层级占用

# 递归统计每层目录占用
du -h /var --max-depth=2 | sort -hr | head -20

9.4 检查系统保留空间

ext 系列文件系统默认保留 5% 空间给 root 用户。

# 查看保留空间配置
tune2fs -l /dev/sda1 | grep "Reserved block count"

# 调整保留空间为1%
tune2fs -m 1 /dev/sda1

9.5 实时监控磁盘写入

# 监控哪个进程在写入磁盘
iotop -o -P

# 或使用pidstat
pidstat -d 1

# 监控文件变化
watch -n 1 "du -sh /var/log"

十、故障预案

10.1 紧急情况处理流程

磁盘空间 100% 使用时:

  1. 立即清理已删除文件
lsof | grep deleted | awk '{print $2}' | xargs -I {} kill -9 {}
  1. 清理系统临时文件
rm -rf /tmp/*
rm -rf /var/tmp/*
  1. 清理日志文件
find /var/log -name "*.log" -exec truncate -s 0 {} \;
  1. 清理包管理缓存
# CentOS/RHEL
yum clean all

# Ubuntu/Debian
apt-get clean
  1. 清理 Docker 资源
docker system prune -af

10.2 回滚方案

如果清理后服务异常:

  1. 恢复日志文件
# 从备份恢复
cp /backup/app.log /var/log/app.log
  1. 重启相关服务
systemctl restart app.service
  1. 检查应用状态
systemctl status app.service
journalctl -u app.service -n 100

十一、监控与告警

11.1 Prometheus 监控指标

# 磁盘空间监控
- record: disk:usage:percentage
expr: (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100

# inode监控
- record: disk:inode:percentage
expr: (1 - node_filesystem_files_free / node_filesystem_files) * 100

# du与df差异监控
- record: disk:du_df:diff_gb
expr: (node_filesystem_size_bytes - node_filesystem_avail_bytes - sum(du_bytes) by (mountpoint)) / 1024^3

11.2 告警规则

groups:
- name: disk_alerts
rules:
- alert: DiskSpaceCritical
expr: disk:usage:percentage > 95
for: 5m
labels:
severity: critical
annotations:
summary: "磁盘使用率超过95%"

- alert: InodeExhausted
expr: disk:inode:percentage > 90
for: 5m
labels:
severity: warning
annotations:
summary: "inode使用率超过90%"

- alert: DiskUsageMismatch
expr: disk:du_df:diff_gb > 50
for: 10m
labels:
severity: warning
annotations:
summary: "du与df差异超过50GB"

十二、最佳实践清单

日常维护:

  • [ ] 配置 logrotate,定期轮转日志
  • [ ] 配置 tmpwatch 或 systemd-tmpfiles,定期清理临时文件
  • [ ] 配置 Docker 日志限制
  • [ ] 定期检查 du 与 df 差异

监控告警:

  • [ ] 监控磁盘空间使用率,阈值 85% 告警
  • [ ] 监控 inode 使用率,阈值 80% 告警
  • [ ] 监控 du 与 df 差异,超过 10GB 告警
  • [ ] 监控已删除但未释放的文件

应用开发:

  • [ ] 应用退出时关闭所有文件描述符
  • [ ] 日志输出到 stdout,由容器或 systemd 管理
  • [ ] 避免应用长期持有已删除文件的描述符
  • [ ] 定期 rotate 日志,避免单个文件过大

运维操作:

  • [ ] 删除大文件前检查是否有进程打开
  • [ ] 挂载前检查目标目录是否为空
  • [ ] 定期备份重要数据
  • [ ] 磁盘空间告警后,先排查再删除

容量规划:

  • [ ] 根据业务增长预估磁盘需求
  • [ ] 预留 20% 以上空间缓冲
  • [ ] 定期 Review 磁盘使用趋势
  • [ ] 提前规划磁盘扩容方案

这些措施能够有效预防和快速解决 du 与 df 不一致的问题,确保系统稳定运行。

十三、常见问题解答

Q1:为什么重启服务器后空间就释放了?

A: 重启会强制关闭所有进程,所有被进程持有的文件描述符都会释放。但重启代价高,生产环境应优先使用重启具体进程的方式。

Q2:可以强制释放文件描述符而不重启进程吗?

A: 可以通过清空 /proc/PID/fd/N 文件释放空间,但文件内容会丢失。更安全的方式是使用信号让进程重新打开文件:

# 发送SIGHUP信号(大部分应用会重新打开日志文件)
kill -HUP <PID>

# 或使用systemd reload
systemctl reload app.service

Q3:如何快速定位哪个目录增长最快?

# 记录当前各目录大小
du -sh /* > /tmp/du_baseline.txt

# 等待一段时间后再次统计
sleep 3600  # 等待1小时
du -sh /* > /tmp/du_current.txt

# 对比差异
paste /tmp/du_baseline.txt /tmp/du_current.txt | awk '{print $2, $1, $3}' | while read dir size1 size2; do
echo "$dir: $size1 -> $size2"
done

Q4:ext4 和 xfs 在元数据开销上有什么区别?

A:

  • ext4:固定 inode 数量,格式化时确定,无法动态调整
  • xfs:动态分配 inode,更适合小文件场景,但元数据开销可能更大

如果有大量小文件,xfs 通常表现更好。

Q5:可以在线调整文件系统保留空间吗?

A: ext 系列可以:

# 将保留空间从5%调整为1%
tune2fs -m 1 /dev/sda1

xfs 不支持保留空间配置。

Q6:如何避免日志文件占满磁盘?

A: 三种方案:

  1. logrotate 自动轮转 :最推荐
  2. 应用层限制日志大小 :如 log4j 的 RollingFileAppender
  3. 监控+告警+自动清理 :作为兜底方案

Q7:Docker overlay2 占用空间大怎么办?

# 查看Docker磁盘使用情况
docker system df

# 清理未使用的镜像、容器、卷
docker system prune -a

# 清理所有build cache
docker builder prune -a

Q8:如何预估文件系统元数据开销?

粗略估算:

  • 大文件场景 (平均 >1MB):元数据开销约 1-2%
  • 中等文件场景 (平均 100KB-1MB):元数据开销约 3-5%
  • 小文件场景 (平均 <100KB):元数据开销可能超过 10%

精确计算:

# ext4
dumpe2fs /dev/sda1 | grep "Block count"
dumpe2fs /dev/sda1 | grep "Free blocks"
# 元数据开销 = (总block - 可用block - 已用文件占用block) × block大小

Q9:为什么有时候 df 显示的 Used + Available ≠ Size?

A: 几个原因:

  1. 文件系统保留空间 (ext 系列默认 5%)
  2. 元数据占用 (inode 表、bitmap、journal)
  3. 坏块标记占用的空间
  4. 对齐和填充占用的空间

这是正常现象,不是 bug。

Q10:生产环境磁盘空间紧急,哪些文件可以安全删除?

相对安全:

  • /tmp/* 临时文件
  • /var/tmp/* 临时文件
  • /var/log/*.gz 压缩日志
  • /var/cache/yum/*/var/cache/apt/* 包管理缓存
  • ~/.cache/* 用户缓存

需谨慎:

  • 当前日志文件 (可能被进程持有)
  • 数据库文件 (binlog、redo log 等)
  • 应用数据文件

绝对不能删:

  • 系统配置文件 /etc/*
  • 系统二进制文件 /bin/*/sbin/*/usr/bin/*
  • 系统库文件 /lib/*/usr/lib/*

十四、扩展阅读

14.1 文件系统原理

  • inode 与文件的关系
  • 文件系统的 block 分配策略
  • 日志型文件系统 (journaling) 的原理
  • Copy-on-Write 文件系统 (btrfs、zfs)

14.2 相关命令深入

# lsof高级用法
lsof -p <PID>           # 查看指定进程打开的文件
lsof +D /var/log        # 查看目录下被打开的文件
lsof -u root            # 查看指定用户打开的文件
lsof -i :8080           # 查看占用端口的进程

# stat查看文件详细信息
stat <file>             # 查看文件inode、block、时间戳
stat -f <file>          # 查看文件系统信息

# dumpe2fs查看ext文件系统信息
dumpe2fs /dev/sda1      # 查看superblock、inode表信息

# xfs_info查看xfs文件系统信息
xfs_info /dev/sda1      # 查看block大小、inode大小等

14.3 性能优化

  • 选择合适的文件系统 (ext4、xfs、btrfs)
  • 调整 inode 数量和大小
  • 使用 SSD 时启用 trim
  • 定期清理碎片 (ext4 使用 e4defrag)
  • 监控 IOPS 和延迟

这些知识点能够帮助深入理解 Linux 文件系统的工作机制,更好地排查和预防磁盘空间相关问题。对于想系统提升 Linux 运维与 SRE 能力 的读者,可以从 Bash 脚本、ELK Stack 日志分析、配置管理等多维度构建完整的运维知识体系。

十五、自动化脚本

15.1 一键排查脚本

#!/bin/bash
# disk_analysis.sh - 一键排查du与df差异

echo "=== 磁盘使用情况 ==="
df -h | grep -v tmpfs

echo -e "\n=== du与df差异分析 ==="
for mp in $(df -h | grep -v tmpfs | grep -v Filesystem | awk '{print $6}'); do
    df_used=$(df -BG $mp | tail -1 | awk '{print $3}' | tr -d 'G')
    du_used=$(du -sg $mp 2>/dev/null | awk '{print $1}')
    diff=$((df_used - du_used))
if [ $diff -gt 10 ]; then
echo "$mp: df=${df_used}GB du=${du_used}GB 差异=${diff}GB"
fi
done

echo -e "\n=== 已删除但未释放的文件 ==="
lsof 2>/dev/null | grep deleted | awk '{print $1,$2,$7}' | awk '{sum[$1]+=$3} END {for (p in sum) if (sum[p] > 1073741824) print p, sum[p]/1073741824 "GB"}' | sort -k2 -rn

echo -e "\n=== inode使用情况 ==="
df -i | grep -v tmpfs | awk '{if (NR>1 && $5+0 > 80) print $0}'

echo -e "\n=== TOP 10大文件 ==="
find /var /home -type f -size +1G 2>/dev/null -exec ls -lh {} \; | awk '{print $9, $5}' | sort -k2 -hr | head -10

15.2 自动清理脚本

#!/bin/bash
# auto_cleanup.sh - 自动清理磁盘空间

THRESHOLD=90  # 磁盘使用率阈值

for mp in $(df -h | grep -v tmpfs | grep -v Filesystem | awk '{print $6}'); do
    usage=$(df -h $mp | tail -1 | awk '{print $5}' | tr -d '%')

if [ $usage -gt $THRESHOLD ]; then
echo "[$mp] 使用率${usage}%,开始清理..."

# 清理已删除文件
        lsof 2>/dev/null | grep $mp | grep deleted | awk '{print $2}' | sort -u | while read pid; do
echo "  清空进程 $pid 的已删除文件"
for fd in /proc/$pid/fd/*; do
if [ -L $fd ] && readlink $fd | grep -q deleted; then
                    : > $fd 2>/dev/null
fi
done
done

# 清理旧日志
        find $mp -name "*.log.*" -mtime +7 -delete 2>/dev/null
        find $mp -name "*.gz" -mtime +30 -delete 2>/dev/null

echo "  清理完成,当前使用率:$(df -h $mp | tail -1 | awk '{print $5}')"
fi
done

15.3 监控集成脚本

#!/bin/bash
# disk_exporter.sh - 导出自定义磁盘指标到Prometheus

TEXTFILE_DIR="/var/lib/node_exporter/textfile_collector"
mkdir -p $TEXTFILE_DIR

# 统计已删除但未释放的文件
deleted_bytes=$(lsof 2>/dev/null | grep deleted | awk '{sum+=$7} END {print sum}')
echo "disk_deleted_but_unreleased_bytes ${deleted_bytes:-0}" > $TEXTFILE_DIR/disk_deleted.prom

# 统计du与df差异
for mp in $(df -h | grep -v tmpfs | grep -v Filesystem | awk '{print $6}'); do
    df_bytes=$(df -B1 $mp | tail -1 | awk '{print $3}')
    du_bytes=$(du -sb $mp 2>/dev/null | awk '{print $1}')
    diff=$((df_bytes - du_bytes))
echo "disk_usage_diff_bytes{mountpoint=\"$mp\"} $diff"
done > $TEXTFILE_DIR/disk_diff.prom

这些脚本可以集成到 cron 或 systemd timer 中,实现自动化巡检和清理。

十六、总结

核心要点:

  1. du 统计可见文件,df 统计文件系统超级块,两者统计方式不同
  2. 已删除但被进程持有的文件不会释放空间,是最常见的原因
  3. 文件系统元数据、挂载点覆盖、稀疏文件也会导致差异
  4. 使用 lsof、df -i、stat 等工具快速定位问题
  5. 预防措施包括日志轮转、监控告警、应用优雅重启

排查流程:

确认差异 → 检查已删除文件 → 检查挂载点 → 检查稀疏文件 → 检查 inode → 检查文件系统错误

最佳实践:

配置日志轮转 + 监控告警 + 定期清理 + 应用优雅重启 + 避免挂载点覆盖

掌握这些知识和工具后,面对磁盘空间问题能够快速定位、正确处理、有效预防。像这类围绕 Linux 文件系统、inode、进程描述符的实战问题,在 云栈社区 的运维板块里也经常被拿出来讨论,如果你在实际排查中遇到过更隐蔽的情况,欢迎来一起交流。


ONE MORE THING :本文偏向 ext4/xfs 场景。如果你用的是 btrfs 或 zfs 这类 Copy-on-Write 文件系统, dudf 的差异本质上也会存在,但底层机制和排查工具会有差异,建议结合对应文件系统的文档进一步验证。




上一篇:Agent 死循环只设 max_steps=8?面试官忘了看无进展信号
下一篇:JSON 太慢?K8s 1.37 CBOR 二进制序列化原理与 8 倍提速实测
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-26 23:59 , Processed in 0.879639 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表