ARTICLE DETAIL

资讯详情

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

Linux文件系统选型与调优:ext4/xfs、inode耗尽与在线扩容

Linux文件系统选型与调优:ext4/xfs、inode耗尽与在线扩容 上周又有同事跑来找我救火说服务器上df -h明明还剩两百多 G往日志目录里写文件却一直报No space left on device。这个场景我这些年遇到不下十次答案几乎都指向 inode 被耗尽而现场会主动敲一下df -i的人不到一半。文件系统这东西平时安静得像不存在一出事就是半夜告警加数据风险。Linux 下的 ext4、xfs、btrfs、f2fs 这些名字大家基本都能念出来但真问到某个业务该选哪个、mkfs参数怎么给、挂载选项怎么调、掉电之后会不会丢数据能把理由讲清楚的人并不多。这篇东西我想把 ext4 和 xfs 这两个生产环境里出场率最高的文件系统从头拆一遍顺带把 btrfs、f2fs 的定位说清楚。内容覆盖磁盘布局、日志模式、参数计算、挂载选项、在线扩容、故障排查每一块都尽量给到能直接抄的配置和命令。写给自己复盘用也适合刚接手 Linux 运维、做嵌入式根文件系统裁剪、或者准备面试想系统梳理存储知识的人看。不追求面面俱到但求每个结论都能说清为什么这么选。1. 文件系统到底在整条 I/O 链路上管什么1.1 从一次 inode 耗尽事故往前推先说开头那个空间没满写不进数据的问题根因其实就是文件系统内部有两套互相独立的资源账本空间块block和索引节点inode。每创建一个文件哪怕内容只有一个字节系统都要从 inode 池里分配一个 inode 来记录它的属主、权限、大小、时间戳和数据的物理位置同时再从数据块池里分配至少一个 block 存内容。这两本账各自记各自的所以完全可能出现块还多得很inode 用光了的情况。哪些业务容易踩这个坑典型的就是缓存目录、邮件队列、会话文件、海量小图、日志按秒切分的那种目录。一个目录里塞进去几百万个几 KB 的小文件块空间消耗不大inode 却是按个消耗的。这时候你用du -sh看不出问题用ls | wc -l数文件个数才有感觉最快的判断命令是df -i它会打印每个挂载点的 IUse%、IFree。如果 IUse% 到 100%块再空也没用。这时候能做的只有三件事清掉无用小文件、把数据挪到别的分区、或者重建文件系统时把 inode 数量调大。注意最后一条要重新 mkfs是有损操作别在生产上一时冲动。1.2 从应用到磁盘之间隔了几层理解文件系统最好先把它在链路上的位置摆正。你写文件调用的write()系统调用并不直接落到硬盘上中间要穿过好几层应用层 → 系统调用层 → VFS虚拟文件系统→ 具体文件系统ext4/xfs→ 通用块层 → I/O 调度器 → 块设备驱动 → 物理设备。这里最关键的一层是 VFS也就是 Virtual File System。Linux 最聪明的地方在于它用 VFS 抽象出一套统一的open/read/write/close接口上层的应用不管底层是 ext4、xfs 还是网络文件系统调用的都是一样的函数。你在代码里能通过/proc/filesystems看到当前内核支持的全部文件系统类型用cat /proc/filesystems就能列出来带nodev前缀的表示不需要挂载块设备比如 proc、sysfs、tmpfs。再往下一层是页缓存page cache。你写完数据内核先把它拷进内存里的页缓存标记为脏页然后再由内核线程kworker在合适的时候回写磁盘。这解释了为什么断电时写成功的文件可能内容是空的——应用返回成功只是说数据进了内存没进磁盘。想强制刷盘就要在关键位置调用sync、fsync、fdatasync。sync命令会把所有脏页刷下去但它只是发起并不保证执行完毕真正等它结束要配合sync; sync; sync这种老运维习惯或者直接盯cat /proc/meminfo | grep Dirty看脏页数量降到接近零。1.3 主流文件系统的定位差异生产环境里常见的那几个其实各有各的舒适区我整理了一张对照表参数是经验值具体以你内核版本的官方文档为准文件系统设计重心单文件上限参考能否缩小典型场景ext4兼容性与稳定性16 TB可以离线系统盘、通用服务器、嵌入式根文件系统xfs大文件与高并发8 EB不可以数据库、海量小文件、大容量数据盘btrfs快照与校验16 EB可以需要快照回滚、数据完整性校验的场景f2fs闪存友好3.94 TB有限制手机、嵌入式 eMMC/UFS 存储这里有个认知要纠正一下很多人觉得 ext4老性能差其实在中小规模、随机读写为主的通用负载下ext4 和 xfs 的差距经常在个位数百分比真正拉开距离的是超大文件顺序写、极高并发元数据操作、以及几 TB 以上容量时的扩展性。选型不能只看跑分还要看你团队的熟悉度、备份恢复工具链、以及出问题时谁能修。2. ext4 深度拆解为什么它至今还是默认选项2.1 磁盘布局与块组机制ext4 把整个分区切成一个个块组block group默认情况下每个块组管理的空间是 128 MB由块大小和块组内块位图能表示的位数决定。每个块组里有超级块备份、组描述符、块位图、inode 位图、inode 表、以及实际数据块。超级块只完整存在第一个块组的开头其余的块组保存备份这就是为什么 ext4 分区坏了还能用e2fsck -b 32768指定备份超级块去救。ext4 相比 ext3 引入了几个真正提升性能的机制extentext3 记录一个文件的数据块位置用的是间接块三级指针一个上 TB 的文件元数据量大得吓人。ext4 用 extent 描述从某个块开始连续 N 个块属于我一段连续空间一条记录元数据量骤降大文件顺序写性能明显改善。延迟分配delayed allocation应用写数据时ext4 不立刻决定落到哪个块而是先在页缓存里攒着等真正要回写时再一次性分配连续空间。这招大幅减少碎片但也带来一个副作用——进程崩溃时已经write()的数据可能丢失因为还没分配块。这个特性默认开启也是为什么我写完了断电却丢了的常见原因之一。flex_bg把多个块组的位图、inode 表集中放在一起元数据更集中减少了磁头/闪存随机寻址。想看清楚一个分区的布局最直接的命令是dumpe2fs -h /dev/sda1 # 只看超级块信息块大小、inode 数量、日志模式 dumpe2fs /dev/sda1 | head -100 # 看完整块组信息 tune2fs -l /dev/sda1 # 类似 dumpe2fs -h更常用tune2fs -l的输出里Block count、Inode count、Block size、Reserved block count、Filesystem features这几个字段最值得看。2.2 三种日志模式与数据安全取舍ext4 是日志文件系统但它记的是元数据日志还是数据日志可以通过挂载选项切换这是我认为最需要理解的一个点。三种模式差别巨大datajournal元数据和数据都先写日志再写最终位置。掉电恢复能力最强但写入路径变成两遍性能损失经常到一半以上。只有对数据极度敏感、吞吐要求不高的场景才用比如某些嵌入式配置存储。dataordered默认只把元数据记日志但保证数据块先于引用它的元数据提交。效果是掉电后不会出现文件变成垃圾内容或者元数据指向不属于自己的块这种严重损坏最多是丢最近没刷盘的写入。这是性能与安全的平衡点绝大多数场景保持默认即可。datawriteback只记元数据日志不保证数据先落盘。性能最高但掉电后可能出现文件大小对内容却是旧数据或零的情况。数据库自己做了 WAL 的时候会用这个模式把一致性交给数据库自己管。改法有两种。临时改重启失效mount -o remount,datawriteback /data。永久改就写进/etc/fstab的第四列。要提醒一句切换日志模式会让日志重新初始化务必先备份。判断当前生效的模式mount | grep sda1看有没有dataxxx没写就是 ordered。注意为一个数据库业务把整个分区切成 datajournal是很常见的过度设计。数据库通常自带事务日志和刷盘策略你再加一层全数据日志等于同一件事做了两遍吞吐白白砍半。2.3 mkfs 参数与计算过程先看一个我常用的小文件密集型分区的创建命令mkfs.ext4 -F -b 4096 -I 256 -i 8192 -m 1 -O extent,flex_bg,uninit_bg,dir_index,has_journal \ -E lazy_itable_init1,stride16,stripe-width48 -L data0 /dev/sdb1逐个说清楚为什么这么给-b 4096块大小 4 KB。这是绝大多数场景的甜蜜点太小元数据开销大太大浪费小文件的尾部空间。-I 256每个 inode 占 256 字节。ext4 默认就是 256除非你要用扩展属性比如 SELinux 上下文、ACL 大量使用时才考虑 512。-i 8192每 8192 字节空间分配一个 inode比默认的 16384 密一倍。这就是为小文件场景准备的。假设分区是 1 TB块大小 4 KB按默认 16384 算 inode 数量大约是 1TB / 16KB ≈ 6700 万个改成 8192 后翻倍到约 1.3 亿个。代价是 inode 表本身占空间也翻倍大概每 1 亿 inode 吃掉 25 GB 左右按 256 字节算。-m 1保留空间比例从默认 5% 降到 1%。默认保留块是给 root 应急用的非系统盘没必要留 5%。1 TB 盘 5% 就是 51 GB纯浪费。系统盘/建议保留数据盘可以压到 1% 甚至 0。-E stride16,stripe-width48这两个是 RAID 上必须给的参数。stride chunk大小 / 块大小stripe-width stride × 数据盘数量。以 4 块盘的 RAID5、chunk 64 KB、块大小 4 KB 为例stride 64/4 16数据盘是 4-13 块stripe-width 16 × 3 48。不给这两个值ext4 会按单盘逻辑分配RAID5 上写放大严重。提示-E和-O里的选项写错会导致 mkfs 直接失败但-i给得过小会让 mkfs 变慢、inode 表变大。现在很多发行版默认打开lazy_itable_init首次挂载后 inode 表在后台慢慢初始化所以 mkfs 很快就返回别以为它没干活等几分钟再跑测试更靠谱。2.4 挂载选项与在线扩容的坑挂载选项对性能的影响不亚于 mkfs 参数。几个我必调的/dev/sdb1 /data ext4 defaults,noatime,nodiratime,dataordered,commit30,barrier1 0 2noatime不更新文件的访问时间。默认的 relatime 已经很克制了但在高并发读的场景下关掉更省。有些老程序依赖访问时间做缓存淘汰用之前确认一下。nodiratime不更新目录访问时间。commit30把 journal 提交间隔从默认 5 秒放宽到 30 秒。写入合并更多吞吐提升但掉电可能丢最多 30 秒数据。数据库盘别这么干。barrier1保持写屏障开启保证日志提交顺序。除非你确定底层存储比如某些虚机磁盘、老 RAID 卡缓存不支持否则不要关。扩容这块。ext4 最舒服的一点是支持离线缩小和在线扩容。在线扩容对一个已挂载的 ext4growpart /dev/sda 1 # 如果是云主机先把分区表扩了 pvresize /dev/sda1 # 如果上面还叠了 LVM lvextend -l 100%FREE /dev/vg0/data resize2fs /dev/vg0/data # ext4 在线扩容demand 挂载状态也能跑顺序不能反路由是分区 → LVM → 文件系统一层层从上往下扩。反过来说要缩小 ext4 必须先umount再resize2fs再缩分区。这是有损操作务必先备份尤其别在根分区上试。3. XFS 深度拆解大文件与高并发场景的答案3.1 分配组设计与并发能力XFS 是 1993 年 SGI 为 IRIX 做的后来移植到 Linux天生就冲着大机、并行 I/O 去的。它的核心组织和 ext4 完全不同XFS 把整个文件系统划分成若干个分配组Allocation GroupAG。每个 AG 有自己的 inode 空间、空闲空间 B 树和一套独立的元数据可以看作一个小型文件系统。多个进程同时往不同 AG 写锁竞争天然就低这是 XFS 在高并发元数据操作和大规模并行写时比 ext4 强的主要原因。AG 数量是 mkfs 时定的之后不能改。mkfs.xfs会根据容量自动推算一个值经验上小盘给 4 个、大盘按每 4~16 GB 一个 AG 往上走但现在的 v5 格式对这个规则做了调整。如果你明确知道要跑极高并发可以手工指定mkfs.xfs -f -d agcount16 -l size128m -i maxpct10 -L data0 /dev/sdb1-d agcount16直接定死 16 个分配组。-l size128m日志区大小 128 MB。XFS 的日志默认放在文件系统内部大小由容量自动决定高写入场景适当调大能减少日志回绕等待。-i maxpct10inode 空间最多占整个文件系统的 10%。这里要解释一下 XFS 的动态 inode——它不像 ext4 那样在 mkfs 时把 inode 数量写死而是按需分配。所以 XFS 基本不会出现 inode 耗尽理论上受 maxpct 限制这是它面对海量小文件时的一个巨大优势。看当前 XFS 的实际参数用xfs_info /data它会打印出 blocksize、agcount、sectsz、logbsize 等比tune2fs -l对 ext4 的角色。3.2 v5 格式带来的几个现代特性XFS 现在默认创建的是 v5 格式也叫 CRC 格式带几个值得知道的特性元数据校验metadata checksum所有元数据块都带 CRC32 校验能主动发现静默损坏而不是等崩溃。reflink支持写时复制CoW和去重。做虚拟机镜像、容器镜像层的时候reflink copy 复制一个 10 GB 的文件几乎瞬间完成只占增量空间。命令是cp --reflinkalways。rmapbt反向映射 B 树是 reflink 和在线xfs_scrub的基础代价是元数据略微变大。创建时可以显式打开mkfs.xfs -f -m crc1,reflink1 -n ftype1 /dev/sdb1ftype1这个别关systemd、容器和很多现代软件依赖它判断目录项类型。3.3 不能缩小的现实以及在线扩容XFS 有个非常硬的限制只能扩不能缩。这是它的设计决定的——空间按 AG 打散B 树里到处是指针缩容要重排整个布局实现成本极高社区就没做。所以给业务分 XFS 分区要保守宁可一开始给大一点。扩容过程比 ext4 简单而且所有操作在线完成growpart /dev/sda 1 pvresize /dev/sda1 lvextend -l 100%FREE /dev/vg0/data xfs_growfs /data # 注意xfs_growfs 接受的是挂载点不是设备注意xfs_growfs传的是挂载点路径传设备名会报错这和resize2fs /dev/xxx的习惯正好相反从 ext4 转过来的人十有八九第一次会踩。3.4 从 ext4 迁到 XFS 的评估清单不是所有场景都值得迁。我自己做迁移决策时会过一遍下面这些判断判断维度倾向 ext4倾向 XFS单文件体量多在几百 MB 以下经常几 GB 到几十 GB并发写进程数几十个以内几百上千个文件总数量百万级以内千万级以上是否需要缩容有可能基本只扩不缩备份工具链依赖 ext4 快照工具的用 xfsdump/xfsrestore 或卷快照团队熟悉度熟熟我一般建议系统盘和通用应用盘保持 ext4纯数据盘、数据库盘、对象存储后端、日志汇聚盘优先 XFS。如果团队没人熟 XFS 的恢复流程那就别迁工具链的成熟度比那点性能更重要。4. 选型落地把文件系统真正装到生产上4.1 完整实操流程从裸盘到开机自动挂载这块我按实际动手顺序写一遍你可以照着抄。假设新增了一块/dev/sdb。第一步确认设备和分区lsblk -f # 看设备、分区、文件系统、UUID、挂载点 blkid /dev/sdb # 看设备的 UUID 和文件系统类型第二步分区如果整盘不分区也可以直接 mkfs 到 /dev/sdbparted -s /dev/sdb mklabel gpt parted -s /dev/sdb mkpart primary xfs 1MiB 100% partprobe /dev/sdb第三步创建文件系统XFS 为例mkfs.xfs -f -L data0 /dev/sdb1第四步挂载并写 fstab。这一步的关键是一定要用 UUID 而不是设备名因为设备名会随着插盘顺序、内核识别顺序变化重启后可能挂错。取 UUIDblkid -s UUID -o value /dev/sdb1然后写进/etc/fstabUUID你的uuid /data xfs defaults,noatime,nodiratime,logbsize256k 0 2写完后务必先用mount -a试一下看有没有报错、有没有报已挂载。直接重启去验证是个坏习惯fstab 写错会导致系统进 emergency mode远程机器就麻烦了。提示XFS 的logbsize256k在大写入场景能减少日志 I/O 次数但内存开销略增如果不是高写入负载保持默认即可别为了调而调。4.2 观测工具别靠感觉判断性能调完之后要有数据支撑靠dd测文件系统性能是很业余的做法——它绕过太多层也测不出随机和并发。正经的做法是fio看延迟分布而不是只看 IOPSfio --namerandwrite --ioenginelibaio --direct1 --bs4k \ --iodepth32 --rwrandwrite --numjobs4 --size4G \ --runtime60 --time_based --filename/data/test.fio --group_reporting运行时的系统级观测靠iostatiostat -x 1 10重点看三列%util设备忙不忙接近 100% 说明饱和、await平均 I/O 等待毫秒数、aqu-sz平均队列长度持续大于 1 说明排队严重。如果 %util 不高但 await 很高问题往往在队列深度或者存储后端不在文件系统本身。文件系统层面的使用率看这几个df -h # 块空间 df -i # inode stat -f /data # 文件系统的块大小、总块数、空闲块数4.3 挂载参数速查表参数适用作用风险noatimeext4/xfs不更新访问时间依赖 atime 的程序会不准nodiratimeext4/xfs不更新目录访问时间基本无commit30ext4放宽日志提交间隔掉电最多丢 30 秒logbsize256kxfs增大日志缓冲内存略增discard两者在线 TRIM部分 SSD 会卡顿建议用 fstrim 定时替代inode64xfs允许 inode 超过 32 位v5 默认已开allocsizexfs预分配大小小文件多时设大反而浪费discard这个我多说一句。它能让 SSD 在删除文件时立刻回收块但很多消费级 SSD 的 TRIM 实现同步阻塞导致删除操作变慢。更稳的做法是关掉 discard改用定时任务fstrim -av配合cron每天跑一次效果一样卡顿可控。5. 常见故障与排查实录5.1 inode 耗尽与空间没满却写不进症状df -h有剩余空间写入报No space left on device。排查顺序df -i /data # 看 IUse% find /data -maxdepth 2 -type d -exec sh -c echo $(ls -U $1 | wc -l) $1 _ {} \; | sort -rn | head上面那条 find 命令是找出哪个子目录里文件数量最多。找到之后要么清理要么归档。如果是日志类通常需要改应用的滚动策略比如从每秒一个文件改成按大小滚动。治本的做法是下次 mkfs 时降低-i的值。5.2 中文文件名乱码与解压问题从 Windows 打包过来的 zip在 Linux 上解压经常出现中文变成问号或乱码。根因是 zip 格式本身没有规定文件名编码Windows 的压缩工具默认用 GBK/CP936Linux 的unzip默认按 UTF-8 解释。解决办法unzip -O cp936 archive.zip # 指定用 GBK 解读文件名 # 或者用 7z它会自动探测 7z x archive.ziptar.gz 一般不存在这个问题但如果确实出现乱码往往是打包端 locale 设置不对可以用tar -tf archive.tar.gz | iconv -f gbk -t utf-8先看看文件名。5.3 掉电后的恢复流程文件系统掉电之后的处理ext4 和 XFS 的路径完全不同这点必须记住ext4日志会重放通常挂载时自动完成。如果挂载失败需要手动e2fsckumount /dev/sdb1 e2fsck -f -y -C 0 /dev/sdb1 # -f 强制检查-y 自动修复-C 显示进度超级块损坏时用备份超级块e2fsck -b 32768 -B 4096 /dev/sdb1XFS 没有e2fsck对应的东西用的是xfs_repair而且必须卸载后才能跑umount /data xfs_repair -v /dev/sdb1 xfs_repair -L /dev/sdb1 # 日志损坏时才加 -L 清日志这一步会丢未提交数据慎用xfs_repair前强烈建议先xfs_metadump做一份元数据镜像万一修复方向错了还有退路xfs_metadump /dev/sdb1 /backup/sdb1.mdump5.4 排查速查表现象最可能原因首查命令空间满写不进但 df 有空间inode 耗尽df -i文件写入后掉电内容为空数据在页缓存未刷盘应用加 fsync挂载报已挂载或类型错fstab 设备名变动blkid 改用 UUIDRAID 上写入性能差缺 stride/stripe-widthdumpe2fs 看 strideXFS 无法缩容报错设计限制无解备份重建扩容后 df 没变化漏了 resize2fs 或 xfs_growfs补执行文件系统层扩容删除大量小文件卡顿SSD TRIM 同步阻塞关 discard 改 fstrim6. 几条容易被忽略的实战经验6.1 sync、fsync 和 writeback 的边界很多人以为调用了write()数据就安全了其实只是进了页缓存。真正的落盘时机由内核的 writeback 机制控制/proc/sys/vm/dirty_ratio和dirty_background_ratio决定脏页攒到多少比例开始回写。默认一般 dirty_ratio 是 20%dirty_background_ratio 是 10%。意思是内存的 10% 脏页时后台开始慢慢刷到 20% 时应用自己的写操作会被阻塞去刷盘。如果服务器内存特别大比如 256 GB20% 就是 51 GB 脏页一次掉电能丢的数据量非常可观。所以内存越大越应该把这个比例调小比如sysctl -w vm.dirty_ratio10 sysctl -w vm.dirty_background_ratio5 sysctl -w vm.dirty_expire_centisecs1500要持久化就写进/etc/sysctl.d/下的配置文件。第三行的意思是脏页超过 15 秒就强制回写避免数据在内存里躺太久。6.2 嵌入式与容器场景下的叠加文件系统嵌入式 Linux 的根文件系统很多是只读的 squashfs 加一层可写的 overlayfs或者在 eMMC 上直接用 f2fs、ubifs。这时候你要注意overlayfs 只是个叠加层写操作最终落在可写层所在的底层文件系统上性能和容量都受底层约束。容器场景同理镜像层通常是只读的容器可写层落在宿主机某个 ext4 或 xfs 目录里。另外嵌入式设备上有个经典悲剧为了延长 Flash 寿命把根文件系统挂成只读但应用又要写配置于是频繁写日志分区导致坏块。处理办法是把高频写的内容放到 tmpfs 里定期同步到持久分区从根上减少擦写次数。6.3 备份和迁移的几个选择ext4 有dump/restore但更常用的是rsync加卷快照。XFS 官方那套是xfsdump/xfsrestore它支持增量备份能保留扩展属性和 ACL但它是针对 XFS 的跨文件系统恢复不行。实际生产里我更倾向在存储层做快照然后挂载快照走rsync -aHAX --numeric-ids复制这样和具体文件系统解耦迁移也方便。有一点要注意rsync不加-H会导致硬链接被打散不加-A会丢 ACL不加-X会丢扩展属性--numeric-ids则是避免用户名映射错乱做系统级迁移时这四个几乎必带。我自己在这些年里最大的体会是文件系统这块的默认值往往比调优值更值得信任。内核社区给的默认参数是权衡了无数种场景之后的结果我的大部分调优动作最后收益都没超过个位数百分比反倒是noatime、-m 1、正确的stride/stripe-width这几个简单操作带来的提升最实在。所以我的建议是先把基本盘做对——分区大小合理、fstab 用 UUID、监控里加上 inode 使用率、定时跑fstrim然后再谈那些花哨的参数。至于 ext4 和 xfs 之争等你真正遇到单文件上 TB 或者并发上万的那个量级答案自己就浮出来了。
返回列表