前言
在前文[1]中,我们简述了SPDK中RBD bdev模块以及所依赖的librbd接口的调用方法。本文就SPDK RBD bdev结合Ceph RBD的使用进行更为深入的探讨,并结合实际的应用场景,总结性能调优的方法。
SPDK RBD bdev & Ceph RBD线程模型
SPDK RBD bdev结合Ceph RBD,可以将image定义为SPDK的块设备类型。通过SPDK框架对块设备的管理,我们实现了用户态驱动,并利用轮询机制,替换了中断模型,降低了延迟,提升了对块设备操作的性能。SPDK提供的RBD bdev是BDEV子系统中的一个模块。当前SPDK实现了10个子系统,结合BDEV子系统,RBD bdev可以被SPDK其他模块,比如iSCSI/vhost-user/NVMe-oF target等等所使用。BDEV提供了通用的用户态块设备存储抽象,其作用类似于内核当中的通用块层。它会屏蔽底层模块的具体实现,而对外提供统一的接口,整个RBD bdev的软件栈如图1所示。
图1. RBD bdev调用栈
SPDK调用librbd提供的调用接口,将在后台创建Ceph RBD相关的线程。在SPDK框架下,SPDK会利用到CPU的亲和性(CPU Affinity),绑定一个或几个CPU。这使得Ceph RBD创建的线程会与SPDK用到的线程相冲突,造成性能下降,也不符合SPDK独占某些核的设计理念。因此, SPDK利用了spdk_call_unaffinitized函数,将Ceph RBD中线程的创建和使用,放在了其他的核上。关于该函数的具体逻辑可参考文章[1]所述。这里的优化,对于其他也有类似自有线程的第三方bdev也有参考价值。
SPDK RBD bdev模块的使用需要配置rbd选项进行编译,具体可参考下述命令:
1. git clone https://github.com/spdk/spdk
2. cd spdk && git submodule update --init
3. ./scripts/pkgdep.sh
4. ./configure --with-rbd && make
在SPDK RBD bdev模块中,依据librbd暴露的接口,我们采用了以下4个步骤打开Ceph RBD image。
创建rados对象
1. rc = rados_create(&entry->cluster, user_id)
连接到rados
2. rc = rados_connect(entry->cluster)
根据pool_name创建对应的ioctx对象
3. rados_ioctx_create(*(rbd->cluster_p), rbd->pool_name, &rbd->io_ctx)
根据上一步创建的io_ctx打开image
4. rbd_open(rbd->io_ctx, rbd->rbd_name, &rbd->image, NULL)
其中步骤2在建立连接时,rados_connect()会在其调用过程中,创建RBD client需要的线程,如图2所示。
图2. Ceph RBD后台线程
用户程序在连接Ceph RBD过程中,会创建5个常驻线程和10个临时线程,即忽略TIME+约为0的临时线程 (注1)。常驻线程有两种,分别是io_context_pool和msgr-work-n线程。io_context_pool线程主要处理I/O请求,默认1个连接会产生2个此线程。msgr-work-n线程,我们也称为messenger线程,主要用于与server端OSD的通信,发送消息和接收处理消息,消息中包含了I/O请求。默认1个连接会产生3个此线程。而其他线程如log,service线程等的CPU占用率低ÿ