SPDK线程模型
一、reactor线程
与传统的reactor线程模型相比,SPDK的reactor在功能实现上还是有很大区别的,线程不在基于流水线形式进行作业,而是采用Run-To-Complete来做运行处理。
如图所示,每个reactor线程会绑定一个cpu core,线程的主体函数通过reactor_run来封装,运行期间主要做以下处理。
(1) 充当事件轮询器对event无锁队列进行轮询 reactor线程采用RTC方式运行,线程运行期间不能有上下文切换的逻辑发生,线程之间如果想要做使用交互则可通过event无锁队列来做请求中转(相关功能函数可参考spdk_event_allocate、spdk_event_call),每个event事件会传递一个函数指针,reactor线程获取到事件对象后,会对其所指向的功能函数做调用处理。
(2) 轮询每个协程的poller函数 协程的定义决定了reactor的运行行为,我们可以根据自己的需要来编写相关的协程函数,并将其注入到reactor线程模型,去做周期性的轮询处理。需要注意的是,协程函数的编写不能引入额外的锁操作(防止reactor线程出现上下文切换),也不能是处理起来特别耗时的同步动作(阻碍后续操作的执行),需要遵循异步无锁编程原则来对函数的功能做定义实现。
(3) 处理协程间通信 SPDK的协程采用spdk_thread进行封装(可通过spdk_thread_create函数来创建),与reactor线程类似,其内部也有一个无锁队列来负责接收其他协程发送过来的请求,这些队列统一由reactor线程去做轮询处理。(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
二、RDMA集成到reactor
为了实现Server端整体链路run to complete改造,可考虑将RDMA组件也一并集成到reactor线程模型中去,以此来实现请求的异步接受和处理,大体的集成方案如图所示。
首先由Listener线程负责感知客户端的连接请求,并创建与之对应的QP来负责与客户端进行交互,每个QP会绑定一个CQ,完成有关上下文的初始化操作之后,需要触发一个event事件给reactor线程(提交到线程的无锁队列),事件所对应的操作函数主要是注册一个RDMA Poller,来对创建好的CQ进行轮询(借助ibv_poll_cq函数),以便对客户端的request请求进行感知。
接收到请求之后的处理逻辑比较关键,不同于传统的reactor线程模型,请求被接收后会采用独立的handler来对其进行处理,SPDK则全部交由reactor线程内部来完成,从而避免了线程的上下文切换。
因此针对请求的处理不可以是一个特别耗时的同步动作,因为这样便会阻塞后续的请求,从而出现比较大的长尾时延问题。为此可考虑将请求的执行尽量做成异步,比如针对硬盘的访问操作,可借助NVMe用户态驱动来实现IO的异步处理。
这样从请求接收到最后response返回,整个RPC处理链路全部在reactor线程内部完成,中间没有任何上下文切换,也不会有锁同步所带来的overhead开销。
三、内核参数调优
可通过改写/proc/irq/*/smp_affinity文件,来避免中断控制器向某些CPU核(spdk的reactor线程所绑定的core)发送中断,从而使reactor线程的运行不受外界干扰。
还可通过isolcpus系统启动选项将部分CPU核隔离出来,使它们只被reactor线程使用。
原文链接:https://zhuanlan.zhihu.com/p/534235804