首页 > 数据库 > 数据库开发技术 > SPDK对接Ceph性能优化
关键词: S PDK 、 NVM e OF 、 Ceph、 CPU 负载均衡
SPDK是intel公司主导开发的一套存储高性能开发套件,提供了一组工具和库,用于编写高性能、可扩展和用户态存储应用。它通过使用一些关键技术实现了高性能:
1. 将所有必需的驱动程序移到用户空间,以避免系统调用并且支持零拷贝访问
2. IO的完成通过轮询硬件而不是依赖中断,以降低时延
3. 使用消息传递,以避免 IO路径中使用锁
SPDK是一个框架而不是分布式系统,它的基石是用户态(user space)、轮询(polled-mode)、异步( asynchronous )和无锁的 NVMe驱动,其提供了零拷贝、高并发直接用户态访问SSD的特性。SPDK的最初目的是为了优化块存储落盘性能,但伴随持续的演进,已经被用于优化各种存储协议栈。S PDK 架构分为协议层、服务层和驱动层,协议层包含 NVM e OF T arget、vhost-nvme T arget、iscsi T arget、vhost-scsi T arget以及vhost-blk T arget等,服务层包含L V 、 Raid、A IO 、 malloc以及Ceph RBD 等, driver层主要是 NVM e OF initiator、N VM e PCI e、virtio以及其他用于持久化内存的driver等。

SPDK 架构
Ceph是目前应用比较广泛的一种分布式存储,它提供了块、对象和 文件( ) 等存储服务,SPDK 很早就支持连接Ceph RBD作为块存储服务,我们在使用SPDK测试RBD做性能测试时发现性能到达一定规格后无法继续提升,影响产品的综合性能,经过多种定位方法并结合现场与代码分析,最终定位问题原因并解决,过程如下。
1. 测试方法 :启动 SPDK nvmf_tgt并绑定0 ~7 号核, . /build/bin/nvmf_tgt -m 0xff ,创建 8个rbd bdev,8个nvmf subsystem,每个rbd bdev作为namespace attach到nvmf subsystem上,开启监听,initiator端通过nvme over rdma连接每一个subsystem,生成8个nvme bdev,使用fio对8个nvme bdev同时进行性能测试。
2. 问题 :我们搭建了一个 4 8 OSD 的 Ceph全闪集群,集群性能大约4 0 w IOPS ,我们发现最高跑到 2 0 w IOPS 就上不去了,无论增加盘数或调节其他参数均不奏效。
3. 分析定位 :使用 spdk_top显示0号核相对其他核更加忙碌,继续加压, 0 号核忙碌程度增加而其他核则增加不明显。

查看 poller显示rbd只有一个poller bdev_rbd_group_poll,与nvmf_tgt_poll_group_0都运行在id为2的thread上,而nvmf_tgt_poll_group_ 0 是运行在 0号核上的,故bdev_rbd_group_poll也运行在0号核。
[root@test]# spdk_rpc.py thread_get_pollers
{
"tick_rate": 2300000000,
"threads": [
{
"timed_pollers": [
{
"period_ticks": 23000000,
"run_count": 77622,
"busy_count": 0,
"state": "waiting",
"name": "nvmf_tgt_accept"
},
{
"period_ticks": 9200000,
"run_count": 194034,
"busy_count": 194034,
"state": "waiting",
"name": "rpc_subsystem_poll"
}
],
"active_pollers": [],
"paused_pollers": [],
"id": 1,
"name": "app_thread"
},
{
"timed_pollers": [],
"active_pollers": [
{
"run_count": 5919074761,
"busy_count": 0,
"state": "waiting",
"name": "nvmf_poll_group_poll"
},
{
"run_count": 40969661,
"busy_count": 0,
"state": "waiting",
"name": "bdev_rbd_group_poll"
}
],
"paused_pollers": [],
"id": 2,
"name": "nvmf_tgt_poll_group_0"
},
{
"timed_pollers": [],
"active_pollers": [
{
"run_count": 5937329587,
"busy_count": 0,
"state": "waiting",
"name": "nvmf_poll_group_poll"
}
],
"paused_pollers": [],
"id": 3,
"name": "nvmf_tgt_poll_group_1"
},
{
"timed_pollers": [],
"active_pollers": [
{
"run_count": 5927158562,
"busy_count": 0,
"state": "waiting",
"name": "nvmf_poll_group_poll"
}
],
"paused_pollers": [],
"id": 4,
"name": "nvmf_tgt_poll_group_2"
},
{
"timed_pollers": [],
"active_pollers": [
{
"run_count": 5971529095,
"busy_count": 0,
"state": "waiting",
"name": "nvmf_poll_group_poll"
}
],
"paused_pollers": [],
"id": 5,
"name": "nvmf_tgt_poll_group_3"
},
{
"timed_pollers": [],
"active_pollers": [
{
"run_count": 5923260338,
"busy_count": 0,
"state": "waiting",
"name": "nvmf_poll_group_poll"
}
],
"paused_pollers": [],
"id": 6,
"name": "nvmf_tgt_poll_group_4"
},
{
"timed_pollers": [],
"active_pollers": [
{
"run_count": 5968032945,
"busy_count": 0,
"state": "waiting",
"name": "nvmf_poll_group_poll"
}
],
"paused_pollers": [],
"id": 7,
"name": "nvmf_tgt_poll_group_5"
},
{
"timed_pollers": [],
"active_pollers": [
{
"run_count": 5931553507,
"busy_count": 0,
"state": "waiting",
"name": "nvmf_poll_group_poll"
}
],
"paused_pollers": [],
"id": 8,
"name": "nvmf_tgt_poll_group_6"
},
{
"timed_pollers": [],
"active_pollers": [
{
"run_count": 5058745767,
"busy_count": 0,
"state": "waiting",
"name": "nvmf_poll_group_poll"
}
],
"paused_pollers": [],
"id": 9,
"name": "nvmf_tgt_poll_group_7"
}
]
}
再结合代码分析, rbd模块加载时会将创建io_channel的接口bdev_rbd_create_cb向外注册,rbd bdev在创建rbd bdev时默认做bdev_examine,这个流程会创建一次io_channel,然后销毁。在将rbd bdev attach到nvmf subsystem时,会调用创建io_channel接口,因为nvmf_tgt有8个线程,所以会调用8次创建io_channel接口,但disk->main_td总是第一次调用者的线程,即nvmf_tgt_poll_group_0,而每个IO到达rbd模块后b dev_rbd_submit_request 接口会将 IO上下文调度到disk->main_td,所以每个rbd的线程都运行在0号核上。


