SStorpath
原理预计 30 分钟

文件系统:从 VFS 到 XFS 挂载参数

存储服务底下压着的还是本地文件系统,它的行为直接决定上层表现。

学完这节你能做到

  • 解释 VFS、inode、dentry cache 在读写路径中的位置
  • 说明日志(journal)如何影响写放大
  • 为存储节点选择合理的 XFS/ext4 挂载参数

分布式存储底下压着的还是本地文件系统

即使你运维的是 Ceph 或 GPFS,节点上仍然有本地文件系统在干活:MON 的 RocksDB 放在本地盘的 XFS 上,容器镜像放在本地盘上,日志也在本地盘上。而且理解本地文件系统的机制,是理解分布式文件系统的前提——CephFS 的 MDS 解决的问题,和 XFS 的 inode 解决的是同一类问题,只是尺度不同。

VFS:一层让一切都像文件的抽象

应用: open("/mnt/data/a.txt")
   ↓
VFS: 统一的 inode / dentry / file 抽象
   ↓
具体文件系统: XFS / ext4 / CephFS / NFS
   ↓
块层或网络

VFS 定义了三个核心对象,理解它们就理解了元数据开销从哪来:

对象是什么缓存在哪
inode文件的元数据:大小、权限、时间戳、数据块位置inode cache
dentry目录项:把文件名映射到 inodedentry cache
file一个打开的文件句柄,含当前偏移进程的 fd 表

打开 /mnt/data/a.txt 需要逐级解析:查 / 的 dentry → 查 mnt → 查 data → 查 a.txt。路径每深一层,就多一次查找。这就是为什么深目录结构在网络文件系统上会明显更慢——每一级都可能是一次网络往返。

# 查看 inode 和 dentry 缓存占用
cat /proc/slabinfo | grep -E 'dentry|inode' | head -5

# 或者更直观
slabtop -o | head -10
i元数据缓存也吃内存

一台缓存了几千万个 dentry 的机器,光 dentry cache 就能占掉几十 GB。这部分内存算在 buff/cache 里,可回收,但回收之后下一次 ls 就要重新去盘上读。这正是 CephFS MDS 缓存不足时性能锐减的同一个道理,只是发生在服务端而已。

日志(journal)与写放大

XFS 和 ext4 都是日志文件系统。它们的承诺是:元数据操作要么完整生效,要么完全没发生,掉电不会留下损坏的目录结构。

代价是同一份元数据要写两次:先写日志,再写正式位置。

创建一个文件:
  1. 写 journal:记录"我要分配 inode X,在目录 D 里加一项"
  2. journal 落盘(这一步可能触发 flush)
  3. 写实际的 inode 和目录块

ext4 提供三种日志模式,理解它们的差别就理解了写放大:

模式日志内容安全性性能
journal元数据 + 数据都写日志最高最慢,数据写两遍
ordered(默认)只记元数据,但保证数据先于元数据落盘
writeback只记元数据,不保证顺序掉电可能读到旧数据最快

XFS 只有类似 ordered 的模式,且日志实现更高效,这是它在大容量、高并发场景更受青睐的原因之一。

挂载参数:存储节点上真正该调的几个

# 格式化:XFS 通常直接用默认值即可,块大小 4K 适配绝大多数场景
mkfs.xfs -f /dev/nvme0n1p1

# 挂载
mount -o noatime,nodiratime,logbufs=8,logbsize=256k /dev/nvme0n1p1 /var/lib/ceph
参数作用建议
noatime不更新访问时间必开。否则每次读都会产生一次元数据写
nodiratime目录不更新访问时间建议开(noatime 已包含)
logbufs / logbsizeXFS 日志缓冲区元数据密集场景可调大
discard在线 TRIM不建议在生产开,改用定期 fstrim
×不要在生产开 discard

discard 会让每次删除都同步向 SSD 发 TRIM 命令,在删除密集时造成明显的延迟毛刺。标准做法是挂载时不加 discard,改用 fstrim 定时任务在业务低峰期批量执行:

systemctl enable fstrim.timer --now

小文件问题:为什么元数据比数据更难扛

存 100 万个 4KB 文件(共 4GB)和存 1 个 4GB 文件,数据量相同,但代价天差地别:

100 万小文件1 个大文件
inode 数量100 万1
元数据操作至少 100 万次 create1 次
空间浪费每个文件按块对齐,4KB 文件占满一个块几乎无
顺序读吞吐受随机 IOPS 限制,可能只有几百 MB/s接近盘的顺序上限
备份/迁移耗时以小时计以分钟计
# inode 也会耗尽,而且 df -h 看不出来
df -i
Filesystem        Inodes   IUsed     IFree IUse% Mounted on
/dev/nvme0n1p1  524288000 523998211    289789  100% /data     ← 空间没满,但文件建不了了
!磁盘没满却报 No space left on device

十有八九是 inode 耗尽。df -h 显示还有几 TB 空间,df -i 一看 IUse% 已经 100%。ext4 的 inode 数量在格式化时就固定了,事后无法扩容——只能备份数据重新格式化(mkfs.ext4 -i 调小每 inode 字节数)。XFS 的 inode 是动态分配的,不存在这个问题,这也是存储场景优先选 XFS 的一个实际理由。

检查点单选

一台机器 df -h 显示 /data 还有 3TB 可用,但应用写文件报 No space left on device。最该先查什么?

检查点多选

关于挂载参数,下面哪些做法适合存储节点?(多选)

这节课的落点

  • VFS 的 inode / dentry / file 三件套决定了元数据开销的来源
  • 路径每深一层就多一次查找,网络文件系统上尤其贵
  • 日志保证元数据一致性,代价是写放大;不要用 writeback 换性能
  • noatime 必开,discard 不开改用 fstrim.timer
  • 小文件的真正代价在元数据和 inode,不在容量;XFS 的动态 inode 是实际优势

延伸资料

  • ·Systems Performance, 2nd Edition — Brendan Gregg