相信很多做存储的同学都听说过SPDK,它是Intel开发的一套开源存储栈。SPDK的全称为存储高性能开发包(Storage Performance Development Kit),从名称可以看出SPDK其实就是一个第三方的程序库。但是这个程序库却是非常强大的,下图是SPDK的软件模块图,从该图可以看出,几乎囊括了整个存储栈。
其实SPDK最早开发出来得时候功能还是比较单一的,它是作为一个用户态的NVMe驱动来发布的。这一点可以通过阅读SPDK的发布日志得到。但是到目前,SPDPK包含三层数十个功能组件。其底层是驱动层,包括NVMe驱动和经过网络的NVMe-oF驱动,这也是SPDK最开始发布的内容。
中间层则是SPDK开发的一个存储服务层,这里最核心的是提供了一个块设备抽象层。同时,在该层还实现了块存储服务常见的功能,比如逻辑卷、快照和克隆等等。
最上层则是存储协议层,包括常见的如iSCSI协议,Linux网络块存储nbd,以及NVMe相关的NVMe-oF等。通过该层的功能,主机端可以通过存储协议以远程的方式访问NVMe提供的存储功能。
我们从架构回到具体实现,如下图是SPDK的目录结构。在该目录树中,核心的三个目录分别是app、lib和module,这里面包含着SPDK中比较核心的代码。从目录名称上我们也大概能猜出其中代码的大致功能。
其中app目录包含着一些应用程序的源代码,其中包含iscsi_tgt、nvmf_tgt和spdk_dd等子目录,每个子目录可以编译成一个可以独立执行的应用程序。例如iscsi_tgt,这个其实是一个iscsi的Target端,它本身也是一个开源项目。SPDK借鉴了该该开源项目,并且在其上做了大量的优化。由于这些应用都是可以独立运行的,因此我们也可以以这些应用的代码作为切入点来学习SPDK的代码实现。
另外一个目录是lib目录,从名称上来看可以知道该目录包含一个功能集合,这是SPDK最核心的代码。SPDK的代码组织还是比较清晰的,很多核心功能都以库的形式提供。比如SPDK中比较核心的事件(event)和线程模型(thread)等。
其中module目录也是非常重要的目录,其中包含的内容与上述架构图中的存储服务相对应。从上图我们可以看到诸如加密、逻辑卷、RAID、Linux AIO和Ceph RBD等。
除了上面介绍的三个目录外,还有一个目录中的代码是值得关注的,这就是examples目录。这个目录中的代码虽然不是SPDK的核心代码,但是我们可以通过其中示例来学习SPDK的实现细节。
上面都是从理论层面来介绍SPDK的,感觉还是不太接地气。从程序员的角度来说更喜欢实际动手操作一下。接下来我们从实操层面介绍一下SPDK。首先我们可以安装一个Ubuntu的虚拟机,在虚拟机上进行相关的学习。这里我们使用的版本是Ubuntu20.04,其他版本不确定释放可以正常运行。
首先我们需要下载一下SPDK的代码,SPDK的代码就放在github上,我们可以通过git命令clone一个本地库。
git clone https://github.com/spdk/spdk --recursive
由于SPDK依赖很多第三方的软件库,所以我们需要安装一下。大家不必担心安装这些软件库有多难,因为SPDK已经给我们提供了工具。我们可以通过下面命令自动安装依赖的软件包:
sudo scripts/pkgdep.sh
上述命令可以安装一个最小依赖包,也可以通过下面命令安装所有的依赖软件包:
sudo scripts/pkgdep.sh –all
安装完成依赖包后就可以编译SPDK的源代码了,编译过程也是非常简单的,我们可以执行如下两条命令完成SPDK的编译。
./configure
make
编译完成后可以运行一下SPDK自带的单元测试用例库,在运行该用例库的时候可能会看到不少错误信息。不过没有关系,只要我们在最后看到运行成功的提示信息就可以了。
./test/unit/unittest.sh
如果一切没有问题,说明SPDK已经编译成功了。在运行SPDK示例之前,我们需要进行一个基本的设置,具体命令如下:
sudo scripts/setup.sh
上述命令主要是释放内核对NVMe设备的管理,当然也包含一些其他设置。完成上述设置后,我们就具备一个运行SPDK应用的基本环境了。接下来我们看一下如何运行一个基于SPDK的基本应用。
做为一个存储工程师,IP-SAN是最常见的应用场景了。我们就看一下如何通过SPDK构建一个iSCSI目标端应用,并且通过iSCSI来进行访问。如上图是IP-SAN的拓扑,计算节点用过以太网与存储系统建立连接,访问存储系统上的存储资源。在存储端需要运行一个称为目标器(Target)的程序。
对于SPDK来说,整体拓扑是一致的,但略有差异。目标器服务的存储资源LUN对应的后端为一个bdev设备,本文以Malloc设备为例,具体如上图所示。另外,在SPDK中已经具备一个iSCSI目标器程序,也就是iscsi_tgt。我们可以利用该程序入门SPDK,导出bdev设备给计算节点作为块设备。首先我们运行该程序,具体需要执行如下命令:
./build/bin/iscsi_tgt
该程序运行后相当于启动了一个目标器服务,同时该命令不会退出,因此我们需要另外启动一个窗口进行后续的配置。
首先需要有基本的存储资源,我们这里通过创建一个Malloc类型的bdev设备,而不是使用NVMe设备。这里创建一个块大小为512字节,64MB存储空间的bdev设备,具体命令如下所示:
sudo ./scripts/rpc.py bdev_malloc_create -b Malloc0 64 512
然后是在目标器服务中创建一个端口组(port group),这里端口组的ID为1,地址为虚拟机的IP地址。
sudo ./scripts/rpc.py iscsi_create_portal_group 1 192.168.2.173:3260
再然后是创建一个启动器组,其ID为2,后面的地址表示允许接入该目标器服务的IP地址。本文选用的是跟目标器相同的虚拟机,所以是相同的地址。当然也可以使用另外一个新的虚拟机,这个时候就需要更改该IP地址了。
sudo ./scripts/rpc.py iscsi_create_initiator_group 2 ANY 192.168.2.173
最后是在目标器服务中创建一个目标器,其名称是disk1,同时可以有一个别名“Data Disk1”。在该目标器中同时创建一个LUN0,其所使用的bdev设备是前面创建的Malloc0。同时建立该目标器与端口组1和启动器组2的关联。也就是说,在上述配置的IP地址上的启动器可以通过端口组访问该目标器了。
sudo ./scripts/rpc.py iscsi_create_target_node disk1 "Data Disk1" "Malloc0:0" 1:2 64 -d
万事俱备,接下来我们就可以通过iSCSI协议来访问配置的资源了。在虚拟机中执行如下命令发现我们新创建的目标器。这个命令就是一条普通的iSCSI命令,与SPDK无关。
sudo iscsiadm -m discovery -t sendtargets -p 192.168.2.173
执行上述命令可以得到如下结果,可以看到这里的disk1正式我们前面创建的目标器的名称。
192.168.2.173:3260,1 iqn.2016-06.io.spdk:disk1
发现目标器后我们就可以登录目标器了。执行如下命令,我们就可以登录目标器,也就可以访问存储资源了。
sudo iscsiadm -m node –login
登录成功后会返回如下内容。
Logging in to [iface: default, target: iqn.2016-06.io.spdk:disk1, portal: 192.168.2.173,3260]
Login to [iface: default, target: iqn.2016-06.io.spdk:disk1, portal: 192.168.2.173,3260] successful.
此时我们可以通过fdisk命令来查看一下本地的块设备列表,可以看到这里多出一个名为sdb的块设备,大小为64MB。
至此,我们对SPDK做了一个整体的介绍,从架构到代码结构,然后又实操了一把。相信大家对SPDK有了一个基本的认识了。但回头想想,Linux内核其实已经实现上述功能了,为什么要在用户态实现这么一套系统呢?
Intel官方给出的解释是内核的处理太过笨重了,NVMe的性能并没有得到充分的发挥。我们基于虚拟机的环境并不能测试性能,但是我们可以看一下Intel官方的测试数据。下面两张图分别是4KB随机读和4KB随机写的性能对比图。通过可以看到通过SPDK的性能是Linux内核的2倍左右。可以看到,SPDK在提高性能方面确实有不错的效果。