F-Stack LD_PRELOAD 测试版介绍

跳票许久许久的LD_PRELOAD功能模块(后续以 libff_syscall.so 代替)在 F-Stack dev 分支的 adapter/sysctall 目录下已经提交,支持 hook 系统内核 socket 相关接口的代码,降低已有应用迁移到 F-Stack 的门槛。下面将分别进行具体介绍, 主要包括libff_syscall.so 相关的架构涉及其中的一些思考,支持的几种模式以及如何使用等内容。

总体结论:

  • 原有应用程序的接入门槛比原本的 F-Stack 有所降低,大部分情况下可以不修改原有的用户应用程序和 F-Stack lib 的代码,而是仅修改libff_syscall.so相关代码即可适配。
  • 可以支持多 F-Stack 实例(即原 F-Stack 应用程序进程),每个 F-Stack 实例可以对应 1 个或多个用户应用程序。
    • 为了达到最佳的性能,建议一个用户应用程序(进程或线程)对应一个 fstack 实例应用程序,即为一组应用实例。
  • 每组应用实例的性能会略高于系统内核的性能,与单个标准 F-Stack 应用进程互有高低;单机整体的性能相比系统内核仍有较大的优势,但与标准 F-Stack 仍有差距。
    • 新的每组应用实例需要运行在两个 CPU 核心上,而标准 F-Stack 应用进程只需要运行在一个 CPU 核心上,总体而言性价比不高,是否使用可以视各业务的具体情况而定。
    • Nginx 600 字节的 body 内存应答测试中,长连接中相同数量的新应用实例于标准 F-Stack 应用进程,短连接中相同数量的新应用实例则略于标准 F-Stack 应用进程,见 Nginx 接入介绍章节,但使用的 CPU 几乎翻倍。

【注意】目前 libff_syscall.so 功能尚不完善,仅供测试使用,欢迎所有的开发者一起进行完善,存在一些问题,如下所示:

  • 进程结束时尚存在内存泄漏、容易死锁等问题。
  • 有些接口(如sendmsg、readv、readmsg等)因为尚未使用到,没有优化测试,还需进一步性能优化和测试。
  • 缺乏更长时间的运行验证,可能存在一些未知的隐藏问题尚未发现。
  • 多个f-stack实例运行的时候,暂时无法作为客户端使用,如Nginx的代理。参考修改方案如下:
    • @铁皮大爷:我之前有实现过一套逻辑,和现在坐着实现的类似,但是在 hook中 加入了 rss,从延迟 socket 建立(仅在确定目标、源以后才真正的选择使用哪一个 fstack 作为 worker 进程,要求在网卡接收时,也设置 rss 对称 hash,保证输出和输入能在同一个 fstack-worke r中)。 app -> sock -> hold一个sock操作,创建 fd(fd1),返回给用户 app -> bind -> hold一个bind操作,将 bind 参数绑定在 fd1 上,返回给用户, app -> connect -> 加个connect参数绑定在 fd1 上面,根据 rss 对称 hash 计算,选择一个 fstack 进程(worker),并将 hold 的 sock、bind、connect一并交给 fstack 进程,并等待同步返回结果。

libff_syscall.so 的编译

先设置好FF_PATHPKG_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 实例应用程序

fstack应用程序对标的是标准版 F-Stack 中的应用程序,其运行与普通的 F-Stack 应用程序完全相同,包括配置文件及其多进程(每进程即为一个实例)的运行方式等, 具体运行方式可以参考 F-Stack 主目录的 README, 在执行 LD_PRELOAD 的用户应用程序前必须先运行 fstack实例应用程序。

