14. 初识 SPDK
之前介绍了一些关于PCIe和NVMe相关的计算机底层知识。现在,我们进入正题,上升到软件开发界面,学习SPDK开发工具包。
简介
SPDK是由英特尔发起的,用于加速NVMe SSD作为后端存储使用的应用软件加速库。这个软件库的核心是用户态、异步、轮询方式的NVMe驱动。相比内核的NVMe驱动,SPDK可以大幅降低NVMe command的延迟,提高单CPU核的IOps,形成一套高性价比的解决方案,如SPDK的vhost解决方案可以被应用到HCI中加速虚拟机的NVMe I/O。

SPDK最早的项目代号为WaikikiBeach,全称是DPDK For Storage,2015年开源以后,改为SPDK。SPDK提供了一套环境抽象化库(位于 lib/env目录下),主要用于管理SPDK存储应用所使用的CPU、内存、PCIe等设备资源。DPDK作为SPDK默认的环境库,每次SPDK发布的新版
本都会使用最新发布的DPDK的稳定版本。
从目前来讲,SPDK并不是一个通用的适配解决方案。把内核驱动放到用户态,导致需要在用户态实施一套基于用户态软件驱动的完整I/O栈。文件系统毫无疑问是其中一个重要的话题,显而易见内核的文件系统,如ext4、Btrfs等都不能直接使用了。虽然目前SPDK提供了非常简单的文件系统blobfs/blostore,但是并不支持可移植操作系统接口,为此使用文件系统的应用需要将其直接迁移到SPDK的用户态“文件系统”上,同时需要做一些代码移植的工作,如不使用可移植操作系统接口,而采用类似AIO的异步读/写方式。
开发背景
在 NVMe SSD 时代,虽然 NVMe 协议已经大幅提升了存储性能,但在软件层面仍然存在一些瓶颈,SPDK 的研发正是为了解决这些问题,原因主要有以下几点:
传统 I/O 栈的限制
内核上下文切换和中断开销大:传统的 I/O 模型中,应用程序提交读写请求后进入睡眠状态,一旦 I/O 完成,中断就会将其唤醒。这种方式在 NVMe SSD 这种高速设备上会导致大量的内核上下文切换和中断处理,造成延迟和开销。对于低速设备(SATA HDD)而言,工作量主要集中在IO处理上,单位时间内并不会占用太多CPU。而像 NVME SSD 这种高速设备传输数据实在太快,单位时间内也就要大量占用CPU进行上下文切换,传统的IO数据处理方法已经不再适用频繁高速处理的设备。

软件栈无法充分利用 SSD 性能:Linux 内核的 I/O 栈是针对机械硬盘设计的,包含了许多针对机械硬盘的优化,如 page cache 等。这些优化在 NVMe SSD 上反而会成为性能瓶颈,因为 NVMe SSD 的性能远高于机械硬盘,传统 I/O 栈无法充分利用其性能。
SPDK 的优化方式
用户态驱动:SPDK 考虑在系统安全的情况下,将针对 IO 的上下文切换给砍掉,将设备驱动代码运行在用户态,避免了内核上下文切换和中断。这种方式节省了大量的处理开销,允许更多的时钟周期被用来做实际的数据存储。
轮询模式:SPDK考虑转换IO处理思路, 采用轮询模式代替传统的中断模式。在轮询模式下,应用程序提交读写请求后继续执行其他工作,以一定的时间间隔回头检查 I/O 是否已经完成。这种方式避免了中断带来的延迟和开销,并使得应用程序提高了 I/O 效率。当单位时间内IO传输完成数量远小于CPU响应次数时,中断自然是好办法;反观可之CPU应该主动增加响应次数才能满足IO处理效率,主动轮询便成为了主流。而过高的响应次数又会增加CPU的负担,此时就如第一条所说优化内核上下文切换到用户态。

无锁队列:SPDK 引入了无锁队列,使用 lock-free 编程,从而避免锁带来的性能损耗。无锁队列主要依赖 DPDK 的实现,其本质是使用 cas(compare and swap)实现了多生产者多消费者 FIFO 队列。使用无锁队列,保证一个IO线程绑定一个处理核心,不再让多个IO线程争用一个处理核心而导致的僧多粥少的局面。
与 RDMA 的结合:SPDK 在用户态实现 NVMe 驱动,天然能和 RDMA 结合。两者的队列能一一映射,能达到锦上添花的效果,与RDMA结合主要用于远程存储的情况,特别适合于大型分布式云存储。
使用场景
目前SPDK使用比较好的场景有以下几种。
· 提供块设备接口的后端存储应用,如iSCSI Target、NVMe-oF Target。之后我们主要关注 NVMe-oF Target
· 对虚拟机中I/O的加速,主要是指在Linux系统下QEMU/KVM作为Hypervisor管理虚拟机的场景,使用vhost交互协议,实现基于共享内存通道的高效vhost用户态Target。如vhost SCSI/blk/NVMe Target,从而加速虚拟机中virtio SCSI/blk及Kernel Native NVMe协议的I/O驱动。其主要原理是减少了VM中断等事件的数目(如interrupt、VM_EXIT),并且缩短了host OS中的I/O栈。
· SPDK加速数据库存储引擎,通过实现RocksDB中的抽象文件类,SPDK的blobfs/blobstore目前可以和RocksDB集成,用于加速在NVMe SSD上使用RocksDB引擎,其实质是bypass kernel文件系统,完全使用基于SPDK的用户态I/O栈。此外,参照SPDK对RocksDB的支持,亦可以用SPDK的blobfs/blobstore整合其他的数据库存储引擎。