NVMe 命名空间:概念、创建与管理

命名空间,说白了就是 NVMe 协议里的“逻辑盘”。跟传统存储的 LUN 有点像,但灵活得多。我遇到过不少人,一上来就问“命名空间和分区有什么区别?”嗯,分区是对一块物理盘切逻辑段,而命名空间可以是整块盘、半块盘、甚至跨越多个 NVM 介质——它背后是一个逻辑块地址空间,由 NVM 子系统提供。

每个命名空间在驱动层面靠一个 NSID(Namespace Identifier)来区分。这个 ID 从 1 到 255 是管理员地址空间,内核态驱动一般用 1 到 127;厂商自定义范围 128+ 也常见。你想想看,一个控制器最多能挂 255 个命名空间?但硬件实现上往往更少,毕竟要留资源给别的控制器。

命名空间的核心属性

创建命名空间前,得先搞清楚它带哪些参数。我直接把几个关键属性整理成表,方便你们对照:

属性说明项目里踩过的坑
NSID1~255,0xFFFFFFFF 表示广播别用 0,那是保留值
大小(Size / Capacity)以 LBA 为单位的块数有的 SSD 要求对齐到 1MB
LBA 格式(LBAF)常用 512B、4KiB、8KiB用 4K 格式时 OS 得支持
元数据大小每个 LBA 额外带 0~128 字节Endurance Group 里常跑 64B 元数据
EUI64 / NGUID用于多路径识别共享命名空间时必须唯一
格式化 NVM(Format NVM)擦除后重新定义 LBA 格式会清数据,记得先备份

我个人习惯在调试时先跑 nvme list-ns 看看当前有多少命名空间,再 nvme id-ns /dev/nvme0n1 读具体属性。尤其是元数据大小,很多上层应用没有考虑要存额外的校验信息,结果写进去就直接卡死。

创建命名空间:从命令到流程

创建命名空间可以通过 NVMe-MI(带外管理)或者 Admin Command(带内)。最常用的是 admin 命令,比如 Linux 下 nvme 工具:

# 创建一个 64GiB 的命名空间,LBA 大小选 4KiB
nvme create-ns /dev/nvme0 \
  --nsze=16777216 \
  --ncap=16777216 \
  --flbas=0x02 --dps=0 --nmic=0

# 创建后附加到控制器 1
nvme attach-ns /dev/nvme0 \
  --namespace-id=1 \
  --controllers=0x1

参数 nsze 是逻辑块总数,ncap 是最大可用容量——多数情况设成一样。 flbas 定义了 LBA 格式索引:0x02 对应索引 2,如果盒子里事先有格式化好的 LBA 表就能用。我踩过一回:选了一个设备不支持的 flbas,返回 Invalid Field in Command,查半天才发现是厂家固件只放了索引 0 和 1。所以 创建前先 nvme id-ctrl --vendor-specific 看看支持哪些 LBAF。

我的经验:LBA 格式选 4KiB 对顺序读写更友好,但数据库页如果恰好是 8KiB 或 16KiB,会带来双倍映射开销。我在做全闪阵列时,统一用 512B 格式兼容性最高,不过元数据占得也多。

命名空间的生命周期管理

创建只是第一步。你还需要:

  • 附加(Attach):把命名空间绑定到一个或多个控制器上。一个控制器可以挂多个命名空间,一个命名空间也可以被多个控制器访问——这就是命名空间共享。
  • 分离(Detach):把命名空间从控制器拿掉,不删除,后续还能再附。
  • 删除(Delete):彻底销毁命名空间,设备上的数据也没了。

流程大致是:Create → Attach → Use → Detach → Delete。但我建议在 Delete 前先 nvme detach-ns,不然有的固件会返回 Namespace Not Ready。我曾经在生产环境遇到过,热拔盘前没 detach,结果 IO 一直挂在那,惊出一身冷汗。

命名空间共享与多路径

共享命名空间是高可用设计的核心。两个控制器可以同时看到同一个命名空间,配合 Reservation 协议来避免冲突。比如:

场景共享方式推荐 Reservation 类型
双控主备一个写、一个只读Reservation 持有者写,其他注册者可读
双活两个同时读写需 Persistent Reservation + 分布式锁
多路径访问同一主机多条 PCIe 路径通常不用 Reservation,靠 ANA(Asymmetric Namespace Access)

我曾经给一个银行核心存储系统加共享命名空间时,因为没有上 Reservation,两个主机同时写同一个 LBA 导致元数据崩溃,最终花了两天恢复。从那以后,我在所有共享命名空间上都强制启用 Namespace Reservation——不管硬件宣称有多强的原子写入能力。

警告:NVMe 共享命名空间不支持文件系统级别的缓存一致性。你如果用 ext4/XFS 同时挂载到两台主机,即使有 Reservation 也没用,文件系统自身不是多写者安全的。数据库场景一般走裸设备 + 存储级锁。

下面这张图展示了命名空间与 NVM 子系统内部的关系:

NVMe NVM 子系统 - 命名空间架构 控制器 1 PCIe 队列 / Admin 控制器 2 PCIe 队列 / Admin 命名空间 NSID 1 大小 512GiB | LBA 4KiB 命名空间 NSID 2 大小 256GiB | LBA 512B 共享命名空间 NSID 3 大小为 128GiB | 可被控制器1和控制器2同时访问 🔄 每个命名空间对接独立 LBA 映射表;共享命名空间需要 NVMe Reservation 或 ANA 支持。

从图里能看到:控制器与命名空间是多对多的关系。我见过一个极端案例——8 个控制器共享同一个命名空间,用于多机 IO 栈。结果没有 reservation,也没开 ANA,性能波动得厉害。后来改成主机侧做锁,才算稳定下来。说白了,共享是功能,但得靠配套机制才能用在生产环境。

命名空间格式化与重置

格式化(Format NVM)会擦除所有数据并重新设置 LBA 格式,相当于给命名空间来一次“出厂化”。命令:

nvme format /dev/nvme0n1 --lbaf=1 --ses=0
# ses=0 表示全部擦除,ses=1 只擦除用户数据保留元数据区域

注意:有些 NVMe 设备共享介质,格式化一个命名空间不会影响同介质里的其他命名空间。但也有廉价 SSD 只支持全片格式化——选型时得看清楚 spec。

实操建议:我在实验室测试不同 LBA 格式对性能的影响时,会准备一个 1GiB 的小命名空间做试验田。格式化成 512B、4KiB、8KiB 分别跑 FIO,对比 IOPS 和时延。这样不会动到生产数据,而且切换快。

嗯,命名空间管理说复杂也复杂,但核心就是理解逻辑与物理的映射。你设计的命名空间模式,会直接影响上层应用的扩展性和故障隔离。别嫌基础,我当年就因为没搞清楚 NSID 范围,在驱动里硬编码了 255 个命名空间支持,结果遇到某个盘只能同时亮 10 个命名空间,调试了整整一周。好好记住这些基础,后面聊多路径和虚拟化时才不会发懵。