SPDK 是 Intel 发布的存储性能开发工具集,旨在帮助存储 OEM 和 ISV 整合硬件,提升存储系统性能与效率。随着固态存储设备优势凸显,存储软件栈性能愈发关键,SPDK 应运而生。其工作依赖运行于用户态和轮询模式两大关键技术,前者避免内核上下文切换和中断以节省处理开销,后者改变 I/O 基本模型,避免中断延迟和开销。SPDK 由硬件驱动、后端块设备、存储服务、存储协议等数个子组件构成,各组件既能构建端对端体系结构,也可融入非 SPDK 架构。文章还介绍了 SPDK 的编译使用步骤及实现原理,并指出其适用场景。此外,阐述了 SPDK 的动机、基本原理,以及在 Ceph 中的使用情况,同时介绍了 Blobstore 和 BlobFS 的设计目标、工作方式及与 SPDK 的关系,还对比了与 GAE 相关的 3 种存储方法。
重要亮点
- SPDK 诞生背景与目标:固态存储设备在性能、功耗和机架密度上优势显著,取代传统磁盘设备趋势明显。但存储软件栈性能成为瓶颈,为助存储相关企业整合硬件,Intel 构建 SPDK,通过运用 Intel 多项技术,提升存储系统效率与性能,如借助硬件设计软件,实现每秒数百万次 I/O 读取。
- SPDK 关键技术:一是运行于用户态,设备驱动代码不在内核运行,避免内核上下文切换和中断,节省处理开销,让更多时钟周期用于实际数据存储,无论存储算法复杂与否,都能提升性能和降低延迟;二是轮询模式,应用程序提交读写请求后继续执行其他工作,定时检查 I/O 是否完成,避免中断开销,在固态设备时代,对提升 I/O 效率效果显著,可降低延迟、提高吞吐量。
- SPDK 组件构成:硬件驱动包含 NVMe Driver 和 Inter QuickData Technology 等;后端块设备有 NVMe over Fabrics initiator、Ceph RADOS Block Device 等多种;存储服务包括 Block device abstration layer 和 Blobstore;存储协议涵盖 iSCSI target、NVMe-oF target 等。从流程看,由网络前端、处理框架和存储后端组成,各部分协同工作,且部分组件可独立用于加速其他软件。
- SPDK 适用场景判断:判断 SPDK 组件是否适合自身体系结构,可从多方面考量。如存储系统基于 Linux 或 FreeBSD,且硬件平台为 Intel 体系结构;高性能路径运行在用户态,系统能合并无锁的 PMDs 到线程模型;当前使用 DPDK 处理网络数据包工作负载,且开发团队具备理解和解决问题的专业技能等场景较为适用。
下载代码:
git clone https://github.com/spdk/spdk --recursive
cd spdk
./configure
make && make install
#使用fio测试
cd spdk
./configure --with-fio=fio_dir/fio
make && make install
绑定设备
1.需要解绑设备,并格式化;相当于准备一块未格式化的nvme盘
执行前
root@spdk:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 7.3T 0 disk
└─sda1 8:1 0 7.3T 0 part /
sdb 8:16 0 7.3T 0 disk
sdc 8:32 0 7.3T 0 disk
sdd 8:48 0 3.8G 0 disk
└─sdd1 8:49 0 3.8G 0 part /boot
nvme0n1 259:0 0 1.5T 0 disk
root@spdk:~/github/spdk# HUGEMEM=8192 /root/github/spdk/scripts/setup.sh
0000:02:00.0 (8086 0953): nvme -> uio_pci_generic
0000:00:04.0 (8086 2f20): ioatdma -> uio_pci_generic
0000:80:04.0 (8086 2f20): ioatdma -> uio_pci_generic
0000:00:04.1 (8086 2f21): ioatdma -> uio_pci_generic
0000:80:04.1 (8086 2f21): ioatdma -> uio_pci_generic
0000:00:04.2 (8086 2f22): ioatdma -> uio_pci_generic
0000:80:04.2 (8086 2f22): ioatdma -> uio_pci_generic
0000:00:04.3 (8086 2f23): ioatdma -> uio_pci_generic
0000:80:04.3 (8086 2f23): ioatdma -> uio_pci_generic
0000:00:04.4 (8086 2f24): ioatdma -> uio_pci_generic
0000:80:04.4 (8086 2f24): ioatdma -> uio_pci_generic
0000:00:04.5 (8086 2f25): ioatdma -> uio_pci_generic
0000:80:04.5 (8086 2f25): ioatdma -> uio_pci_generic
0000:00:04.6 (8086 2f26): ioatdma -> uio_pci_generic
0000:80:04.6 (8086 2f26): ioatdma -> uio_pci_generic
0000:00:04.7 (8086 2f27): ioatdma -> uio_pci_generic
0000:80:04.7 (8086 2f27): ioatdma -> uio_pci_generic
执行后
root@spdk:~# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 7.3T 0 disk
└─sda1 8:1 0 7.3T 0 part /
sdb 8:16 0 7.3T 0 disk
sdc 8:32 0 7.3T 0 disk
sdd 8:48 0 3.8G 0 disk
└─sdd1 8:49 0 3.8G 0 part /boot
查看状态
root@spdk:~/github/spdk# /root/github/spdk/scripts/setup.sh status
Hugepages
node hugesize free / total
node1 1048576kB 1 / 2
NVMe devices
BDF Vendor Device NUMA Driver Device name
0000:02:00.0 8086 0953 0 uio_pci_generic -
I/OAT Engine
BDF Vendor Device NUMA Driver
0000:00:04.0 8086 2f20 0 uio_pci_generic
0000:80:04.0 8086 2f20 1 uio_pci_generic
0000:00:04.1 8086 2f21 0 uio_pci_generic
0000:80:04.1 8086 2f21 1 uio_pci_generic
0000:00:04.2 8086 2f22 0 uio_pci_generic
0000:80:04.2 8086 2f22 1 uio_pci_generic
0000:00:04.3 8086 2f23 0 uio_pci_generic
0000:80:04.3 8086 2f23 1 uio_pci_generic
0000:00:04.4 8086 2f24 0 uio_pci_generic
0000:80:04.4 8086 2f24 1 uio_pci_generic
0000:00:04.5 8086 2f25 0 uio_pci_generic
0000:80:04.5 8086 2f25 1 uio_pci_generic
0000:00:04.6 8086 2f26 0 uio_pci_generic
0000:80:04.6 8086 2f26 1 uio_pci_generic
0000:00:04.7 8086 2f27 0 uio_pci_generic
0000:80:04.7 8086 2f27 1 uio_pci_generic
IDXD Engine
BDF Vendor Device NUMA Driver
virtio
BDF Vendor Device NUMA Driver Device name
VMD
BDF Numa Node Driver Name
root@spdk:~# /root/github/spdk/scripts/setup.sh reset 重新bind nvme driver
0000:02:00.0 (8086 0953): uio_pci_generic -> nvme
0000:00:04.0 (8086 2f20): uio_pci_generic -> ioatdma
0000:80:04.0 (8086 2f20): uio_pci_generic -> ioatdma
0000:00:04.1 (8086 2f21): uio_pci_generic -> ioatdma
0000:80:04.1 (8086 2f21): uio_pci_generic -> ioatdma
0000:00:04.2 (8086 2f22): uio_pci_generic -> ioatdma
0000:80:04.2 (8086 2f22): uio_pci_generic -> ioatdma
0000:00:04.3 (8086 2f23): uio_pci_generic -> ioatdma
0000:80:04.3 (8086 2f23): uio_pci_generic -> ioatdma
0000:00:04.4 (8086 2f24): uio_pci_generic -> ioatdma
0000:80:04.4 (8086 2f24): uio_pci_generic -> ioatdma
0000:00:04.5 (8086 2f25): uio_pci_generic -> ioatdma
0000:80:04.5 (8086 2f25): uio_pci_generic -> ioatdma
0000:00:04.6 (8086 2f26): uio_pci_generic -> ioatdma
0000:80:04.6 (8086 2f26): uio_pci_generic -> ioatdma
0000:00:04.7 (8086 2f27): uio_pci_generic -> ioatdma
0000:80:04.7 (8086 2f27): uio_pci_generic -> ioatdma
查找traddr(参考 https://www.jianshu.com/p/eeaf81ffb7b5)
root@spdk:~# lspci | grep SSD
02:00.0 Non-Volatile memory controller: Intel Corporation PCIe Data Center SSD (rev 01)
root@spdk:~/ceph-deploy# lspci -mm -n -D -d 8086:0953
0000:02:00.0 "0108" "8086" "0953" -r01 -p02 "8086" "3709"
注意
由于spdk已经提前从系统中unbind了nvme设备,所以/dev/下是没有nvme设备的,必须指定到具体的pcie的接口上,fio的jobfile中的filename需要使用key=val的模式(这个地方有点怪异)
trttype:有pcie和rdma两种
traddr:pcie的“domain:bus:device:function”格式,可以用lspci命令查看对应的nvme设备在那个总线上,一般单台机器的domain都是0000
ns:namespace的id
spdk做性能测试时,对每个namespace会绑定一个lcore,所以fio的thread只能等于1
fio测试random的io时,需要设置norandommap=1 ,防止fio的random map影响性能
fio配置
root@spdk:~/github/spdk# cat /root/github/spdk/examples/nvme/fio_plugin/example_config.fio
[global]
ioengine=spdk
thread=1
group_reporting=1
direct=1
verify=0
time_based=1
ramp_time=0
runtime=2
iodepth=128
rw=randrw
bs=4k
[test]
numjobs=1
filename=trtype=PCIe traddr=02.00.0 ns=1
问题
提示:INFO:Requested 1024 hugepages but 8192 already allocated
root user memlock limit: 8MB
This is the maxinum amount of memory you will be
able to use whit DPDK and VFIO if run as user root
To change this, please adjust limits.conf memlock limit for user root;
查看一下文章中的内容;
SPDK 存储性能开发套件
SPDK.liblightnvm 提供了一个使用 英特尔存储性能开发套件(Intel SPDK) 实现的内核绕过后端。
安装 SPDK
在最新的Ubuntu长期支持版系统上,运行以下命令从源码安装SPDK:
# Make sure system is up to date
sudo apt-get update && sudo apt-get upgrade && sudo apt-get dist-upgrade
# Clone SPDK into /opt/spdk
sudo git clone https://github.com/spdk/spdk /opt/spdk
sudo chown -R $USER:$USER /opt/spdk
# Go to the repository
cd /opt/spdk
# Checkout the v18.07 release
git checkout v18.07
git submodule update --init
# Install dependencies
sudo ./scripts/pkgdep.sh
sudo apt-get install uuid-dev
# Configure and build it
./configure
make -j $(nproc)
# Check that it works
./test/unit/unittest.sh
# Install DPDK
cd /opt/spdk/dpdk
sudo make install
# Install SPDK
cd /opt/spdk
sudo make install
上一个 unittest.sh
命令的输出应该为:
=====================
All unit tests passed
=====================
解除设备绑定并设置内存
通过运行以下命令,将配置大页并使设备从内核NVMe驱动程序分离:
sudo HUGEMEM=8192 /opt/spdk/scripts/setup.sh
这应该输出类似内容:
0000:01:00.0 (1d1d 2807): nvme -> vfio-pci
如果上述内容是从setup.sh
输出的其他内容,例如:
0000:01:00.0 (1d1d 2807): nvme -> uio_generic
Or: 或者:
Current user memlock limit: 16 MB
This is the maximum amount of memory you will be
able to use with DPDK and VFIO if run as current user.
To change this, please adjust limits.conf memlock limit for current user.
## WARNING: memlock limit is less than 64MB
## DPDK with VFIO may not be able to initialize if run as current user.
然后查阅关于无限制启用“VFIO”的说明。
重新绑定设备
运行以下命令:
sudo /opt/spdk/scripts/setup.sh reset
输出内容应类似于:
0000:01:00.0 (1d1d 2807): vfio-pci -> nvme
设备标识符
由于设备在 /dev
中不再可用,因此将使用 SPDK 表示法来表示PCI ID,例如 traddr:0000:01:00.0
,例如使用命令行界面:
sudo nvm_dev info traddr:0000:01:00.0
并且使用API的话,情况会类似如下:
...
struct nvm_dev *dev = nvm_dev_open("traddr:0000:01:00.0");
...
构建支持SPDK的liblightnvm
有了SPDK后,使用以下命令配置liblightnvm的构建:
make spdk_on configure build
将您的源文件与liblightnvm和SPDK相链接
Invoke like so: 调用方式如下:
gcc hello.c -o hello \
-fopenmp \
-llightnvm \
-lspdk_nvme \
-lspdk_util \
-lspdk_log \
-lspdk_env_dpdk \
-lrte_bus_pci \
-lrte_eal \
-lrte_mempool \
-lrte_mempool_ring \
-lrte_pci \
-lrte_ring \
-lrt \
-ldl \
-lnuma \
-luuid
上述内容编译了快速入门指南中的示例,请注意,代码中有一个硬编码的设备标识符,你必须更改此标识符以匹配 SPDK 标识符。
无限制启用VFIO
如果nvme
重新绑定到uio_generic
而不是vfio
,那么VT-d可能不受支持或已禁用。无论哪种情况,尝试以下两个步骤:
- 验证您的CPU支持VT-d且已在BIOS中启用。
- 通过提供内核选项来启用内核。如果你使用的不是英特尔CPU,请查阅关于为你的CPU启用VT-d/IOMMU的文档。
- 增加限制,打开
/etc/security/limits.conf
并添加:
* soft memlock unlimited
* hard memlock unlimited
root soft memlock unlimited
root hard memlock unlimited
完成这些步骤后,执行以下命令:
dmesg | grep -e DMAR -e IOMMU
应包含:
[ 0.000000] DMAR: IOMMU enabled
并且这条这条命令:
find /sys/kernel/iommu_groups/ -type l
输出应类似于:
/sys/kernel/iommu_groups/7/devices/0000:00:1c.5
/sys/kernel/iommu_groups/5/devices/0000:00:17.0
/sys/kernel/iommu_groups/3/devices/0000:00:14.2
/sys/kernel/iommu_groups/3/devices/0000:00:14.0
/sys/kernel/iommu_groups/11/devices/0000:03:00.0
/sys/kernel/iommu_groups/1/devices/0000:00:01.0
/sys/kernel/iommu_groups/1/devices/0000:01:00.0
/sys/kernel/iommu_groups/8/devices/0000:00:1d.0
/sys/kernel/iommu_groups/6/devices/0000:00:1c.0
/sys/kernel/iommu_groups/4/devices/0000:00:16.0
/sys/kernel/iommu_groups/2/devices/0000:00:02.0
/sys/kernel/iommu_groups/10/devices/0000:00:1f.6
/sys/kernel/iommu_groups/0/devices/0000:00:00.0
/sys/kernel/iommu_groups/9/devices/0000:00:1f.2
/sys/kernel/iommu_groups/9/devices/0000:00:1f.0
/sys/kernel/iommu_groups/9/devices/0000:00:1f.4
And SPDK setup: 以及SPDK设置:
sudo HUGEMEM=8192 /opt/spdk/scripts/setup.sh
Should rebind the device to vfio-pci
, eg.:应将设备重新绑定到 vfio-pci
,例如:
0000:01:00.0 (1d1d 2807): nvme -> vfio-pci
HUGEPAGES检查并手动更改 可用内存,也称为
安装脚本提供了环境变量,以通过[未提及具体方式]来控制可用内存量。但是,如果你想手动更改或只是查看配置,请参考以下内容。
通过运行以下命令检查系统配置:
grep . /sys/devices/system/node/node0/hugepages/hugepages-2048kB/*
如果你尚未运行设置脚本,那么它很可能输出:
/sys/devices/system/node/node0/hugepages/hugepages-2048kB/free_hugepages:0
/sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages:0
/sys/devices/system/node/node0/hugepages/hugepages-2048kB/surplus_hugepages:0
运行设置脚本后,它应该输出:
/sys/devices/system/node/node0/hugepages/hugepages-2048kB/free_hugepages:1024
/sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages:1024
/sys/devices/system/node/node0/hugepages/hugepages-2048kB/surplus_hugepages:0
这表明有大小为 的大页可用,也就是说,总共可以使用2GB。
增加可用于的内存的一种方法是增加大页的数量。例如,通过将增加到 ,将内存从2GB增加到8GB:
echo "4096" > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
完成此操作后,检查配置应输出:
/sys/devices/system/node/node0/hugepages/hugepages-2048kB/free_hugepages:4096
/sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages:4096
/sys/devices/system/node/node0/hugepages/hugepages-2048kB/surplus_hugepages:0