跳票许久许久的LD_PRELOAD
功能模块(后续以 libff_syscall.so
代替)在 F-Stack dev 分支的 adapter/sysctall
目录下已经提交,支持 hook 系统内核 socket 相关接口的代码,降低已有应用迁移到 F-Stack 的门槛。下面将分进行具体介绍, 主要包括libff_syscall.so
相关的架构涉及其中的一些思考,支持的几种模式以及如何使用等内容。
总体结论:
libff_syscall.so
相关代码即可适配。【注意】目前 libff_syscall.so
功能尚不完善,仅供测试使用,欢迎所有的开发者一起进行完善。
libff_syscall.so
的编译先设置好FF_PATH
和PKG_CONFIG_PATH
环境变量
export FF_PATH=/data/f-stack
export PKG_CONFIG_PATH=/usr/lib64/pkgconfig:/usr/local/lib64/pkgconfig:/usr/lib/pkgconfig
在adapter/sysctall
目录下直接编译即可得到ibff_syscall.so
的相关功能组件
cd /data/f-stack/adapter/sysctall
make clean;make all
ls -lrt
fstack
libff_syscall.so
helloworld_stack
helloworld_stack_thread_socket
helloworld_stack_epoll
helloworld_stack_epoll_thread_socket
helloworld_stack_epoll_kernel
下面将分别进行介绍各个组件的主要作用
fstack 应用程序对标的是标准版 F-Stack 中的应用程序,其运行与普通的 F-Stack 应用程序完全相同,包括配置文件及其多进程(每进程即为一个实例)的运行方式等。
fstack 应用程序的作用主要是底层对接 F-Stack API,其主函数ff_handle_each_context
即为普通 F-Stack 应用的用户层 loop 函数,非空闲时或每间隔 100us (受 HZ
参数影响) 时会调用该函数去循环处理与 APP 对接的上下文,如果 APP 有对应的 API 请求,则调用实际的 F-Stack API 进行处理。
与 libff_syscall.so
用户应用进程间通信使用 DPDK 的 rte_malloc
分配的 Hugepage 共享内存进行。
该函数对 libff_syscall.so
的整体性能有至关重要的影响,目前是复用了 F-Stack 主配置文件(config.ini)中的 pkt_tx_dalay
参数,死循环并延迟该参数指定的值后才会回到 F-Stack 的其他处理流程中。
如果想提高 libff_syscall.so
的整体性能,那么fstack
实例应用程序与 APP 应用程序的匹配十分重要,只有当一个ff_handle_each_context
循环中尽量匹配一次循环的所有事件时才能达到最优的性能,这里需要调十分精细的调优,但是目前还是粗略的使用 pkt_tx_dalay
参数值。
【提示】pkt_tx_dalay
参数的默认值为 100us, 较适合长连接的场景。如果是 Nginx 短链接的场景,则应考虑设置为 50us,可以可获得更好的性能。当然不同的用用场景如果想达到最优的性能,可能需要业务自行调整及测试。复用该参数也只是临时方案,后续如果有更优的方案,则随时可能进行调整。
该动态库主要作用是劫持系统的 socket 相关接口,根据 fd 参数判断是调用 F-Stack的相关接口(通过上下文 sc 与 fsack 实例应用程序交互)还是系统内核的相关接口。
与fstack
实例应用进程间通信使用 DPDK 的 rte_malloc
分配的 Hugepage 共享内存进行。
【注意】在第一次调用相关接口时分配相关内存,不再释放,进程退出时存在内存泄漏的问题,待修复。
F-Stack用户的应用程序 (如 helloworl 或 Nginx)设置 LD_PRELOAD
劫持系统的 socket 相关 API 时使用,即可直接接入 F-Stack 开发框架,可以参考如下命令:
export LD_PRELOAD=/data/f-stack/adapter/syscall/libff_syscall.so
当然如果是改造用户的 APP 使用 kqueue
代替 Linux 的 epoll 相关事件接口时,也可以在用户 APP 中直接链接该运行库, 可以参考相关示例程序helloworld_stack
和helloworld_stack_thread_socket
对应的源文件main_stack.c
和main_stack_thread_socket.c
,因为不是使用的LD_PRELOAD
, 所以本文档不再详细介绍。
其他编译生成的hello_world
开头的可执行文件为当前libff_syscall.so
支持的几种不同运行模式的相关演示程序,下一节进行具体介绍。
为了适应不同应用对 socket 接口的不同使用方式,降低已有应用迁移到 F-Stack 的门槛,并尽量提高较高的性能,目前 F-Stack 的 libff_syscall.so
主要支持以下几种模式,支持多线程的 PIPELINE 模式、线程(进程)内的 RTC(run to completion)模式、同时支持 F-Stack 和内核 socket 接口的 FF_KERNEL_EVENT 模式和类似内核 SO_REUSEPORT 的 FF_MULTI_SC 模式。
该模式为默认模式,无需额外设置任何参数直接编译libff_syscall.so
即可。
在此模式下,socket 相关接口返回的 fd 可以在不同线程交叉调用,即支持 PIPELINE 模式,对已有应用的移植接入更友好,但性能上相应也会有更多的损失。
该模式除了单进程运行方式外,同时可以支持用户应用程序多进程方式运行,每个用户进程对应一个fstack
实例应用程序的实例,更多信息可以参考附录的运行参数介绍。
【注意】以此默认方式接入 F-Stack 的应用程序只能使用 F-Stack 的 socket 网络接口,而不能使用系统的 socket 接口。
对于已有的 Linux 下的应用,事件接口都是一般使用的是epoll
相关接口,对于没有更多特殊要求的应用程序,可以直接使用默认的编译参数编译libff_syscall.so
后使用,参考 DEMO 程序helloworld_stack_epoll
, 代码文件为main_stack_epoll.c
。
【注意】F-Stack 的epoll
接口依然为kqueue
接口的封装,使用上依然与系统标准的epoll
事件接口有一定区别,主要是事件触发方式和multi accept的区别。
当然libff_syscall.so
除了支持使用LD_PRELOAD
方式 hook 系统的 socket 接口的方式使用,也支持普通的链接方式使用,此时除了可以使用系统的epoll
事件接口之外,还可以使用 F-Stack(FreeBSD)具有的kqueue
事件接口,参考 DEMO 程序helloworld_stack
, 代码文件为main_stack.c
。
该使用方式的性能比LD_PRELOALD
使用系统epoll
接口的方式有略微的性能提升。
该模式需要设置额外的编译参数后来编译libff_syscall.so
才能开启,可以在adapter/sysctall/Makefile
中使能FF_THREAD_SOCKET
或执行以下 shell 命令来开启。
export FF_THREAD_SOCKET=1
make clean;make all
在此模式下,socket 相关接口返回的 fd 仅可以在本线程内调用,即仅支持线程内的 RTC 模式,对已有应用的移植接入门槛稍高,但性能上相应也会有一定的提升,适合原本就以 RTC 模式运行的应用移植。
同样的,该模式除了单进程运行方式外,同时可以支持用户应用程序多进程方式运行,每个用户进程对应一个fstack
实例应用程序的实例,更多信息可以参考附录的运行参数介绍。
【注意】以此默认方式接入 F-Stack 的应用程序同样只能使用 F-Stack 的 socket 网络接口,而不能使用系统的 socket 接口。
其他同默认的 PIPELINE 模式,可以参考 DEMO 程序helloworld_stack_epoll_thread_socket
, 代码文件为main_stack_epoll_thread_socket.c
。
其他同默认的 PIPELINE 模式,可以参考 DEMO 程序helloworld_stack_thread_socket
, 代码文件为main_stack_thread_socket.c
。
该模式可以同时支持 F-Stack 和系统内核的 socket 接口,需要设置额外的编译参数后来编译libff_syscall.so
才能开启,可以在adapter/sysctall/Makefile
中使能FF_KERNEL_EVENT
或执行以下 shell 命令来开启。
export FF_KERNEL_EVENT=1
make clean;make all
在此模式下,epoll
相关接口在调用 F-Stack 接口的同时会调用系统内核的相关接口,并将 F-Stack 返回的 fd 与系统内核返回的 fd 建立映射关系,主要为了支持两个场景:
socket
接口,并需要指定type | SOCK_KERNEL
参数,并为返回的 fd 单独调用 bind()
、listen()
、epoll_ctl()
等接口,参考 DEMO 程序helloworld_stack_epoll_kernel
, 代码文件为main_stack_epoll_kernel.c
【注意1】F-Stack 中 FreeBSD 的内核参数 kern.maxfiles
不应该大于 65536(原默认值为 33554432),以保证 F-Stack 的 epoll fd 到系统内核的 epoll fd 的正确映射。
【注意2】Nginx 的无缝接入需要开启此模式,因为在 Nginx 中有多个控制 fd 与 数据 fd 使用相同的 epoll fd。
该模式为 Nginx 等使用内核SO_REUSEPORT
且fork
子进程 worker 运行等特殊的设置为设置,需要设置额外的编译参数后来编译libff_syscall.so
才能开启,可以在adapter/sysctall/Makefile
中使能FF_MULTI_SC
或执行以下 shell 命令来开启。
export FF_MULTI_SC=1
make clean;make all
在此模式下,用户应用程序与fstack
实例相关联的上下文sc
除了保存在全局变量sc
中之外,会额外保存在全局的scs
数组中,在fork()
子进程 worker 时会使用 current_worker_id
设置sc
变量为对应 worker 进程 fd 对应的 sc,供子进程复制及使用。
Nginx 的reuseport
模式的主要流程为,主进程为每个 worker 分别调用 socket()
、bind()
、listen()
等接口,并复制到 worker 进程,而后 woker 进程各自调用epoll
相关接口处理各自的 fd, 需要各自 fd 对应的上下文 sc 才能正确运行。
【注意】Nginx 的无缝接入需要同时开启 FF_KERNEL_EVENT
和 FF_MULTI_SC
模式。
libff_syscall.so
介绍Nginx(以 F-Stack 默认携带的 Nginx-1.16.1 为例)目前可以不修改任何代码直接以LD_PRELOAD
动态库libff_syscall.so
的方式接入 F-Stack,以下为主要步骤及效果。
libff_syscall.so
需要同时开启 FF_KERNEL_EVENT
和 FF_MULTI_SC
模式进行编译
export FF_PATH=/data/f-stack
export PKG_CONFIG_PATH=/usr/lib64/pkgconfig:/usr/local/lib64/pkgconfig:/usr/lib/pkgconfig
cd /data/f-stack/adapter/sysctall
export FF_KERNEL_EVENT=1
export FF_MULTI_SC=1
make clean;make all
nginx.conf
以下为主要需要注意及修改的相关配置参数示例(非全量参数):
user root;
worker_processes 4; # worker 数量
worker_cpu_affinity 10000 100000 1000000 10000000; # 设置 CPU 亲和性
events {
worker_connections 1024;
multi_accept on; # epoll 是封装 kqueue 接口,必须要开启
use epoll;
}
http {
access_log off; # 关闭访问日志,用于提高测试时的网络性能,否则每次请求都需要额外调用系统的 write() 接口记录访问日志
sendfile off; # 使用 F-Stack 时需要关闭
keepalive_timeout 0; # 视长连接/短链接的业务需要调整
#keepalive_timeout 65;
#keepalive_requests 200; # 默认每个长连接最多 100 个请求,视业务需要调整,长连接时适当提高此值可以略微提高性能
server {
listen 80 reuseport; # 应该设置 reuseport,与使用系统的内核的 reuseport 行为不太一致,但都可以提高性能
access_log off;
location / {
#root html;
#index index.html index.htm;
return 200 "0123456789abcdefghijklmnopqrstuvwxyz"; # 直接返回数据用以测试单纯的网络性能
}
}
}
【注意】此处的 reuseport
作用是使用多个不同的 socket fd, 而每个 fd 可以对接不同的fstack
实例应用程序的上下文sc
来分散请求,从而达到提高性能的目的。与系统内核的reuseport
行为异曲同工。
CPU:Intel(R) Xeon(R) CPU E5-2670 v3 @ 2.30GHz * 2
网卡:Intel Corporation Ethernet Controller 10-Gigabit X540-AT2
OS :TencentOS Server 3.2 (Final)
内核:5.4.119-1-tlinux4-0009.1 #1 SMP Sun Jan 23 22:20:03 CST 2022 x86_64 x86_64 x86_64 GNU/Linux
ff_handle_each_context
的循环次数及时间等)影响很大,并未完全达到性能极致,如果持续的精细化调整可以进一步提高性能,但是通用性也不高。本段总体介绍各个编译选项,所有参数都可以在adapter/sysctall/Makefile
中开启或通过 shell 命令设置环境变量来开启。
开启或关闭 DEBUG 模式,主要影响优化和日志输出等, 默认关闭。
export DEBUG=-O0 -gdwarf-2 -g3
默认的优化参数为
-g -O2 -DNDEBUG
是否开启线程级上下文sc
变量,如果开启,则 socket 相关 fd 只能在本线程中调用,一般可以略微提高性能, 默认关闭。
export FF_THREAD_SOCKET=1
是否开启epoll
相关接口在调用 F-Stack 接口的同时调用系统内核的相关接口,并将 F-Stack 返回的 fd 与系统内核返回的 fd 建立映射关系, 默认关闭,主要为了支持两个场景:
export FF_KERNEL_EVENT=1
在此模式下,用户应用程序与fstack
实例相关联的上下文sc
除了保存在全局变量sc
中之外,会额外保存在全局的scs
数组中,在fork()
子进程 worker 时会使用 current_worker_id
设置sc
变量为对应 worker 进程 fd 对应的 sc,供子进程复制及使用。默认关闭。
export FF_KERNEL_EVENT=1
通过设置环境变量设置一些用户应用程序需要的参数值,如果后续通过配置文件配置的话可能需要修改原有应用,所以暂时使用设置环境变量的方式。
设置 LD_PRELOAD 的运行库,再运行实际的应用程序,可以参考以下命令
export LD_PRELOAD=/data/f-stack/adapter/syscall/libff_syscall.so
如果想通过gdb
调试应用程序,则可以参考以下命令
export LD_PRELOAD=
gdb ./helloworld_stack_epoll
(gdb) set exec-wrapper env 'LD_PRELOAD=/data/f-stack/adapter/syscall/libff_syscall.so'
设置fstack
实例应用程序的实例数,用于和用户应用程序的进程/线程等 worker 数量相匹配,默认1。
export FF_NB_FSTACK_INSTANCE=4
建议用户应用程序 worker 数量与fstack
实例应用程序尽量 1:1 配置,可以达到更好的性能。
配置用户应用程序的 CPU 亲和性绑定的起始 CPU 逻辑 ID,16进制,默认0x4(0b0100),即 CPU 2。
export FF_INITIAL_LCORE_ID=0x4
如果用于应用程序可以配置 CPU 亲和性,则可以忽略该参数,如 Nginx 配置文件中的worker_cpu_affinity
参数。
配置用户应用程序的进程 ID,可以配合FF_INITIAL_LCORE_ID
参数设置 CPU 亲和性的绑定,10进制递增,默认0。
export FF_PROC_ID=1
如果用于应用程序可以配置 CPU 亲和性,则可以忽略该参数,如 Nginx 配置文件中的worker_cpu_affinity
参数。