ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux磁盘空间排查实战:从du原理到定位文件占用

Linux磁盘空间排查实战:从du原理到定位文件占用 生产服务器告警磁盘占用率95%作为运维或者后端开发第一反应肯定是查清楚哪个目录吃掉了空间。这时候Linux的du命令是绕不开的基础工具。du全称disk usage用来统计文件和目录在磁盘上的实际占用情况。别看命令本身很简单真正能用顺手、用得明白其实有不少门道——单位换算、硬链接、稀疏文件、挂载点、权限边界这些细节都可能导致统计结果和你的直觉不一致。这篇文章我从统计原理讲起再整理日常排查方法、最容易踩的坑、以及与周边命令的组合用法最后聊一下大目录扫描的性能控制。适合刚接触Linux的新手建立正确认知也适合已经常用du但没细究过这些行为的运维同学参考。我尽量用实际输出和例子说话你看完可以直接拿去用。1. du统计的到底是什么磁盘块、递归与“假大小”1.1 为什么ls -l和du显示的大小总对不上先看一个很多人问过的问题一个文件用ls -l看是1字节用du -sh看却是4.0K到底谁是对的其实两个都对只是统计维度不同。ls -l显示的是文件逻辑上的字节长度而du统计的是文件在磁盘上实际占用的存储块数量。操作系统的文件系统一般以固定大小的块为单位分配空间Ext4、XFS这类常见文件系统数据块大小通常是4096字节。一个只有1字节的文件逻辑上只有一个字节但落盘时照样会占一个完整的数据块也就是4KB。所以你会看到du输出4.0K。文件大小从1字节变成3KB还是占4KB到了5KB就要分配两个块即8KB。这里的关键概念叫“磁盘占用”。磁盘上不存在按字节精确回收的空间文件系统按块记账。du读取文件元数据中的“已分配块数”再乘以每块字节数最终得到的就是真实占用。1.2 目录本身也是要占空间的很多新手不知道目录在Linux里也是一种特殊文件它同样会消耗磁盘块。新建一个空目录du -sh看这个目录通常就是4.0K。这个大小用来保存目录内部的文件名、inode号等条目信息。目录里的文件越多、文件名越长这个目录本身的体积也会增大但增速不像普通文件那么夸张。更关键的是du在统计目录时会把该目录下所有子目录、所有文件累加起来一起算。也就是说du -sh /var/log输出的是整个log目录树的总占用不是log目录自身那几个字节。这种递归统计是du最核心的行为也是后面排查磁盘空间时真正有价值的地方。如果你想看的是“整个目录树一共多大”用du -sh或du -s如果想看目录里每个子目录各自多大用du -h --max-depth1。如果只把du后面直接跟一个目录路径而不加任何参数它会把下面每一层子目录都列一遍输出非常长日常很少这样用。1.3 du的默认单位、显示格式和几个基础参数GNU版本的du默认输出单位是1024字节的块数也就是数字后面不带单位全是一堆以K结尾的块计数。实际上这些数字的单位是K即“多少个1K块”。加上-h参数后du才会自动把结果换算成K、M、G这种人类友好格式换算关系是1024进位不是1000进位。要精确看字节数可以用-B 1指定块大小为1字节例如du -B 1一个文件会直接输出4096这种原始数值。不过日常排查用-h就够了。还有一层容易混淆的是--apparent-size这个参数可以让du显示表观大小也就是ls -l看到的逻辑大小。对于普通文件它和du默认输出的磁盘占用会有差异这个差异恰恰能帮助我们理解稀疏文件的场景后面会专门讲。基础参数里-s是汇总只给最终总和-a会列出所有文件而不是只列目录--max-depthN控制输出层级比如--max-depth1只显示当前目录下一级子目录的总占用。这几个参数是日常用得最多的。2. 实战排查三层递进定位磁盘占用大户2.1 第一层先确认是哪个分区出了问题拿一台报警服务器举例第一步永远是df -h而不是直接du扫盘。df -h会告诉你每个挂载点的容量、已用、可用、使用率。这一步的核心价值是缩小问题范围如果/分区满了就排查根文件系统下的目录如果/data分区满了就只需要处理/data这块不相关的地方完全不用碰。注意df和du的分工差异df站在文件系统管理的全局视角统计块组、超级块、保留块等整体占用du站在文件视角逐个inode累加文件大小。前者像看一个仓库的总库存后者像把仓库里每件货登记一遍。这种底层差异直接导致两个命令的结果天然会有偏差方向是df的数字通常会大于du统计的花销总和。2.2 第二层从根目录向下找大目录确认了分区之后在根目录上用这条命令du -x -h --max-depth1 / 2/dev/null | sort -h -r | head -20这里有几个参数我逐个解释。-x表示不要跨越到其他文件系统。假如你机器上/data单独挂了一块盘、/home也单独挂载不加-x的话du会把它们内部的内容也统计进来看起来像是根分区吃满了实际却是别的分区占空间很容易误判。-x配合从小到大、从根到叶的排查能保证每个数字都只落在当前文件系统内。--max-depth1控制只输出根目录下一级子目录的总量不会把子目录的孙级内容全部打印出来。sort -h -r按人类可读的大小倒序排列head -20只取最大的二十项。如果/分区下面堆了十几个目录这条命令能立刻让你看到到底是/var、/usr还是/root在膨胀。实测一下输出可能长这样15G /var 8.2G /usr 2.1G /root 1.5G /opt2.3 第三层逐层钻取锁定具体目录拿到上一层的榜单之后下一个动作是进入最大的目录继续钻。比如/var占了15G那就执行du -x -h --max-depth1 /var 2/dev/null | sort -h -r | head -20很可能下一步会看到/var/log占掉了大头。再往里走/var/log/journal或者某个服务日志目录就暴露出来了。这一层一层往下钻的过程本质上是在不断缩小排查半径每次只关注当前层级最大的那个分支。如果钻到某一层发现大文件居多而不是大目录比如日志文件不小但目录总占用却不大说明问题主要集中在少数单文件上。这时候find就该登场了比如查找指定范围内大于500MB的文件find /var -xdev -type f -size 500M -exec ls -lh {} \;清理之前务必确认文件是不是还处于写状态最稳妥的做法是把日志先truncate成空文件而不是直接rm掉。这类处理后面组合部分我会再展开。2.4 几个高频参数组合的快查du -sh 路径只看一个目录树总占用最常用。du -h --max-depth1 路径显示目录子目录占用的单层清单。du -a -h 路径把目录下的所有文件也逐一带上用于定位文件级大块头。du -t 2G只显示占用大于2G的项小目录直接过滤掉输出更干净。du --exclude.cache --exclude*.log 路径排除指定目录或模式后再统计。du --block-size1 文件以字节为单位显示精确占用。这些参数组合不是孤立的排查时经常连续用几条命令配合。我的习惯是先把第二层的榜单打出来再针对最大目录逐层向下而不是一上来就全盘扫。3. du最容易被误解的五个边界场景3.1 硬链接为什么同一个文件会“统计出两份”硬链接是du统计里最容易让人迷惑的情况。假设你执行了ln a.txt b.txt创建了一个硬链接两个文件名指向同一个inode数据只有一份。此时你分别用du -sh a.txt和du -sh b.txt会各自看到一个4K的占用。但如果用一个目录把它们包含进去再du整个目录却不会出现8K而是只统计一份因为GNU du在递归统计同一棵目录树时默认把同一个inode的多个硬链接当作一份记账不会重复计算。这个设计本身是合理的它让“目录树总占用”真正反映物理磁盘的消耗。但反过来也会造成一个经典误区如果你图省事用du -sh dir1 dir2 dir3手动加起来准备估算两个目录合并后的占用而其中存在跨目录的硬链接手动求和就会虚高。要强制重复计数可以用--count-links参数但在绝大多数日常场景里默认的去重行为恰恰是我们要的。备份目录、邮件存储、Git对象库这类硬链接出现频繁的地方统计前最好先想清楚自己到底要“逻辑上的多个文件占用”还是“物理磁盘上的实际占用”。默认行为对应后者。3.2 稀疏文件50GB的逻辑文件可能只占几KB稀疏文件是另一个反直觉的例子。文件系统允许你创建一个“空洞”文件只记录文件长度不真正给这些空洞分配磁盘块。比如创建一个50GB的稀疏文件truncate -s 50G sparse.imgls -lh会显示这个文件有50G但du -sh大概率显示0或者几KB因为实际落盘的块极少。这类文件常见于数据库快照、虚拟磁盘镜像、某些下载工具的临时文件、以及部分日志系统的预分配场景。碰到ls和du数字差异特别大的文件先不要质疑命令坏了用file、stat或者du --apparent-size对比确认是不是稀疏文件。复制这类文件时如果普通cp可能会把它展开成完整50G反而浪费磁盘应该用cp --sparsealways保留稀疏特性。3.3 文件已被删除但磁盘不释放你du不到却真实存在这是运维场景中非常经典的一类故障df -h显示某个分区100%满但用du从根目录开始把整个文件系统翻遍统计出来的总和远小于df的输出。明明删掉了一个大文件空间却没有回来。根本原因通常是有进程仍然持有这个文件的文件句柄。Linux下删除文件只是把目录项摘除如果有进程打开了它inode本身不会被立刻回收对应的磁盘块继续被占用。du是按文件名遍历目录来统计的文件名都没了du自然看不到这部分占用但df知道块还没释放。排查命令很直接lsof L1L1的含义是只列出link count小于1的文件也就是那些已经被删除但还被进程打开的文件。看到结果后对应处理方式无非两种重启持有句柄的服务让进程关闭句柄或者在极端紧急情况下考虑kill掉相关进程。空间会立刻回来。3.4 扫描边界挂载点、/proc和-x的关系du默认会递归进入所有子目录包括那些实际上是其他文件系统的挂载点。这意味着du /的时候如果/data是独立分区/data下的内容会被计入根文件系统的“目录树占用”里造成根分区统计数据虚高。反之如果只想看某个目录在当前文件系统内的真实消耗-x这个选项就非常关键。还有一个更隐蔽的问题是虚拟文件系统。直接du -sh /proc或者du划到/proc内部root用户可以读一堆动态数字和端口信息但这些不代表磁盘真实占用甚至可能让命令挂起或产生无意义的输出。日常扫描根目录时务必习惯性带上-x它同时帮你跳过proc、sys、dev这些不需要统计的伪文件系统。3.5 权限不足普通用户扫根目录时的隐形缺口普通用户执行du /会在一堆子目录上收到Permission denied这些目录没有读取权限时du既取不到它的大小也不会继续深入。结果就是统计出来的“总占用”比真实情况小。命令输出里会出现大量cannot access的提示如果忽略了很容易误判空间不大。处理办法很简单要么对特定目录有理有据地用sudo执行要么在执行时加上2/dev/null把错误输出丢掉只看有效统计。但丢掉错误输出会掩盖细节我的习惯是先不加过滤跑一次看清哪些目录没权限再决定是否用sudo或排除。真正做自动化采集时才会统一2/dev/null保持日志干净。4. du和周边命令组合从看大小到做清理4.1 sort -h怎么让du输出变成可读榜单GNU sort自带-h参数支持按人类可读的数字后缀排序。这条命令是排查空间的神器du -h --max-depth1 /data 2/dev/null | sort -h -r | head -30如果直接用sort -n排序的是纯数字对4.0K、32M、2.1G这种带单位的文本没意义结果会乱掉。sort -h会正确识别K、M、G、T后缀从大到小排得明明白白。这个能力是基于GNU coreutils提供的Linux发行版基本都有只要sort --version不低于8.16基本没问题。拿到榜单后如果要长期观察几个关键目录的增长可以把同样的命令写进一个脚本输出带上时间戳按天存档。4.2 du管总账find管大文件du擅长算目录树的总量但它不会替你精确找出某个大文件的完整路径。找大文件是find的专场。典型场景是du已经定位到/opt/app占用最高接下来想知道这个大占用是无数小文件堆积还是某个巨型文件导致用一条find直接锁文件find /opt/app -xdev -type f -size 200M -exec ls -lh {} \;这里的size 200M表示大于200MB-exec ls -lh是打印出人类可读大小-xdev仍然是不跨文件系统。把“目录总账”和“文件级定位”组合起来一次磁盘告警通常五六条命令就能彻底定位。如果既要按目录看、又想要文件粒度可以退一步用du -a -h然后再sort。但文件数量多的时候输出会非常长远不如find来得精准。两个命令的分工是du管“哪些目录在膨胀”find管“具体哪个文件是元凶”。4.3 df与du结果不一致完整排查链路df和du数值对不上在Linux运维里概率不小尤其是大分区、高负载、频繁写日志的机器。我建议按下面的顺序排查。第一步确认df -h里的使用率记下具体分区。第二步用du -x -h --max-depth1在该分区根目录跑一遍把这些子目录占用加起来。第三步如果du总和明显小于df已用空间立刻执行lsof L1查已删除但被打开的文件这是最高频原因。第四步如果查不到deleted文件检查文件系统保留块和元数据开销。Ext4默认会为root保留5%的块避免碎片和紧急情况这部分不算可用空间所以df显示100G的分区可用可能只有95G。用tune2fs -l /dev/设备名可以看reserved block count把保留比例调小能释放一部分空间但不建议生产环境乱调。再加上du本身不统计超级块、日志节点这类文件系统元数据df比du整体偏大是正常现象偏差一般不会到几个G。如果差得离谱料定问题出在deleted文件或者某些隐藏的挂载点上。4.4 ncdu这种交互式工具值不值得用ncdu是一个非常聪明的du封装它复用du的扫描数据把它变成终端里的交互式界面。装好后跑一句ncdu /data就能像文件管理器一样在各层目录间移动实时看到每个目录的占比还能按大小排序。对一次性排查和探索型清理来说效率比命令行逐个du高不少。安装很常规发行版软件源里基本都有。使用上我只提醒两点第一删文件前务必按两下方向键确认选中的目标这个界面按d会直接发送删除动作看清路径再动手第二它扫描的同样是递归统计大目录下初次进入会有一段等待时间和du本身的性能瓶颈一致别觉得是卡死了。ncdu适用场景是“人肉追踪一个会话内彻底搞清目录结构”而普通cron脚本采集用du就够了因为ncdu交互式设计天然不适合无人值守。5. 大目录下的性能控制与自动化5.1 为什么百万级小文件的目录会卡住du本质上是顺序遍历目录树对每个文件、每个目录做一次stat系统调用。性能瓶颈不在于数据量多少而在于文件数量和文件系统元数据读取成本。一个包含上百万个小文件的目录树哪怕总体积只占几个G扫描时间也可能以分钟甚至小时计。经典的案例是Yarn的缓存、node_modules、邮件存储和某些不清理的日志目录。这里有个关键认知必须澄清--max-depth参数只控制输出层级的多少并不减少扫描工作量。du为了拿到任意一级目录的总占用必须递归到最深层把整个子树走完。真正能加速的办法只有缩小扫描范围或者用--exclude把不需要的目录排除掉。5.2 给du设置IO和CPU优先级在业务繁忙的机器上直接跑du可能把磁盘IO推高影响正常业务。尽量避开高峰期扫描如果必须在白天跑建议通过ionice和nice降低影响ionice -c3 nice -n 19 du -xsh /data-c3表示idle调度只有磁盘空闲时才真正读写对业务影响最小。nice -n 19把进程的CPU优先级降到最低适合低频次后台统计。如果扫描量实在太大还有一个蠢办法但有效先看df确认分区整体容量然后只对最可疑的几个大目录分别du缩小扫描范围比调优先级更实在。5.3 并行统计大目录的思路和代价du本身是单线程的在目录设计比较合理的场景下可以手动并行。思路是先把大目录按一级子目录拆开用xargs -P并行执行多个du -s最后汇总。例如ls -d /data/* | xargs -P 4 -I{} du -xs {} 2/dev/null然后再用awk把结果相加。这种做法的收益在子目录之间相互独立时非常明显能把扫描时间从串行的10分钟压缩到几分钟。但代价是可能破坏硬链接去重逻辑默认du去重依赖从根开始的单一遍历按子目录拆开并行统计后跨目录的硬链接会被重复计算。对没有硬链接的普通数据目录这个风险基本不存在对有大量硬链接的备份目录并行求和数据可能偏大结论只能用来做排序参考不能当作精确disk usage。5.4 用cron定期记录空间占用趋势磁盘问题不是只有满了才需要关注最好的处理方式是在膨胀初期就发现它。我习惯给关键目录做定期快照写进cron30 2 * * * /usr/bin/du -x -s /data /var/log/diskusage.log 21这样每天凌晨跑一次不会干扰业务。日志里每行一个数字加时间空间增长是几周内逐步堆上去的还是一次异常暴涨翻历史记录立刻就知道。结合du输出配合sort生成Top目录榜单也能留着事后复盘。这比每次告警了才被动排查轻松太多。就实际体验来说du这种命令翻来覆去还是那几十个参数真正拉开差距的是对统计原理的理解和使用场景的把握。搞懂它统计的是磁盘块而不是逻辑大小记得排查时用-x避开挂载点看到df和du对不上先查deleted文件再掌握几个组合命令和性能控制手段这个工具就彻底吃透了。
返回列表