传统的存储以磁盘和 SSD 为主,通过 PCIe 插入到各主机上,作为主机的个人存储设备. 在服务器中心,将多张磁盘插入到存储节点构建存储服务器,其他主机通过网络或通过 RDMA 存储前后端模式,从存储服务器上获得存储服务, 这便是常见的存储中心的池化方案. 当 CXL 2.0 出现之后,主机之间可以通过 CDFP 和 CXL SWITCH 形成新的互联方式, 在新的链接方式下,存储池化的模型也发生了变化.
在 CXL 池化方案中,在一个机柜内,服务器之间通过 CDFP 统一连接到顶部的 CXL SWITCH 上,服务器被分作 CPU、内存、存储、GPU 异构和网络节点,在 CPU 节点,服务器内部部署了高密的 CPU Core,内存服务器则插满了 CXL Type3 内存设备,存储服务器则插满了 CXL Type3 PMEM/DDR 设备作为存储节点,而 GPU 异构节点则插满了 GPU.
当一个任务下发下来,首先动态从计算节点热插指定数量的 CPU Core,接着从内存服务器热插指定容量的内存,然后从存储服务器节点热插指定容量的磁盘,最后根据需求热插指定数量的 GPU 和网卡, 通过上面的操作之后运行任务的环境弹性构建完毕. 当任务处理完毕之后,将弹性热插的资源全部归还给各模块.
在该方案中,存储节点的池化可以存在多种实现方案,本节重点介绍基于 CXL 的存储池化方案. 池化中两个基础功能就是: Pooling(隔离) 和 Sharing(共享). 首先是 Pooling(隔离),存储池可以根据需求弹性提供指定容量的存储设备,并且存储设备需要与其他主机之间隔离,也就是需求方独占(Exclusive) 存储设备. 其次是 Sharing(共享),存储池根据需求将同一个存储设备同时让多个主机之间共享,以便在特定场景下实现多主机之间数据共享.接下来重点介绍一下 Sharing(共享) 的实现原理.
在 CXL 架构中,存储服务器由管理节点和存储节点构成,存储节点由 CXL Type3 设备构成,CXL Type3 设备分作两类,首先一类是由 NVDIMM 颗粒构成的非易失 CXL Type3 PMEM 设备,其特点是可以掉电存储数据. 另外一类是由 Volatile DRAM 颗粒构成的易失 CXL Type3 DDR 设备. 管理节点在收到命令之后,可以将 CXL Type3 设备热插到指定主机,也可以将设备从指定主机热拔设备.
主机则通过 CDPF 连接到 CXL SWITCH,CXL SWITCH 下游则连接到存储节点的 CXL Type3 设备,CXL SWITCH 独立于主机,由 Fabric Manager(FM) 管理, FM 可以让多台主机同时连接到 CXL SWITCH,并让每个主机看到的 CXL SWITCH 是一个独立隔离的 VCS(Virtual CXL SWITCH),这就是 CXL 池化方案的基础.
FM 为每个主机创建了独立的 CXL 总线(PCIe 总线)之后,池化系统可以根据需求,从存储节点将指定容量的 CXL Type3 设备热插到 CXL 总线. 接下来 CXL Type3 支持 MHMLD 和 MHSLD,简单的来说 MHMLD 可以将一个 CXL Type3 设备虚拟成多个 CXL Type3 设备, 并且被虚拟出来的设备可以同时挂接到不同的 CXL 总线上. 同理 MHLD 可以将一个 CXL Type3 设备同时挂载到不同的 CXL 总线.
硬件上构建了一条隔离的 CXL 总线,以及共享的 CXL Type3 设备. 接下来 CXL Type3 设备热插到系统之后,系统内部识别到热插的 CXL 设备,接下来就是对该设备创建新的 CXL REGION,以及构建文件系统. 如果 CXL REGION 的模式配置为 FSDAX,那么可以选择 XFS 和 EXT4 文件系统,因为这类文件系统支持 FSDAX 模式. 如果 CXL REGION 的模式配置为 DEVDAX,则可以选择 FAMFS 文件系统,因为该文件系统支持 DEVDAX 模式.
当系统不在需要存储设备时,系统内部先做软件层面的热插插座,然后向池化系统发送热拔(HOT-REMOVE)操作. 池化系统收到热拔请求之后,将对应的 CXL Type3 设备从 CXL 总线上热拔掉,这样该 CXL Type3 设备在存储池子里变成空闲可用的存储节点. CXL 存储池化 Sharing(共享) 的好处是可以在主机之间通过文件系统共享数据,同时由于对于 DEVDAX 的支持,存储池化共享不再是持久存储,而是临时存储. 那么接下来通过一个实践案例讲解整个过程,实践案例在 BiscuitOS 上的部署逻辑是:
# 切换到 BiscuitOS 项目目录
cd /BiscuitOS
# 选择开发环境,如果已经选择过可以跳过,这里与 linux 6.10 X86 为例
make linux-6.10-x86_64_defconfig
# 通过 Kbuild 选择需要部署的应用程序
make menuconfig
[*] DIY BiscuitOS/Broiler Hardware --->
[*] CXL: Compute Express Link
CXL Hardware Topology (CXL2.0: x1 VCS + x1 Type3 PMEM) --->
(5120) CXL Type3 PMEM Size(MiB)
[*] CXL STORAGE POOLING
[*] Package --->
[*] HETEROGENEOUS MEMORY MANAGEMENT
[*] CXL STORAGE POOL: SHARING MODE --->
# 配置完毕保存,然后进行部署
make
# 切换到实践案例所在目录
cd /BiscuitOS/output/linux-6.10-x86_64/package/BiscuitOS-CXL-STORAGE-POOL-SHARING-default
# 准备依赖工具
make prepare
# 编译实践案例
make download
make build
首先启动第一台主机,当主机启动之后,首先使用 “lspci-common -vt” 查看 CXL 总线(PCIe 总线),可以看到 0000:0C 总线为 CXL 总线,其 CXL SWITCH 上只挂接了一个 CXL Type3 设备. 接下来向池化管理者发起弹性热插一块存储的命令:
# 切换路径
cd /BiscuitOS/output/linux-6.10-x86_64/package/BiscuitOS-CXL-STORAGE-POOL-SHARING-default/BiscuitOS-CXL-STORAGE-POOL-SHARING-default
# 发送热拔请求
./FManager.sh HOTPLUG HOST1
# 主机内部执行热拔命令
RunBiscuitOS.sh
# 查看 CXL 总线
lspci-common -vt
当在主机 1 里面执行 RunBiscuitOS.sh 脚本,脚本里包含了热插存储节点的命令,当命令执行完毕之后,再次使用 “lspci-common -vt” 查看 CXL 总线,可以看到新插入的存储节点(0000:10:00.0), 并使用 mount 命令将存储节点挂载到 “/mnt/BiscuitOS-FAMFS”, 该文件系统里包含了一个文件 BiscuitOS.txt, 接下着使用 APP 命令对该文件进行读写操作, 可以看到现在文件里的数据是 “THIS IS BISCUITOS-HOST1”. 接下来将在主机 2 上也热插一个存储节点,使用如下命令:
# 切换路径
cd /BiscuitOS/output/linux-6.10-x86_64
# 启动主机 2
./RunBiscuitOS-CXL.sh
# 主机 2 外新开一个 Docker 窗口发起发送热拔请求
./FManager.sh HOTPLUG HOST2
# 主机 2 内部执行热拔命令
RunBiscuitOS.sh
# 查看 CXL 总线
lspci-common -vt
当主机 2 其中之后执行 RunBiscuitOS.sh 脚本,脚本里包含了热插存储节点的命令,当命令执行完毕之后,再次使用 “lspci-common -vt” 查看 CXL 总线,可以看到新插入的存储节点(0000:18:00.0), 并使用 mount 命令将存储节点挂载到 “/mnt/BiscuitOS-FAMFS”,该文件系统此时包含一个文件 BiscuitOS.txt, 接下来使用 APP 命令对该文件进行读数据,读到的数据是 “THIS IS BISCUITOS-HOST1”, 此时只要其中一台主机写入数据,那么在另外一台主机同时可以读到刚刚写入数据,这就是两台主机同时挂载同一个磁盘文件系统, 也验证了存储池化的 Sharing(共享) 的特性. 以上便是 CXL 存储池化 Sharing(共享) 的完整实践案例.