fstack 应用程序的作用主要是底层对接 F-Stack API,其主函数ff_handle_each_context即为普通 F-Stack 应用的用户层 loop 函数,非空闲时或每间隔 10ms (受 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,可以可获得更好的性能。当然不同的用用场景如果想达到最优的性能,可能需要业务自行调整及测试。复用该参数也只是临时方案,后续如果有更优的方案,则随时可能进行调整。

libff_syscall.so

该动态库主要作用是劫持系统的 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

确保 fstack实例应用程序已经正确运行的前提下,然后启动用户应用程序。

当然如果是改造用户的 APP 使用 kqueue代替 Linux 的 epoll 相关事件接口时,也可以在用户 APP 中直接链接该运行库, 可以参考相关示例程序helloworld_stackhelloworld_stack_thread_socket对应的源文件main_stack.cmain_stack_thread_socket.c,因为不是使用的LD_PRELOAD, 所以本文档不再详细介绍。

【重要提示】一组对应的fstack应用程序和用户应用程序最好运行在同一个 CPU NUMA 节点不同物理核上,其他场景(运行在同一个CPU核心、两个 CPU 核心跨 NUMA 节点,物理核和超线程核混用)都无法达到一组实例的最佳性能。

  • 特别的,如果 CPU 物理核心比较缺乏,可以考虑一组实例分别运行在对应的一组 CPU 的物理核心和 HT 核心上,虽然单组实例性能会有所下降(约 20% 左右),但可以使用更多的 CPU 核心,单机总性能可能会有所提升。

DEMO 演示程序 helloworld_stack*

其他编译生成的hello_world开头的可执行文件为当前libff_syscall.so支持的几种不同运行模式的相关演示程序,下一节进行具体介绍。

F-Stack LD_PRELOAD 支持的几种模式

为了适应不同应用对 socket 接口的不同使用方式,降低已有应用迁移到 F-Stack 的门槛,并尽量提高较高的性能,目前 F-Stack 的 libff_syscall.so 主要支持以下几种模式,支持多线程的 PIPELINE 模式、线程(进程)内的 RTC(run to completion)模式、同时支持 F-Stack 和内核 socket 接口的 FF_KERNEL_EVENT 模式和类似内核 SO_REUSEPORT 的 FF_MULTI_SC 模式。

支持多线程的 PIPELINE 模式

该模式为默认模式,无需额外设置任何参数直接编译libff_syscall.so即可。

在此模式下,socket 相关接口返回的 fd 可以在不同线程交叉调用,即支持 PIPELINE 模式,对已有应用的移植接入更友好,但性能上相应也会有更多的损失。

该模式除了单进程运行方式外,同时可以支持用户应用程序多进程方式运行,每个用户进程对应一个fstack实例应用程序的实例,更多信息可以参考附录的运行参数介绍。

【注意】以此默认方式接入 F-Stack 的应用程序只能使用 F-Stack 的 socket 网络接口,而不能使用系统的 socket 接口。

hook 系统 epoll 接口

对于已有的 Linux 下的应用,事件接口都是一般使用的是epoll相关接口,对于没有更多特殊要求的应用程序,可以直接使用默认的编译参数编译libff_syscall.so后使用,参考 DEMO 程序helloworld_stack_epoll, 代码文件为main_stack_epoll.c

【注意】F-Stack 的epoll接口依然为kqueue接口的封装,使用上依然与系统标准的epoll事件接口有一定区别,主要是事件触发方式和multi accept的区别。

使用 kqueue

当然libff_syscall.so除了支持使用LD_PRELOAD方式 hook 系统的 socket 接口的方式使用,也支持普通的链接方式使用,此时除了可以使用系统的epoll事件接口之外,还可以使用 F-Stack(FreeBSD)具有的kqueue事件接口,参考 DEMO 程序helloworld_stack, 代码文件为main_stack.c

该使用方式的性能比LD_PRELOALD使用系统epoll接口的方式有略微的性能提升。

线程(进程)内的 RTC(run to completion)模式

该模式需要设置额外的编译参数后来编译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 接口。

hook 系统 epoll 接口

其他同默认的 PIPELINE 模式,可以参考 DEMO 程序helloworld_stack_epoll_thread_socket, 代码文件为main_stack_epoll_thread_socket.c

使用 kqueue

其他同默认的 PIPELINE 模式,可以参考 DEMO 程序helloworld_stack_thread_socket, 代码文件为main_stack_thread_socket.c

FF_KERNEL_EVENT 模式

该模式可以同时支持 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 建立映射关系,主要为了支持两个场景:

  • 用户应用程序中有控制 fd 与 数据 fd 使用相同的 epoll fd, 如 Nginx。
  • 希望本机也可以同时访问用户应用程序监听的网络接口。
    • 如果希望单独与本机系统内核进行普通网络通信,需要额外调用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。

FF_MULTI_SC 模式

该模式为 Nginx 等使用内核SO_REUSEPORTfork子进程 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_THREAD_SOCKETFF_MULTI_SC 模式。

Nginx 接入libff_syscall.so介绍

Nginx(以 F-Stack 默认携带的 Nginx-1.16.1 为例)目前可以不修改任何代码直接以LD_PRELOAD动态库libff_syscall.so的方式接入 F-Stack,以下为主要步骤及效果。

编译libff_syscall.so

需要同时开启 FF_THREAD_SOCKETFF_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行为异曲同工。

运行

假设运行4组 Nginx – fstack 实例应用程序,可以简单按照以下步骤进行

  • 运行 fstack 实例
  • 设置config.ini中的lcore_mask=f00,即使用 CPU 核心 9-11, 其他配置按照标准 F-Stack 配置进行。
  • 参考以下命令启动 fstack 实例,并等待一段时间待 fstack 主进程和子进程都启动完成
  cd /data/f-stack
  bash ./start.sh -b adapter/syscall/fstack
  • 运行 Nginx
  • 参考以下命令配置libff_syscall.so所需的环境变量
  export LD_PRELOAD=/data/f-stack/adapter/syscall/libff_syscall.so # 设置 LD_PRELOAD libff_syscall.so
  export FF_NB_FSTACK_INSTANCE=4 # 设置有 4 个 fstack 实例应用程序,前面 nginx.conf 中也配置了 4 个worker
  • 启动 Nginx
  /usr/local/nginx/sbin/nginx # 启动 Nginx

性能对比

测试环境

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

Nginx长连接

  • body 大小为 602 字节(不包括 http 头等)。
  • LD_PRELOAD 实际使用的 CPU 为几乎横轴 CPU 核心数的双倍,系统内核均衡软中断实际使用的 CPU 也远高于 worker 数量对应的 CPU 核心数量。
  • 限于时间所限,其中 LD_PRELOAD 的测试数据为以上测试环境的数据,其他为历史 40G 测试环境的数据,后续会更新为相同测试环境的数据。
  • 受网卡硬件所限,8核 LD_PRELOAD 测试带宽已经接近 10G 网卡线速(服务端出带宽9.xG,148万 RPS), 导致的与标准 F-Stack 的数据差异,实际CPU尚有一些空闲,后续应使用 40G/100G 网卡进行对比测试
  • pkt_tx_delay 参数为 100us。

Nginx短链接

  • body 大小为 602 字节(不包括 http 头等)。
  • LD_PRELOAD 实际使用的 CPU 为几乎横轴 CPU 核心数的双倍,系统内核均衡软中断实际使用的 CPU 也远高于 worker 数量对应的 CPU 核心数量。
  • 受 CPU 硬件所限(12C24HT * 2),LD_PRELOAD 测试只能测试12组应用实例组,即使用了全部 CPU 的物理核心,无法进行更多实例组的测试。
  • 8核之后 LD_PRELOAD 的性能不如标准 F-Stack 的性能,最主要是受用户应用程序和fstack应用程序的匹配度不高(ff_handle_each_context的循环次数及时间等)影响很大,并未完全达到性能极致,如果持续的精细化调整可以进一步提高性能,但是通用性也不高。
  • pkt_tx_delay 参数由 100us 调整到 50us。

附录:详细参数介绍

编译参数

本段总体介绍各个编译选项,所有参数都可以在adapter/sysctall/Makefile中开启或通过 shell 命令设置环境变量来开启。

DEBUG

开启或关闭 DEBUG 模式,主要影响优化和日志输出等, 默认关闭。

export DEBUG=-O0 -gdwarf-2 -g3

默认的优化参数为

-g -O2 -DNDEBUG

FF_THREAD_SOCKET

是否开启线程级上下文sc变量,如果开启,则 socket 相关 fd 只能在本线程中调用,一般可以略微提高性能, 默认关闭。

export FF_THREAD_SOCKET=1

FF_KERNEL_EVENT

是否开启epoll相关接口在调用 F-Stack 接口的同时调用系统内核的相关接口,并将 F-Stack 返回的 fd 与系统内核返回的 fd 建立映射关系, 默认关闭,主要为了支持两个场景:

  • 用户应用程序中有控制 fd 与 数据 fd 使用相同的 epoll fd, 如 Nginx。
  • 希望本机也可以同时访问用户应用程序监听的网络接口。
export FF_KERNEL_EVENT=1

FF_MULTI_SC

在此模式下,用户应用程序与fstack实例相关联的上下文sc除了保存在全局变量sc中之外,会额外保存在全局的scs数组中,在fork()子进程 worker 时会使用 current_worker_id设置sc变量为对应 worker 进程 fd 对应的 sc,供子进程复制及使用。 默认关闭。

export FF_KERNEL_EVENT=1

运行参数

通过设置环境变量设置一些用户应用程序需要的参数值,如果后续通过配置文件配置的话可能需要修改原有应用,所以暂时使用设置环境变量的方式。

LD_PRELOAD

设置 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'

FF_NB_FSTACK_INSTANCE

设置fstack实例应用程序的实例数,用于和用户应用程序的进程/线程等 worker 数量相匹配,默认1。

export FF_NB_FSTACK_INSTANCE=4

建议用户应用程序 worker 数量与fstack实例应用程序尽量 1:1 配置,可以达到更好的性能。

FF_INITIAL_LCORE_ID

配置用户应用程序的 CPU 亲和性绑定的起始 CPU 逻辑 ID,16进制,默认0x4(0b0100),即 CPU 2。

export FF_INITIAL_LCORE_ID=0x4

如果用于应用程序可以配置 CPU 亲和性,则可以忽略该参数,如 Nginx 配置文件中的worker_cpu_affinity参数。

FF_PROC_ID

配置用户应用程序的进程 ID,可以配合FF_INITIAL_LCORE_ID参数设置 CPU 亲和性的绑定,10进制递增,默认0。

export FF_PROC_ID=1

如果用于应用程序可以配置 CPU 亲和性,则可以忽略该参数,如 Nginx 配置文件中的worker_cpu_affinity参数。

linux&F-Stack(FreeBSD)多vlan VIP策略路由设置

假设我们的服务器上有如下vlan和ip的配置

vlan 10:
IP:10.10.10.10 netmask:255.255.255.0 broadcast:10.10.10.255 gateway:10.10.10.1
外网VIP:110.110.110.0/24中的一个或多个IP,如110.110.110.110

vlan 20:
IP:10.10.20.20 netmask:255.255.255.0 broadcast:10.10.20.255 gateway:10.10.20.1
外网VIP:120.120.120.0/24中的一个或多个IP,如120.120.120.120

vlan 30:
IP:10.10.30.30 netmask:255.255.255.0 broadcast:10.10.30.255 gateway:10.10.30.1
外网VIP:130.130.130.0/24中的一个或多个IP,如30.130.130.130

外部主要访问各个vlan里的vip,需要系统应答包也正确的对应的vlan回复到该vlan的gateway地址上,下面为linux系统和F-Stack(FreeBSD)的策略设置方式

Linux 系统路由配置

创建 vlan 并设置 IP 地址

ip link add link eth0 name eth0.10 type vlan id 10
ifconfig eth0.10 10.10.10.10 netmask 255.255.255.0 broadcast 10.10.10.255 up
route add -net 10.10.10.0/24 gw 10.10.10.1 dev eth0.10

ip link add link eth0 name eth0.20 type vlan id 20
ifconfig eth0.20 10.10.20.20 netmask 255.255.255.0 broadcast 10.10.20.255 up
route add -net 10.10.20.0/24 gw 10.10.20.1 dev eth0.20

ip link add link eth0 name eth0.30 type vlan id 30
ifconfig eth0.30 10.10.30.30 netmask 255.255.255.0 broadcast 10.10.30.255 up
route add -net 10.10.30.0/24 gw 10.10.30.1 dev eth0.30

# 设置各 vlan 的外网 VIP
ifconfig lo.0 110.110.110.110 netmask 255.255.255.255
ifconfig lo.1 120.120.120.120 netmask 255.255.255.255
ifconfig lo.2 130.130.130.130 netmask 255.255.255.255

设置策略路由

echo "10 t0" >> /etc/iproute2/rt_tables
echo "20 t1" >> /etc/iproute2/rt_tables
echo "30 t2" >> /etc/iproute2/rt_tables

# 设置各 vlan 的路由表, 并设置对应的网关地址
ip route add default dev eth0.10 via 10.10.10.1 table 10
ip route add default dev eth0.20 via 10.10.20.1 table 20
ip route add default dev eth0.30 via 10.10.30.1 table 30

#systemctl restart network

# 从对应 VIP 出去的包使用对应 vlan 的路由表
ip rule add from 110.110.110.0/24 table 10
ip rule add from 120.120.120.0/24 table 20
ip rule add from 130.130.130.0/24 table 30

F-Stack(FreeBSD) 路由配置

前提条件:在 F-Stack 的 lib/Makefile中打开默认关闭的ipfw选项,并重新编译 F-Stack lib 库,各个工具及应用程序

# NETGRAPH drivers ipfw
FF_NETGRAPH=1
FF_IPFW=1

创建 vlan 并设置 IP 地址

ff_ifconfig -p 0 f-stack-0.10 create
ff_ifconfig -p 0 f-stack-0.10 inet 10.10.10.10 netmask 255.255.255.0 broadcast 10.10.10.255

ff_ifconfig -p 0 f-stack-0.20 create
ff_ifconfig -p 0 f-stack-0.20 inet 10.10.20.20 netmask 255.255.255.0 broadcast 10.10.20.255

ff_ifconfig -p 0 f-stack-0.30 create
ff_ifconfig -p 0 f-stack-0.30 inet 10.10.30.30 netmask 255.255.255.0 broadcast 10.10.30.255

# 设置各 vlan 的外网 VIP
ff_ifconfig -p0 f-stack-0.10 inet 110.110.110.110 netmask 255.255.255.255 alias
ff_ifconfig -p0 f-stack-0.20 inet 120.120.120.120 netmask 255.255.255.255 alias
ff_ifconfig -p0 f-stack-0.30 inet 130.130.130.130 netmask 255.255.255.255 alias

设置策略路由

# 设置各 vlan 的转发路由表(fib), 并设置对应的网关地址
ff_route -p 0 add -net 0.0.0.0 10.10.10.1 -fib 10
ff_route -p 0 add -net 0.0.0.0 10.10.20.1 -fib 20
ff_route -p 0 add -net 0.0.0.0 10.10.30.1 -fib 30

# 将对应 VIP 发出去的数据包设置对应 vlan 的 fib num,以便使用正确的转发路由表(fib)
ff_ipfw -P 0 add 100 setfib 10 ip from 110.110.110.0/24 to any out
ff_ipfw -P 0 add 200 setfib 20 ip from 120.120.120.0/24 to any out
ff_ipfw -P 0 add 300 setfib 30 ip from 130.130.130.0/24 to any out

参考资料

  1. F-Stack vlan 的支持与使用
  2. Nginx TCP 多证书透明代理及 Linux/F-Stack(FreeBSD) 路由相关设置
  3. linux 和 FreeBSD 相关工具的 man page

F-Stack常用配置参数介绍

目前F-Stack的配置文件中包含有以下8个部分,下面将分别进行简单的介绍:

[dpdk]、[pcap]、[portN]、[vdevN]、[bondN]、[kni]、[freebsd.boot]、[freebsd.sysctl]

[DPDK]

设置运行DPDK的相关参数,如果是DPDK也有的参数,则含义和使用方法同DPDK参数。

lcore_mask

16进制位掩码,用于设置进程运行在哪些CPU核心上。如fc表示使用CPU第2-7个核,不使用第0和1核。

建议优先使用物理核,数据尽量不要跨NUMA节点交互,可以空出前2个CPU核心给系统,且配置其他进程不调度到DPDK要使用的CPU核心上。

channel

内存通道数,一般无需修改,使用默认值即可。

base_virtaddr

指定mmap内存到主进程的虚拟地址,默认关闭。

某些特定场景下可能需要使用,如自动分配的虚地址与其他地址冲突时,可以多次尝试使用DPDK启动时的错误提示进行指定或在应用中尝试修改初始化F-Stack(DPDK)的位置。

promiscuous

0或1,是否开启网卡的混杂模式,默认开启。

建议开启,尤其是对可能需要处理多播包(如OSPF协议包)等场景。

numa_on

0或1,是否开启NUMA支持,默认开启。

建议开启。

tx_csum_offoad_skip

0或1,是否关闭发包校验和的卸载,默认否。

当网卡支持发包校验和卸载时,F-Stack正常总是开启该功能,一般不需要修改。该参数配置为1时,则不会设置发包校验和的网卡硬件卸载,用于某些特殊场景,如需要发送错误的校验和用于测试、或某些网卡宣传支持发包校验和卸载但实际并未计算校验和等。

tso

0或1,是否开启TCP分段卸载(TCP segment offload),默认关闭。

理论上开启应该有更好的性能表现,TCP协议栈无需对大包进行软件分段,交给网卡硬件进行,但目前实测并未表现出性能优势,所以默认关闭。

vlan_strip

0或1,是否开启VLAN卸载(TCP segment offload),默认开启。

开启后,网卡会将收包的VLAN头卸载剥离,某些特殊场景可能需要关闭该功能,如KNI需要VLAN的场景,详细介绍见前期文章《F-Stack vlan 的支持与使用》。

idle_sleep

当前循环未收到数据包的空闲休眠时间,单位微秒,默认0,即一直保持轮询模式,不进行休眠,CPU使用率为100%。

线上实际使用时建议设置为不超过100的值,即当本次循环没有收到数据包时,休眠不超过100微秒,主要目的是降低CPU使用率,且实际对线上业务基本无影响,但是会增加单连接小数据量的收包延迟,如果单纯想测试收发包延迟情况或不在意线上CPU使用率一直保持100%,可以设置为0

目前DPDK已经支持中断+轮询模式,但是F-Stack初始开发时(2012年)DPDK尚未支持中断模式,所以在当时的业务中引入了该参数用于降低CPU使用率,虽然后来DPDK支持了中断模式,但因为影响基本可以忽略,F-Stack目前暂未支持中断模式。

pkt_tx_delay

F-Stack发包延迟时间,单位微秒,默认为100,支持配置范围[0,100],配置超过100时强制置为100。

类似于TCP中的delay ack的概念,为了使用批量发包提升最大的并发吞吐量性能,F-Stack在发包时会先进行缓存并延迟发送,实际发包的触发条件有两个,凑够一次批量发包的包数(目前硬编码为32),或延迟发包时间超时。

默认延迟发包可以提升大并发下的吞吐量性能,但是会增加单连接小数据量的发包延迟,如果单纯想测试收发包延迟情况,可以设置为0,则每次发包都会立即实际发送。除了测试使用,一般不建议修改为0

symmetric_rss

0或1,是否开启对称RSS,默认否。

网关或类似服务可以开启对称RSS选项,通过设置特殊的RSS hash key,使四元组中IP和端口号互换的数据包可以收到同一队列(CPU)中,主要目的是增加CPU的缓存命中率 。

pci_whitelist

F-Stack(DPDK)可以识别加载的网卡设备白名单,默认为所有支持的设备。参数值为设备号,如02:00.0或02:00.0,03:00.0,主要用于仅希望指定的网卡设备可以被DPDK识别使用时。

port_list

F-Stack(DPDK)实际要接管的网卡(网口)设备序号列表,从0开始。如00,1,20-2等。

可以与pci_whitelist配合使用,仅从白名单中的网口设备从0开始进行排序编号。

设置了接管几个网口,后面就应该配置几个对应的[portN]的地址信息配置段,N为网卡网口序号。

当使用bonding模式时,参数值应为bonding虚拟设备的网口号(从实际的设备数往上递增),不应该包含slave设备的网口号。

nb_vdev

配置有几个容器虚拟设备,设置了几个设备,后面就应该配置几个对应的[vdevN]的信息配置段,N为容器编号。

因为容器是F-Stack是第一个支持的虚拟设备,此处的vdev仅用于配置容器参数,其他虚拟设备则使用对应的设备类型来配置,如bonding。

nb_bond

配置有几个bonding虚拟设备,设置了几个设备,后面就应该配置几个对应的[bondN]的信息配置段,N为bonding设备编号。

file_prefix

文件前缀,主要用于同时启动不同的F-Stack(DPDK)进程组,通过不同的配置文件中配置不同的文件前缀,可以同时启动多个主进程及其对应的辅进程,某些特殊场景可能会用到。

no_huge

0或1,是否不使用大页内存,默认为0,即使用大页内存,一般无需修改。

[PCAP]

抓包相关配置选项,每个进程分别写入自己的抓包文件。需要注意的是开启抓包将会严重影响性能,一般仅调试时使用。

enable

0或1,是否开启抓包,默认否。

snaplen

每个包的最大抓包长度,默认96字节。

savelen

单个抓包文件的大小限制,达到限制后将重新打开新的抓包文件,默认值16777216,即16M。

savepath

抓包文件保存目录,默认为.,即程序启动目录。

[portN]

配置网口的地址等相关信息,N对应[DPDK]段的port_list值,如0,1,2,5等,每一个接管的网口都需要单独的一段[portN]来进行配置

addr

网口需要配置的IPv4地址,此处仅支持配置一个IP。

netmask

IPv4掩码。

broadcast

IPv4广播地址。

gateway

IPv4路由地址。

if_name

可选参数,配置F-Stack中的设备名称,默认为f-stack-N,N从0开始,与PortN对应。>= 1.22。

addr6

可选参数,配置本网口的IPv6地址。

prefix_len

IPv6的prefix len,配置了addr6之后才需要配置,默认64。

gateway6

配置了addr6之后的可选参数,当本地IPv6的环境不使用NDP时才需要配置(如腾讯云),如果使用NDP则不需要配置(如AWS)。

vip_ifname

虚拟IP配置到哪个网口设备,默认f-stack-N,根据实际需要可以配置到lo0等设备。>= 1.22。

vip_addr

分号分隔的IPv4虚拟地址,最大支持64个虚拟地址。目前不支持单独配置掩码和广播地址,在函数ff_veth_setvaddr中硬编码使用255.255.255.255x.x.x.255。>= 1.22。

vip_addr6

分号分隔的IPv6虚拟地址,最大支持64个虚拟地址。>= 1.22。

vip_prefix_len

虚拟IPv6地址的prefix_len,所有地址只能使用统一前缀,默认为64。>= 1.22。

lcore_list

使用哪些CPU核心处理本网口的队列,格式与port_list一致,默认为全部CPU核心都绑定处理本网口的队列。

不同进程之间是数据隔离的,如果需要在不同网口间转发数据,必须同一个CPU核心同时绑定处理多个网卡的队列或自行进行IPC,使用时需要注意,一般无特殊需求的话,无需修改配置该参数。

slave_port_list

当本网口为bonding虚拟设备的时候需要配置该参数,指定组成本bonding的slave网口,配置格式与port_list一致,如0,10-1

[vdevN]

配置容器的相关信息,N对应[DPDK]段的nb_vdev值,如0,1,2,5等,每一个虚拟设备都需要单独的一段[vdevN]来进行配置

iface

默认值/usr/local/var/run/openvswitch/vhost-userN,不应该设置修改。

path

必选参数,容器内的vhost user设备路径,如/var/run/openvswitch/vhost-userN,

queues

可选参数, vuser的最大队列数,应等于或大于F-Stack的进程数,默认为1。

queue_size

可选参数,队列大小,默认值256。

mac

可选参数,vuser设备的MAC地址,默认值为随机地址。

如果vhost使用物理网卡,则vuser的MAC地址应设置为物理网卡的MAC地址。

cq

可选参数,如果队列数queues为1,则设置为0,默认值。如果队列数queues大于1,则设置为1。

[bond0]

配置bonding虚拟设备的相关信息,N对应[DPDK]段的nb_vdev值,如0,1,2,5等,每一个虚拟设备都需要单独的一段[bondN]来进行配置。

此处仅简单介绍下配置项,bonding的具体信息可以参考DPDK的帮助文档 http://doc.dpdk.org/guides/prog_guide/link_bonding_poll_mode_drv_lib.html

需要注意的时,当前DPDK的bonding驱动不支持多进程模式,而F-Stack目前仅支持多进程模式,多线程模式需要使用方自行修改测试。

mode

bonding模式,默认为模式4,该模式需交换机配置支持。

slave

子设备号列表,多个子设备时需设置多个k=v格式,逗号分隔,如slave=0000:0a:00.0,slave=0000:0a:00.1

primary

主设备号,如0000:0a:00.0

mac

bonding设备的MAC地址,一般可以设置为主网口的MAC地址。

其他可选参数

具体含义可以参考DPDK相关文档

  • socket_id=0
    • NUMA节点号,根据实际设置
  • xmit_policy=l23
    • 转发负载均衡策略
  • lsc_poll_period_ms=100
  • up_delay=10
  • down_delay=50

[kni]

配置kni数据包转发到内核相关参数,配置文件中默认未开启kni段,如需要需自行取消注释并配置相关参数。

enable

0或1,是否开启kni。

method

rejectaccept,配置kni转发的默认策略。

如果设置为reject,则下面tcp_portudp_port指定的数据转发到F-Stack进程协议栈,除此之外其他数据包都转发到内核。

如果设置为accept,则下面tcp_portudp_port指定的数据转发到内核,除此之外其他数据包都转发到F-Stack进程协议栈。

tcp_port

kni转发过滤器过滤的TCP端口,配置格式与port_list一致,如80,443

udp_port

kni转发过滤器过滤的UDP端口,配置格式与port_list一致,如53,443

kni_action

defaultalltoknialltoff,可选参数,可以通过工具knictl分进程控制不同进程的kni转发策略。>= 1.22。

default,默认值,使用上面的通用kni转发配置。

alltokni,所有数据包通过kni转发到内核。

`alltoff,所有数据包转发到F-Stack协议栈。

FreeBSD

网络调优配置,包含一些F-Stack独有的配置,其他为FreeBSD的配置项,绝大部分FreeBSD的配置项都支持,但此处仅列举了少数配置,详细的配置项可以通过工具ff_sysctl -a获取,配置项的详细信息则可以参考FreeBSD的man page。

[freebsd.boot]

hz

定时器每秒扫描频率,默认为100,即10ms精度,无特殊需求一般无需修改。

调大该值可以提高定时器精度,但是不一定会提高性能,目前建议不要设置太高,如不超过1000。

注意:目前F-Stack 1.22版本(尚未正式发布)使用的FreeBSD 13.0,支持开启RACK和BBR,而RACK和BBR都依赖高精度定时器,目前该版本的RACK和BBR暂时都无法正常工作,不排除会受定时器精度影响,后续将进行调试排查。

physmem

一个进程使用的内存大小,单位字节,默认256M,无特殊需求无需修改。

memsz_MB

开启编译选项FF_USE_PAGE_ARRAY之后有效,每进程mmap的页面数组内存大小,单位M字节,默认256M,无特殊需求无需修改。

FF_USE_PAGE_ARRAY编译选项用于开启发送数据包时FreeBSD协议栈到DPDK的零拷贝,虽然减少了内存数据拷贝,但是因为多了一些其他操作,性能不一定提升,如小数据包发送时,开启该选项是否能提升性能需要使用方在自己的使用场景单独进行对比测试

目前应用层到FreeBSD协议栈的socket接口的发包零拷贝也已经支持,正在测试中,在某些特定场景会有一定的性能提升,同样的对特定应用场景是否有提升需使用方单独开启测试,预计很快将提交代码到1.22版本(dev分支),但该功能需要修改应用层的socket接口使用行为,由使用方自行选择是否使用。

fd_reserve

屏蔽一系列描述符以避免与内核的描述符空间重叠,默认1024,即应用层从1024开始分配fd。 您可以根据您的应用增加此值。

特别的,某些较老应用支持的fd范围有限,移植到F-Stack之后可能无法正常运行,需要减小该值。

其他协议栈选项

根据F-Stack调优过的协议栈选项,无特殊需求一般无需修改,相关限制数值都为进程级,非全局限制,因为F-Stack每个进程启动了一个独立的协议栈。部分参数值设置错误可能导致F-Stack进程的协议栈异常,如部分参数值要求必须为2的N次幂。

  • kern.ipc.maxsockets=262144
  • net.inet.tcp.syncache.hashsize=4096
  • net.inet.tcp.syncache.bucketlimit=100
  • net.inet.tcp.tcbhashsize=65536
  • kern.ncallout=262144
  • kern.features.inet6=1
    • 开启IPv6支持,IPv6的部分参数也可以参考前期文章《F-Stack IPv6 的支持与使用》。
  • net.inet6.ip6.auto_linklocal=1
  • net.inet6.ip6.accept_rtadv=2
  • net.inet6.icmp6.rediraccept=1
  • net.inet6.ip6.forwarding=0

[freebsd.sysctl]

  • kern.ipc.somaxconn=32768
    • 等待连接数,应用层可能也可以根据需要配置backlog
  • kern.ipc.maxsockbuf=16777216
  • net.link.ether.inet.maxhold=5
  • net.inet.tcp.fast_finwait2_recycle=1
  • net.inet.tcp.sendspace=16384
  • net.inet.tcp.recvspace=8192
  • #net.inet.tcp.nolocaltimewait=1
    • 开启该参数可能导致某些场景的IPv6异常,所以关闭。
  • net.inet.tcp.cc.algorithm=cubic
    • 设置拥塞算法为cubic,FreeBSD的默认拥塞算法为new reno。当参数net.inet.tcp.functions_default设置为freebsd时有效.
  • net.inet.tcp.sendbuf_max=16777216
  • net.inet.tcp.recvbuf_max=16777216
  • net.inet.tcp.sendbuf_auto=1
  • net.inet.tcp.recvbuf_auto=1
  • net.inet.tcp.sendbuf_inc=16384
  • net.inet.tcp.recvbuf_inc=524288
  • net.inet.tcp.sack.enable=1
  • net.inet.tcp.blackhole=1
  • net.inet.tcp.msl=2000
  • net.inet.tcp.delayed_ack=1
    • 早期版本F-Stack默认没有开启dealy ack,当前版本修改为默认开启,可以提高大并发场景的吞吐量性能,但是会增加单连接小数据量的延迟,如需测试相关场景,可以关闭该功能,参考dpdk.pkt_tx_delay选项。
  • net.inet.udp.blackhole=1
  • net.inet.ip.redirect=0
  • net.inet.ip.forwarding=0
    • 当需要进行IP转发,数据不需要到应用层时需要开启该选项。
  • net.inet.tcp.functions_default=freebsd
    • freebsdrackbbr,设置使用FreeBSD支持的传统拥塞算法(通过参数net.inet.tcp.cc.algorithm设置),还是使用rack或bbr。>= 1.22。
    • 注意:当前尚未正式发布的1.22版本中的rack和bbr尚不能正常工作,需要进一步调试,对希望使用bbr拥塞算法的同学可以一起来调试并提交Pull Request。