ARTICLE DETAIL

资讯详情

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

深度剖析Ext文件系统:inode耗尽与磁盘空间故障排查实战

深度剖析Ext文件系统:inode耗尽与磁盘空间故障排查实战 那是一个周五的晚上线上服务的监控突然开始持续报警。日志写入失败应用端明明白白地报着No space left on device。我第一反应是磁盘满了赶紧敲下df -h看了一眼根分区还剩 58G 可用空间。这怎么可能当时我还没意识到这个问题会把我一路带到 Ext 文件系统的底层——准确地说是 inode 耗尽。后来查了很久才搞明白不是数据块没了而是元数据索引节点inode用完了。这件事之后我把 Ext 系列文件系统从头到尾补了一遍踩了不少坑也整理出一套从原理到实战的完整脉络。这篇博文就当是我的复盘笔记把 Ext 文件系统那些真正值得理解的东西连同排障经验一次讲透。不管你是初学者、面试备考还是日常维护 Linux 服务器的运维应该都能从中找到有用的东西。1. 从 ext2 到 ext4一路演进过来的文件系统家族1.1 为什么还在用 ext2很多人第一次接触 Linux 时都会在某个小分区或者 U 盘的格式化界面里看到 ext2 这个选项然后产生疑问ext4 都出了这么多年了ext2 这种“上古遗物”为什么还没被淘汰ext2 诞生于 1993 年是 Linux 社区自己打造的早期文件系统。它没有日志机制结构非常简单。恰恰因为简单它有一个天然优势对存储设备的写入次数少。U 盘、SD 卡这类闪存设备写入寿命有限ext2 没有日志功能也就少了额外的写入放大。我手头一个 32G 的 U 盘至今用的还是 ext2长期插在嵌入式设备上做数据采集稳定性出乎意料地好。另外ext2 也被很多系统保留作为启动分区的一种选择因为它的实现代码最简单出问题的面也最小。当然ext2 的缺陷同样明显突然断电后正在写入的文件系统可能直接进入不一致状态轻则丢文件重则整个分区无法挂载。这也是它逐渐被 ext3、ext4 替代的主要原因。1.2 ext3 和 ext4 分别解决了什么ext3 在 2001 年问世最核心的贡献就是加入了日志journal机制。日志解决了 ext2 断电后容易损坏的问题其他方面基本沿用了 ext2 的框架。你可以把 ext3 看成“ext2 加上一层写入保护”。ext4 在 2008 年进入内核主线改动就大多了。它引入了 extent区段、延迟分配delayed allocation、多块分配、flex_bg 等机制并把文件系统和单个文件的大小上限提升到 PB 级和 EB 级。实际使用中最直观的感受是大文件的顺序读写性能明显优于 ext3创建大量小文件时也更不容易出现严重碎片。我用一张表总结一下三代核心差异特性ext2ext3ext4日志机制无有jbd有jbd2单个文件最大2TB2TB16TB4K 块文件系统最大16TB16TB1EB取决于内核块映射方式间接块映射间接块映射extent 树延迟分配无无有在线扩容不支持支持支持断电源保护弱强强1.3 为什么 Linux 一直默认用 Ext 而不是其他现在 Linux 发行版里CentOS/RHEL 默认文件系统已经是 XFSDebian/Ubuntu 则还是 ext4。很多人问ext4 不是最先进的文件系统为什么它依然是许多发行版的首选我的理解是ext4 的“历史包袱”恰恰是它的价值。它和内核的配合最成熟工具链mkfs、fsck、resize2fs、debugfs最完备出问题时有海量的文档和经验可以参考。对于服务器来说稳定性和可维护性往往比某个极端性能指标更重要。而且把 ext 系列搞懂了再去理解 XFS、Btrfs 甚至 ZFS 的设计思路你会发现很多概念都是相通的——比如元数据、日志、块的分配策略、位图管理。这就像会了 SQL 再去学各种数据库底子都是一样的。2. inode 与 block拆解“有空间却写不进文件”的故障2.1 故障复盘df 显示正常touch 却失败回到开头那个故障。df -h显示还有 58G 可用但应用写日志报No space left on device。我随手做了一个测试touch /tmp/test.txt结果同样是No space left on device。这时我试着敲了df -i瞬间反应过来——inode 使用率已经到了 100%。df -h看的是数据块的剩余量df -i看的是 inode 的剩余量。两者任何一个耗尽都会导致无法创建新文件。那次故障的根源是某个临时目录里堆积了海量几个字节的小文件每个文件都要占用一个 inode终于把 inode 表塞满了。这个经历让我意识到想理解 Ext 文件系统就必须先把两个基础概念吃透inode 和 block。2.2 inode文件的“户口本”不是“档案袋”inodeindex node是 Ext 全家桶最核心的设计。它保存一个文件的元数据包括文件类型、权限、属主、属组、文件大小、时间戳、ACL 以及指向数据块的指针。打个比方block 是仓库里真正放货的货位inode 是一张记录“这批货在哪些货位、总重量多少、谁放的、什么时候放的”的台账。很多人容易把 inode 误解为“存文件内容的地方”。实际上文件名并不是存在 inode 里的而是存放在目录项dentry中。目录项由目录文件维护它建立了“文件名 - inode 编号”的映射关系。这就是为什么同一个 inode 可以有多个文件名指向它——也就是硬链接。用一个命令就能直观看到$ ls -li /etc/hosts 131073 -rw-r--r-- 1 root root 221 Jun 14 2023 /etc/hosts前面那一串数字就是 inode 编号。想看得更细就用stat$ stat /etc/hosts File: /etc/hosts Size: 221 Blocks: 8 IO Block: 4096 regular file Device: fd00h/64768d Inode: 131073 Links: 1这里有个关键点inode 的数量在格式化时就已经固定了。你无法在文件系统运行后新增 inode除非重建。所以一个小文件的海量堆积消耗的不是数据块而是 inode 表空间。2.3 block 大小怎么选1K、2K、4K 背后的代价block 是文件系统读写的最小单位。Ext4 默认 block 大小是 4096 字节即 4K。选多大 block本质上是在“空间利用率”和“读写性能”之间做取舍。如果 block 太大比如 64K即使一个文件只有 1 字节它也要占满一个 64K 的块空间浪费肉眼可见。如果 block 太小比如 1K大文件会对应大量 block 指针文件系统在读取时要花更多时间去遍历映射关系性能下降。举一个实际算账的例子在一片 100GB 的分区上存 1 亿个 1KB 的小文件。用 4K block每个文件实际占用 4K总数据占用是 400GB超出容量。用 1K block每个文件实际占用 1K总占用 100GB刚好。但 1K block 意味着所有文件的写入都要做更多次寻址硬件 IO 压力明显上升。所以 mkfs 时的默认 4K 是一个比较均衡的选择适合绝大多数场景。如果明确知道某个分区专门用来存海量小文件可以考虑用 2K block如果是大文件仓库可以用 4K 甚至更大的 block再配合大 inode 比例效果会更好。2.4 硬链接与软链接同一个 inode 的两种玩法理解了 inode硬链接和软链接的区别就不用死记了。硬链接的本质是“多个目录项指向同一个 inode”。创建硬链接时inode 的链接计数nlink加 1。删除其中一个文件名只是在目录项里移除对应的映射inode 的数据还会保留直到 nlink 归零。这也是为什么硬链接不能跨文件系统——不同文件系统的 inode 编号是各自独立的没有可比性。软链接symlink则不同。它是一个独立文件有自己的 inode文件内容是一个目标路径字符串。所以你创建软链接后ls -l会看到它的大小正好等于路径字符串的长度比如 11 个字符的路径就是 11 字节。软链接可以跨文件系统因为它只是存路径不涉及目标文件的 inode。从 inode 角度理解rm也很重要rm实际做的是unlink把对应目录项删掉链接计数减 1只有当计数归零且没有进程打开这个文件时数据块才会被真正释放。这一点在后面排障 section 里的“删了文件但空间没释放”场景中会再次遇到。3. 超级块、块组与保留空间格式化后磁盘“少了几十G”的真相3.1 mkfs 之后空间到底去哪了很多人在格式化一块新硬盘后都会发现明明标称 1TB格式化完可用空间却少了几个百分点。这不是厂商坑你而是文件系统自身的元数据开销。一个 Ext 文件系统在格式化时会划分出一块区域来存放超级块superblock、块组描述符表group descriptors、块位图block bitmap、inode 位图inode bitmap、inode 表inode table以及日志区域。这些都属于元数据不直接用来存文件内容。用dumpe2fs可以看得很清楚$ dumpe2fs -h /dev/sda1 | grep -E Block count|Inode count|Reserved block count|Journal size以一块 100GB 的分区为例默认 5% 的保留块就是 5GB再加上 inode 表每 16KB 一个 inode每个 inode 256 字节和日志空间最终可用空间大约为标准容量的 93% 左右。所以看到“少了几十 G”真的不用慌这是文件系统在给自己留管理空间。3.2 块组为什么要像小区一样分片管理Ext 系列把整个分区划分为若干个块组block group。每个块组内部有自己的位图、inode 表和一大段连续的数据块区域。这样设计的目的其实来自磁盘的物理特性磁头寻道是要花时间的。如果 inode 和数据块相隔太远每次访问文件的元数据都要在磁盘上“跑来跑去”。把文件系统分成块组创建文件时会尽量把文件的 inode、目录项和数据块放在同一个块组内减少寻道距离。这就像小区里按楼栋分配车位和储物间车和货都在楼下取用效率自然高。ext4 的 flex_bg 特性又把连续的几个块组合成一个大组把它们的 inode 表和位图集中存放进一步降低了元数据访问的开销。这也是 ext4 比 ext3 在大量小文件场景下表现更好的原因之一。3.3 超级块损坏后的急救备份超级块的真正用途超级块是文件系统的“总目录”记录着块组数量、块大小、inode 总数等全局信息。如果超级块损坏整个文件系统就可能无法识别。好在 ext 系列在格式化时会写入多个超级块副本。默认情况下每个块组都会有超级块副本如果启用 sparse_super 特性则只会在 0、1、3、5、7 和幂次块组的开头保存副本。用这个命令可以找到所有备份位置$ dumpe2fs /dev/sdb1 | grep -i superblock当主超级块损坏系统启动报EXT4-fs error时可以指定备份超级块强制修复$ e2fsck -b 8193 /dev/sdb1这里的 8193 是第一个备份超级块的块号。我实际做过一次这样的修复过程并不复杂但前提是不要对已挂载的文件系统直接执行 fsck否则可能造成二次损伤。3.4 5% 的保留块到底是在防什么Ext 文件系统默认会预留 5% 的空间只允许 root 用户写入。官方理由是防止文件系统在接近写满时产生严重碎片同时为 root 留下紧急操作的缓冲空间。但对大分区来说5% 往往是一个不小的数字。一台 4TB 的数据盘5% 就是 200GB。在很多存储场景里这 200GB 可能几年都用不上。所以生产环境管理数据分区时常见做法是用-m 0把保留比例直接去掉$ mkfs.ext4 -m 0 /dev/sdb1如果已经格式化完也可以用 tune2fs 在线调整$ tune2fs -m 1 /dev/sdb1我个人的习惯是系统盘保留默认的 5%毕竟系统盘不知道什么时候会被日志打满数据盘和备份盘则调到 1% 或 0%把空间留给真正有用的数据。4. 日志机制与数据安全为什么 Ext 断电后还能救回来4.1 日志到底在记录什么ext3/ext4 引入的日志机制可以理解成“数据库里的 redo log”。文件系统在真正修改数据块之前会先把这次修改的描述写入日志区域等日志块落盘后再执行实际的数据写入。如果写数据的过程中断电重启后系统会根据日志里的记录重新执行未完成的修改把文件系统恢复到一致状态。这个过程在后台由 jbd2 内核线程完成用户基本无感知。一个经典的类比是搬家公司在搬货之前先填一张“货物清单”写清楚每件货从哪个房间搬到哪个房间。如果搬到一半停电了第二天凭清单就能知道哪些货没搬到位、需要补齐而不是把整个仓库翻个底朝天。4.2 三种日志模式怎么选ext3/ext4 提供了三种日志模式区别在于“哪些数据需要先写日志”模式元数据日志数据写入顺序安全性性能journal有数据先写入日志再写入数据区最高最低ordered有数据先落盘再提交元数据日志高中writeback有数据和日志写入顺序不保证中高默认是 ordered它兼顾了安全性和性能文件内容先写到数据区确认落盘后再把 inode 的元数据变更提交到日志。这样即使断电也不会出现“文件内容完整但 inode 指向空白块”的典型损坏。某些对数据安全极度敏感的场景比如数据库事务日志盘可以选 journal但代价是每次写入都要在日志区过一遍磁盘 IO 开销大增。普通服务器不建议改保持默认有序模式即可。4.3 ext4 的 extent 和延迟分配解决了什么ext3 时代文件块映射使用的是间接块机制每个数据块都要记录一个块号指针大文件的内存占用和访问复杂度都很高。ext4 改用 extent 树它把连续的块范围记录为一个“起始块号 长度”的区间一个 extent 最多可以覆盖 128MB 的连续空间4K 块下这就大幅压缩了映射信息。延迟分配则更有意思。普通文件系统在写入时是“来一块写一块”磁盘块分配器看不到整体布局很容易产生碎片。ext4 把数据先缓存在内存页中等攒够了再一次性调用块分配器一次性分配一大段连续空间同时配合多块分配器multiblock allocator批量分配。这也是为什么同样是写一个大文件ext4 比 ext3 快且磁盘碎片更少。但延迟分配也有代价如果写入后没等数据落盘就断电内存中尚未分配磁盘块的数据会直接丢失。所以重要数据写入后主动fsync依然是值得保留的习惯。5. 实操命门mkfs、tune2fs、resize2fs、fsck 的完整流程5.1 格式化之前要想清楚的事创建 Ext 文件系统之前最好先把分区用途想明白。我整理了一个常用命令模板$ mkfs.ext4 -m 0 -L data /dev/sdb1-m 0把保留块比例调为 0-L设置卷标。如果确定要放海量小文件可以加大 inode 密度$ mkfs.ext4 -i 8192 /dev/sdb1-i 8192表示每 8192 字节空间分配一个 inode。默认是 16384即每 16KB 一个 inode。调成 8192 后 inode 数量翻倍能容纳更多小文件但 inode 表本身会占用更多空间。这个东西不能拍脑袋定最好提前统计一下业务里的平均文件大小。格式化完成后用dumpe2fs检查实际参数养成习惯能避免很多后续问题。5.2 在线扩容growpart resize2fs 的经典组合服务器磁盘不够用时云控制台里扩完容量分区表和数据盘往往不会自动变化。标准操作流程是# 1. 调整分区表把分区扩展到新容量 $ growpart /dev/sda 1 # 2. 刷新内核分区表 $ partprobe /dev/sda # 3. 在线扩展文件系统 $ resize2fs /dev/sda1如果系统没有 growpart也可以用fdisk或parted手动操作但一定要保留原分区的起始扇区否则文件系统可能直接认不出来。resize2fs 最让人省心的特点就是可以在线扩容不需要卸载分区。但缩容就完全不同了必须先卸载文件系统然后执行e2fsck -f确认一致性后再使用resize2fs缩到目标大小。缩容操作一旦出错后果远比扩容严重所以建议缩容前先完整备份数据。5.3 fsck 的正确姿势和常见误区fsck是检查和修复文件系统的工具但很多人会不假思索地对挂载中的分区直接执行$ fsck -f /dev/sda1 # 错误示范这非常危险。文件系统挂载时内核就在不断写入元数据此时 fsck 看到的视角是“运动模糊”的修复操作很可能覆盖掉正在写入的变更反而造成二次损坏。正确做法是先umount再执行检查。对于 ext 系列我习惯用e2fsck而不是泛化的fsck$ umount /dev/sdb1 $ e2fsck -f /dev/sdb1如果超级块损坏用备份超级块进入$ e2fsck -b 8193 /dev/sdb1另外可以用tune2fs控制开机自检的频率$ tune2fs -c 20 -i 30d /dev/sda1 # 挂载 20 次或间隔 30 天触发自检 $ tune2fs -c 0 -i 0 /dev/sda1 # 禁用自检云服务器通常不建议禁用自检但也不建议把次数设得太小否则重启时动不动就 fsck启动时间会非常难看。6. 文件系统故障排障实录deleted 文件与 inode 耗尽的修复过程6.1 场景一日志文件明明删了磁盘空间却纹丝不动有一次排查磁盘告警我用df -h看到/var/log所在分区使用率 90%于是删掉了一个很大的历史日志文件结果再看使用率还是 90%。问题的根源是那个日志文件正被 nginx 进程持续打开。rm只是把目录项删掉了但进程的文件描述符依然指向这个 inode文件数据并没有被释放。Linux 下这类文件会显示为deleted状态排查命令$ lsof | grep deleted找到对应进程后直接重启进程让文件描述符关闭空间就释放了。如果进程不便重启可以“先清空再通知”$ : /var/log/nginx/access.log # 清空文件内容但保留文件 $ kill -USR1 $(cat /var/run/nginx.pid) # 让 nginx 重新打开日志这也是日志轮转里copytruncate参数存在的意义先复制再清空原文件进程持有的是原来的 inode不会影响后续写日志。6.2 场景二inode 耗尽连个临时文件都建不出来回到开头那个故障。df -i显示 inode 使用率 100%需要找到是谁在疯狂创建小文件。这种场景下常见的元凶目录是/tmp、邮件队列/var/spool/、崩溃报告目录/var/spool/abrt这些。排查命令我会分两步走。先用df -i确认哪个分区 inode 满了再在该分区内用find统计各目录的文件数量$ df -i /tmp $ find /tmp -xdev -type f | wc -l如果判断出某个目录堆积了海量小文件直接清理即可。但要注意inode 耗尽不是“等它自己好”就能解决的——只要不清理小文件它就会一直满着。更关键的是inode 总数在 mkfs 时就固定了一旦发现不够用唯一的根治办法是备份数据、重新格式化并设置更小的 inode 间隔-i参数再把数据拷回去。6.3 面试题里的 Ext 考点怎么回答才显得有深度这几年帮人改简历、模拟面试发现 Linux 面试题里 Ext 文件系统的出现频率非常高。常考的几个点基本都能用本文前面的知识串起来df -h和df -i的区别一个是数据块使用率一个是 inode 使用率。硬链接为什么不能跨文件系统因为硬链接共享的是同一个 inode跨文件系统无法共享。删除了文件但磁盘空间没释放进程仍持有打开的文件句柄inode 尚未释放。ext2/ext3/ext4 的区别核心是日志、extent、延迟分配。为什么大文件在 ext4 上性能更好extent 减少了块映射的开销延迟分配和多块分配减少了碎片。磁盘还有空间却无法创建文件优先怀疑 inode 耗尽。回答这类问题不要背定义最好结合实际场景讲一遍“我是怎么遇到这个问题的、怎么排查的、最后怎么解决”。面试官想听的不是八股而是你真正理解过这个东西。6.4 生产环境监控建议把 df -i 纳入日常巡检经过两次半夜排障之后我把服务器巡检脚本的检查项从原来的df -h扩成了df -h df -i并且给 inode 使用率直接加了监控告警阈值超过 85% 预警超过 95% 立刻处理。另外日志定期清理也建议从源头做配置一个简单的 crontab 任务每天清理超过 7 天的历史日志。很多时候 inode 耗尽不是因为单文件大而是因为“文件数量”在不知不觉中膨胀。数据块配额大家都会看inode 配额却总被忽视。提前监控远比事后抢救省心。最后再分享一个我自己的体会文件系统的知识纸上谈兵永远记不牢。真正把这些概念钉在脑子里的是那次 inode 耗尽的凌晨排查过程。建议你找一个测试机用mkfs.ext4格式化一块小分区手动改改-i和-m参数再用dumpe2fs和debugfs对照着观察内部结构。半小时的操作比看十篇文章都管用。
返回列表