综合环境现象与代码分析,最终定位该问题的原因是:由于 spdk rbd模块在创建盘时bdev_rbd_create_cb接口会将每个盘的主线程disk->main_td分配到0号核上,故导致多盘测试时C PU 负载不均衡,性能无法持续提高。
4. 解决方案 :既然问题的原因是 C PU 负载不均衡导致,那么我们的思路就是让 CPU 更加均衡的负责盘的性能,使得每个盘分配一个核且尽可能均衡。具体到代码层面,我们需要给每个盘的 disk->main_td分配一个线程,且这个线程均匀分布在0 ~7 号核上。我们知道 bdev_rbd_create_cb是在每次需要创建io_channel时被调用,第一次bdev_examine的调用线程是spdk主线程app_thread,之后的调用均是在调用者线程上执行,比如当rbd bdev attach到nvmf subsystem时,调用者所在线程为nvmf_tgt_poll_group_#,因为这些线程本身就是均匀的创建在0 ~7 号核上,故我们可以复用这些线程给 rbd模块继续使用,将这些线程保存到一个global thread list,使用round-robin的方式依次分配给每个盘使用,该方案代码已推送到 SPDK 社区: bdev/rbd: Loadshare IOs for rbd bdevs between CPU cores (I9acf218c) · Gerrit Code Review (spdk.io) 。打上该 patch后,可以观察到 CPU 负载变得均衡,性能突破 2 0 w,达到集群4 0 w能力。
5. 思考回溯 :该问题是如何出现或引入的?我们分析 rbd模块的合入记录,发现在 bdev/rbd: open image on only one spdk_thread · spdk/spdk@e1e7344 (github.com) 和 bdev/rbd: Always save the submit_td while submitting the request · spdk/spdk@70e2e5d (github.com) 对 rbd的结构做了较大的变化,主要原因是rbd image有一把独占锁 exclusive_lock ,通过 rbd info volumes/ rbd 0 可查看,这把锁的作用是防止多客户端同时对一个 image写操作时并发操作,当一个客户端持有这把锁后,其他客户端需要等待该锁释放后才可写入,所以多客户端同时写导致抢锁性能非常低,为此这两个patch做了两个大的改动:1)对每个rbd bdev,无论有多少个io_channel,最后只调用一次 rbd_open ,即只有一个 rbd客户端,参见接口 bdev_rbd_handle 的调用上下文; 2)对每个盘而言, IO 全部收敛到一个线程 disk->main_td发送给librbd。
因为每个盘的 disk->main_td均为第一个io_channel调用者的线程上下文,所以他们的线程都在同一个核上,导致I O 从上游到达 rbd模块后全部汇聚到一个核上,负载不均衡导致性能无法继续提高。
6. 总结 :在定位性能问题时, C PU 利用率是一个重要的指标, spdk_top是一个很好的工具,它可以实时显示每个核的繁忙程度以及被哪些线程占用,通过观察C PU 使用情况,结合走读代码流程,能够更快定位到问题原因。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70014251/viewspace-2898700/ ,如需转载,请注明出处,否则将追究法律责任。