AI 时代的一些个人想法

最近用 AI 比较多,也有一些自己的感受,零散记一下。

1. 大模型、Agent 和平台

大模型就像发动机,Token 是汽油,Agent 智能体相当于汽车,各种 CI/CD 平台就是路和基础设施,OpenClaw 这类工具大概是汽车的智驾。不一定非常准确,大概这么个意思。

2. AI 写代码这件事,现在到底到哪了

春节前,AI 辅助写单元测试,能完成大概六七十。春节后,用上 plan 模式 + Claude Opus/Sonnet 4.6 之类,至少 80%,部分场景 85% 以上。

对于初中级程序员能干的事,AI 基本已经碾压了。简单的功能、函数、小模块,没什么问题。

但是,遇到复杂项目,多模块互相关联、调用链条复杂的那种,目前国内国际顶级模型都还不太行。这块儿目前是高级程序员最后的阵地。但以 AI 目前的迭代速度,这个阵地估计也撑不了太久。

下一个真正的护城河,可能是:对现网复杂问题的经验判断,行业 context 的深度积累。这些东西 AI 目前基本还是胡说八道的多。

3. 真正的护城河在哪

不是什么人文关怀、情感这些,这些 AI 是可以模拟的,而且会越来越好,对这方面的护城河持悲观态度。

真正难被替代的是:私有数据、行业经验、关系网络、运营资源、信息差。这些才是门槛,其他的比如做个 to-do list 应用、学英语 app 之类,基本没有任何护城河,扯淡。

4. 技术跟进节奏

不必追 .0 版本,等到 .1、.2,大版本相对稳定了再跟进。但也不能隔三五个大版本才去关注,那就太晚了。这个思路对 AI 新技术和传统开源软件都一样。

5. 关于 OpenClaw 的实际使用体验

用了一段时间,整体来说,选对模型 + 配好 skills,效果还不错。我这边主要用来:定时提醒股票、花粉等信息、处理 F-Stack 开源项目的 issue,效率比较好。

后续计划用来给 F-Stack 生成架构文档、规范文档,以及尝试用 SDD(Spec-Driven Development)模式开发或优化一些新功能,具体效果后续再看,有些功能对 Claude Code 来说确实也有点吃力。

对其他人的建议:任务复杂但选了弱模型,效果就很差,基本不可用。如果平台只能用自动分配的国产模型,建议换个可以手选模型的,至少选 GLM5、Kimi、MiniMax 这类相对好的。有条件的话还是上海外顶级模型,效果差距很明显。

Token 消耗大的话,可以按任务分级:简单任务用国产模型,复杂的工程化连续任务建议用海外顶级模型;定期做记忆压缩也有帮助;尽量使用Coding Plan。

当然,小龙虾目前这么火,肯定也少不了各种宣传导致的全民焦虑,以及卖铲子和盒饭的推波助澜。

就这些,比较零散,凑合看。


本文是个人提供零散信息后,由 OpenClaw + Claude Code Sonnet-4.6 模拟个人风格整理生成。

F-Stack “No probed ethernet devices” 问题分析与解决指南

整理自 GitHub F-Stack/f-stack 仓库 issue 及 DPDK 官方文档

涉及 issue:#1035、#837、#693、#663、#583、#581、#531、#386、#379 等

最后更新:2026-03-09


一、问题概述

在运行 F-Stack(helloworld、nginx、自定义应用等)时,常见以下报错:

EAL: Error - exiting with code: 1

Cause: No probed ethernet devices

或:

ff_init failed with error "No probed ethernet devices"

该错误的根本原因是 DPDK 无法检测到可用的以太网设备,即 rte_eth_dev_count_avail() 返回 0。

这是 F-Stack 社区中出现频率最高的问题之一,历史上超过 10 个相关 issue,跨越 2019~2025 年,具有高度重复性。


二、根因分类

“No probed ethernet devices” 错误通常由以下几类原因导致,需按顺序逐一排查。


原因 1:网卡未绑定到 DPDK 兼容驱动

最常见原因。 DPDK 需要网卡从内核驱动(如 ixgbevirtio)解绑,并重新绑定到 DPDK 专用驱动(igb_uiovfio-pci)。

诊断命令:

cd /data/f-stack/dpdk

python3 usertools/dpdk-devbind.py --status

如果网卡显示在 Network devices using kernel driver 而不是 Network devices using DPDK-compatible driver,则需要绑定驱动。

解决方案:使用 igb_uio 绑定

# 1. 加载 igb_uio 模块

modprobe uio

insmod /data/f-stack/dpdk/build/kernel/linux/igb_uio/igb_uio.ko

2. 查看网卡 PCI 地址

python3 usertools/dpdk-devbind.py --status

3. 绑定网卡(替换 0000:00:03.0 为你的 PCI 地址)

python3 usertools/dpdk-devbind.py --bind=igb_uio 0000:00:03.0

4. 验证绑定成功

python3 usertools/dpdk-devbind.py --status

应看到网卡出现在 "Network devices using DPDK-compatible driver" 下

解决方案:使用 vfio-pci 绑定(推荐用于现代系统/虚拟机)

# 1. 加载 vfio-pci 模块

modprobe vfio-pci

2. 绑定网卡

python3 usertools/dpdk-devbind.py --bind=vfio-pci 0000:00:03.0

3. 如需在非 IOMMU 环境使用

echo 1 > /sys/module/vfio/parameters/enable_unsafe_noiommu_mode


原因 2:config.ini 中 port_list 未正确配置

config.ini[dpdk] 段中必须配置 port_list,且其编号要与实际绑定的 DPDK 端口对应。

错误示例:

[dpdk]

port_list=0

配置了 port 0,但网卡未绑定或 PCI 白名单不匹配。

排查方法:

# 查看当前绑定的 DPDK 端口

python3 usertools/dpdk-devbind.py --status

运行 testpmd 验证 DPDK 可以看到设备

dpdk-testpmd -l 0-1 -n 4 -- -i

解决方案:

确认 pci_whitelist(旧版)或 allow(新版)参数与实际网卡 PCI 地址匹配:

[dpdk]

lcore_mask=1

channel=4

port_list=0

旧版 DPDK

pci_whitelist=0000:00:03.0

新版 DPDK (21.11+)

allow=0000:00:03.0


原因 3:Makefile 链接参数缺少 --whole-archive

在自定义应用中链接 F-Stack 时,如果没有使用 --whole-archive 标志,DPDK 的 NIC PMD(Poll Mode Driver)驱动库不会被完整链接进来,导致运行时找不到网卡。

错误写法(不完整):

LIBS += -lfstack -ldpdk

正确写法:

DPDK 19.11 及以下:

LIBS += -L${FF_PATH}/lib -Wl,--whole-archive,-lfstack,--no-whole-archive

LIBS += -L${FF_DPDK}/lib -Wl,--whole-archive,-ldpdk,--no-whole-archive

DPDK 20.11 / 21.11 及以上(使用 pkg-config):

CFLAGS  += $(shell pkg-config --cflags libdpdk)

LDFLAGS += $(shell pkg-config --libs libdpdk)

LDFLAGS += -Wl,--whole-archive,-lfstack,--no-whole-archive

参考 f-stack/example/Makefile 作为标准模板


原因 4:pkg-config 版本过低

pkg-config 版本低于 0.28 时,无法正确解析 DPDK 的 .pc 文件,导致驱动库链接不完整,运行时检测不到网卡。

诊断命令:

pkg-config --version

解决方案:升级 pkg-config 到 0.28+

cd /data

wget https://pkg-config.freedesktop.org/releases/pkg-config-0.29.2.tar.gz

tar xzvf pkg-config-0.29.2.tar.gz

cd pkg-config-0.29.2

./configure --with-internal-glib

make && make install

mv /usr/bin/pkg-config /usr/bin/pkg-config.bak

ln -s /usr/local/bin/pkg-config /usr/bin/pkg-config

验证版本

pkg-config --version # 应显示 0.29.2


原因 5:Mellanox(MLX5)网卡缺少 glue 库

使用 Mellanox ConnectX 系列网卡时,DPDK 的 MLX5 PMD 需要额外的动态链接库支持。

错误日志:

net_mlx5: cannot load glue library: librte_pmd_mlx5_glue.so.xx.xx.x:

cannot open shared object file: No such file or directory

net_mlx5: cannot initialize PMD due to missing run-time dependency

on rdma-core libraries (libibverbs, libmlx5)

解决方案:

# 1. 安装 rdma-core 依赖

apt-get install rdma-core libibverbs-dev libmlx5-1 # Ubuntu

yum install rdma-core libibverbs libmlx5 # CentOS

2. 复制 glue 库到系统路径

cp /data/f-stack/dpdk/build/lib/librte_pmd_mlx5_glue.so.* /lib64/

ldconfig

3. 编译 DPDK 时启用 MLX5 支持

cd /data/f-stack/dpdk

make config T=x86_64-native-linuxapp-gcc

sed 's/CONFIG_RTE_LIBRTE_MLX5_PMD=n/CONFIG_RTE_LIBRTE_MLX5_PMD=y/g' \

-i build/.config

make clean && make && make install

4. 修改 config.ini 指定 PCI 地址

[dpdk]

pci_whitelist=0000:03:00.0


原因 6:在虚拟机中运行 / 无物理网卡

在笔记本、虚拟机或容器中没有 DPDK 兼容物理网卡时,需使用虚拟设备(vdev)。

使用 net_ring(内存环回设备):

[dpdk]

lcore_mask=1

channel=4

vdev=net_ring0

port_list=0

[port0]

addr=10.0.0.2

netmask=255.255.255.0

broadcast=10.0.0.255

gateway=10.0.0.1

注意: net_ring 仅用于测试/开发,不适用于生产环境。

在虚拟机中修复 igb_uio:

虚拟机中 pci_intx_mask_supported() 可能返回 false,需修改内核模块代码:

// 文件:f-stack/dpdk/kernel/linux/igb_uio/igb_uio.c 第 274 行

// 修改前:

if (pci_intx_mask_supported(udev->pdev)) {

// 修改后:

if (true || pci_intx_mask_supported(udev->pdev)) {

修改后重新编译 DPDK:

cd /data/f-stack/dpdk

meson -Denable_kmods=true build

ninja -C build && ninja -C build install


原因 7:使用 DPDK 共享库(.so)时驱动未加载

当使用 DPDK 动态库(CONFIG_RTE_BUILD_SHARED_LIB=y)时,PMD 驱动不会自动加载。

解决方案:

  • 切换到 dev 分支代码(已修复此问题)
  • 或改用静态库链接方式

原因 8:Rust binding 链接问题

在 Rust 项目中使用 F-Stack 绑定时,需要在构建脚本中明确链接 NIC 驱动库:

// build.rs

println!("cargo:rustc-link-lib=fstack");

println!("cargo:rustc-link-lib=rte_net_bond"); // 需要显式添加驱动库

// 使用 pkg-config 链接 DPDK

pkg_config::Config::new()

.print_system_libs(false)

.probe("libdpdk")

.unwrap();

注意: 确保 pkg-config 版本 ≥ 0.28,否则驱动库无法被正确识别。


三、快速排查流程

遇到 “No probed ethernet devices” 时,按以下步骤逐一排查:

Step 1: 检查网卡是否绑定 DPDK 驱动

└─ dpdk-devbind.py --status

├─ 未绑定 → 绑定 igb_uio 或 vfio-pci(见原因1)

└─ 已绑定 → 继续 Step 2

Step 2: 检查 config.ini 的 pci_whitelist/allow 是否正确

└─ 与 PCI 地址不匹配 → 修正 config.ini(见原因2)

└─ 匹配 → 继续 Step 3

Step 3: 检查是否自定义 Makefile / 应用

└─ 自定义 → 检查 --whole-archive 链接参数(见原因3)

└─ 使用示例 → 继续 Step 4

Step 4: 检查 pkg-config 版本

└─ < 0.28 → 升级 pkg-config(见原因4)

└─ ≥ 0.28 → 继续 Step 5

Step 5: 检查网卡类型

├─ Mellanox → 安装 rdma-core / 复制 glue 库(见原因5)

├─ 无物理网卡/虚拟机 → 使用 vdev 或修复 igb_uio(见原因6)

└─ 其他 → 确认网卡在 DPDK 支持列表中


四、验证步骤

修复后,按以下步骤验证:

# 1. 验证网卡绑定状态

python3 /data/f-stack/dpdk/usertools/dpdk-devbind.py --status

2. 用 testpmd 验证 DPDK 可以检测到设备

dpdk-testpmd -l 0-1 -n 4 -a 0000:00:03.0 -- -i

若看到 "Found 1 port(s)" 则正常

3. 运行 helloworld 验证 F-Stack 正常启动

cd /data/f-stack/example

./helloworld --conf /etc/f-stack.conf --proc-type=primary --proc-id=0

正常应显示 port 初始化信息,不出现 "No probed ethernet devices"


五、相关 issue 汇总

Issue 状态 关键场景 解决方案
#1035 Open 使用 vdev=net_ring0 但无效 检查 vdev 配置格式
#837 Open 阿里云服务器,eth0 仍使用内核驱动 绑定网卡到 DPDK 驱动
#693 Open 自定义应用中 ff_init 失败 Makefile 添加 –whole-archive
#663 Open Rust binding 运行失败 显式链接 PMD 驱动库
#583 Closed 使用 DPDK .so 动态库 切换到 dev 分支
#581 Closed helloworld 运行失败 升级 pkg-config ≥ 0.28
#531 Closed Mellanox MLX5 网卡 复制 glue 库到 /lib64
#386 Closed ixgbe 网卡,驱动未绑定 绑定 igb_uio 驱动
#379 Closed 自定义 PMD 驱动未链接 在 Makefile 中链接 .a 文件

六、参考资料


*本文档由 OpenClaw 自动整理,基于 F-Stack GitHub issue 历史数据和 DPDK 官方文档。*

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 的权威 DNS 单机 1 亿 QPS 性能优化实践

腾讯云 DNSPod 的权威 DNS 解析目前包含两个版本,基于 F-Stack 的 FTDNS 和基于内核协议栈的 DPDNS,其中 FTDNS 目前性能远高于 DPDNS,是腾讯云对外提供权威 DNS 服务的主力,而 DPDNS 目前主要用作异构容灾及部分特殊场景的部署,如部分只能使用 CVM 或私有化部署的场景。

本文主要介绍 FTDNS 在对 100G 机型进行适配优化的过程,UDP DNS 如何达到单机 1 亿 QPS 的性能,主要涉及网络 I/O 的性能优化和 DNS 解析计算资源的平衡分配,对于 TCP DNS 和内核版的 DPDNS 的性能优化后续将专门进行介绍。

测试平台

测试程序:FTDNS,权威DNS解析程序
F-Stack: 1.21(DPDK 19.11)
OS: Tencent tlinux release 2.2 (Final)
Kernel: 4.14.105-1-tlinux3-0020
CPU: AMD EPYC 7K62 48-Core Processor * 2
NIC: Mellanox Technologies MT28800 Family [ConnectX-5 Ex]
Device type: ConnectX5
Name: MCX516A-CDA_Ax_Bx
Description: ConnectX-5 Ex EN network interface card; 100GbE dual-port QSFP28; PCIe4.0 x16; tall bracket; ROHS R6

Run to completion 架构

因为 DNS 解析的总体逻辑相对比较简单,除了网络 I/O 外只有查找本地缓存查找,所以 FTDNS 在常规架构下完全使用 RTC(Run to completion) 架构以达到更好的性能,网卡收发队列、协议栈、应用与 CPU 核心一一绑定,如下图所示

FTDNS(RTC)
【注】简化结构,仅简单示意 UDP DNS

对于 UDP DNS 请求,直接通过网卡的 RSS 功能将请求包负载收包至已经绑定到各个队列和 CPU 的 FTDNS 业务进程中,并且使用 F-Stack lib 提供的 ff_regist_packet_dispatcher() 函数直接注册回调函数对 UDP 的 DNS 请求包直接进行应用层的解析处理,并直接回包,旁路掉同为用户态的 FreeBSD 的网络协议栈。

性能测试

在拿到 100G 的机器后,使用原有程序直接进行了性能测试,最优性能表现如下图所示

32 进程 RTC QPS
32 进程 RTC CPU 使用率

该测试机型单个 NUMA 节点的 CPU 为 48 核 96 线程 ,对应一张 100G 网卡,为了达到最优性能,正常只使用 48 个物理核进行测试,通过对不同核数分别进行测试,发现在使用 32 核左右时才能达到最优性能,但是也只有 76M 的 QPS,离一亿的小目标差距有点大,接下来进行瓶颈分析。

瓶颈分析

因为直接启动 48 个业务进程测得的性能很低,只有 35M QPS 左右,而此时 CPU 并没跑满,空闲都在一半以上,如下图所示

48 进程 RTC QPS
48 进程 RTC CPU 使用率

所以分别启动不同数量的业务进程分别测得性能数据,且为了排除 FTDNS 业务应用本身性能的影响,同时做了部分关闭 DNS 实际解析逻辑(其他处理逻辑保留)的 echo server 的对比测试,测试结果大致如下

进程数 空载性能(QPS) DNS性能(QPS)
4 80 M /
8 107 M 19 M
12 113 M 28 M
16 113 M 38 M
20 84 M 47 M
24 80 M 56 M
32 77 M 76 M
40 40 M 40 M
48 37 M /

很明显,从以上数据可以得出几个结论:

  1. 收发包性能受队列数影响很大:当队列数较少时,收发包性能主要受 CPU 性能本身影响;当接近 16 个队列时,收发包性能最优,可以达到 113M QPS(非单纯收发的最高性能,因为还包括其他处理逻辑),之后性能急剧下降,分析主要是受 PCIE 通道数量为 16 个的影响,当队列数超过 16 后造成性能下降(此条以后如拿到其他配置机型后可做对比测试进行验证)。
  2. DNS 解析查询性能可线性扩展:当进程数较少时,FTDNS 的性能主要受限于 CPU 的计算性能,在达到收发包极限前,性能可以随进程数增长而线性增长(纯内存缓存、无共享、无锁等),超过 32 进程之后则受收发包性能影响,CPU 出现大量空闲。

优化方向

1. 调整网卡相关参数

通过查看 MLX5 网卡的相关参数文档,对部分参数进行组合调整并压测验证,如 mprq_en, rxqs_min_mprq, mprq_log_stride_size, mprq_max_memcpy_len, txqs_min_inline, txq_mpw_en 等,期望可以达到在网卡开启更多接收队列(如 48 个)也能达到更高的收发包性能,最终通过大量的测试表明,在48队列时网卡的收发包性能依然很低,辅证是由 PCIE 通道数引起的性能下降,此处不再详细展开。

2. 架构优化

2.1 Pipeline

既然网卡开启 16 个接收队列时可以达到最好的接收性能,那么很自然的一个想法,将业务程序的架构由 Run to completion 改为 Pipeline 架构,只开启 16 个接收队列专门用于接收 DNS 请求,再通过 rte ring 将请求报转发到其他业务进程进行实际的 DNS 解析后再直接发送出去,虽然单进程性能会因为 CPU cache miss 的问题急剧下降,但是可以使用更多的 CPU 运行业务进程来弥补,架构简图如下所示。

Pipeline 架构
【注】红色箭头为与 RTC 架构的数据包流向的区别

F-Stack 本身已经提供了函数 ff_regist_packet_dispatcher() 用于对接收到的数据包进行重新分发,且 FTDNS 中已经使用来直接进行 UDP DNS 的解析,稍作修改前 16 个进程仅分发,不再处理 DNS 解析即可,但是实际测试发现转发性能太差,perf 分析主要是软分发回调函数是对数据包一个个进行处理的,性能较差。

所以不得不侵入式修改 F-Stack 的转发逻辑,在 F-Stack 的收发包主循环中,
前 16 个进程 在接收到数据包后直接批量根据配置转发到业务进程中,转发到 ring 的性能得到提升,但业务进程单核性能也下降明显,实测结果如下图所示,

48 进程 Pipelie QPS
【注】该统计将 F-Stack 对流量信息统计进行了修改,软分发也统计到了 worker 进程上,更直观

48 进程 Pipelie CPU

此时 48 核为同一个 NUMA 节点的物理核心,全部使用后性能可以达到 68M QPS,单核性能下降约 12%,符合前期的心里预期,查看 perf 信息,热点也符合预期(如下图所示)。

跨 CPU 访问数据热点

如果有更多业务进程,是否就可以达到小目标了呢?因为机器架构限制的原因,本物理核的另外 48 个超线程核心很难使用到,且根据历史经验,即使使用性能提升也非常小,所以直接继续使用另一个 NUMA 节点的 CPU 的物理核心进行进一步测试,可以用到 80 个 CPU 核心,对应的收包分发线程调整为 20 进程,测试得到 Pipeline 的最优性能如下图所示。

80 进程 Pipeline QPS
【注】:此处 PPS 统计使用的是 F-Stack 原始统计方式,不够直观

80 进程 Pipeline CPU

性能有很大进展,已经达到了 95M QPS,离小目标不远了,但是也可以看出来进程 48 – 79 跨 NUMA 访问的 32 个进程 CPU 使用率是高于本 NUMA 节点的 CPU 的,查看 perf 信息同样证明了跨 NUMA 访问存在的问题。

此架构下继续调整尝试多次,包括继续增加 CPU 等,也无法达到更好的性能,只好尝试其他方向。

2.2 Run to completion + Pipeline

分析 48 进程 Pipeline 架构的 CPU 图可以发现,dispatcher 进程的 CPU 尚有一半空闲,是否也可以利用起来呢?

继续对分发部分代码进行稍微的修改, dispatcher 进程可以支持按照配置的分发进程数自动将收包分别交由本进程的应用层进行解析处理或者通过 Ring 转发到其他 worker 进程,架构如下图所示

RTC + Pipleline 架构
【注】:红色箭头表示与 Pipeline 架构不同的数据包流向

经过多次尝试调整不同进程的数量以及数据包分发比例,最终还是每个 dispatcher 与 worker 进程处理相同量的数据包,但是一个 dispatcher 进程可以根据配置自动对应多个 worker 进程,并测试得到了此时的最优性能,如下图所示

48 进程 RTC + Pipeline PPS
48 进程 RTC + Pipeline CPU

此处达到了 92M的性能,但是总性能还是略差于 Pipleline 性能,优势就是只使用了 48 个 CPU,使用 24 dispatcher + 24 worker 与 16 dispatcher + 32 worker 总体性能相当,此时虽然 dispatcher 进程依然有部分 CPU 空闲,但是增加其 DNS 处理比列并不能提升整体性能了,主要是因为如果分配更多的 CPU 时间片到 DNS 业务处理,则整体轮询次数减少,导致收包能力下降。

此时但是小目标依然没能实现,还需要考虑其他优化点。

2.3 单进程性能提升

在 RTC 测试中,单核 DNS 的性能虽然达到 2.38M QPS,已经很高了,但是通过 perf 分析在 DNS 业务处理逻辑中查询缓存时的 Hash 和 Key 的字符串比较函数占用 CPU 较高,依然有一定的优化空间,先看下 FTDNS 内存缓存结构,如下图所示

FTDNS 内存缓存结构

内存缓存是常规的 Hash 表结构,解决 Hash 冲突使用的是拉链法,存储的是写入时已经组好 DNS 应答包格式的数据,且无共享(无任何多写的数据结构)、无锁(类似 RCU 锁思想,但是并不加锁)等。

对 Hash 的 Key 进行优化,去除了部分不必包含和字符串比较函数进行优化,去除了部分非必要的字段,减少了需要进行 Hash 计算的 Key 长度。

因为大部分 Key 较短,暂未使用 memcmp() 进行比较, 因为仅需要判断 Key 是否相等即可,所以自行实现字符串比较函数,去除标准比较函数中多余的操作。

优化完成后,Pipeline 和 RTC + Pipeline 架构的性能都达到了 1 亿(100M) QPS 的小目标,性能测试结果如下所示

80 进程 Pipeline QPS100M
80 进程 Pipeline CPU 100M
48 进程 RTC + Pipeline QPS 100M
48 进程 RTC + Pipeline CPU100M

虽然两种架构的性能都达到了目标,但是综合考虑 RTC + Pipeline 只需要使用一个 NUMA 节点的 48 个物理核心即可,可以节省大量的 CPU 计算资源,且性能只是略低,所以最终在 FTDNS 中采用 RTC + Pipeline 的架构,常规配置 dispatcher 进程数量为 0 表示使用 RTC 模式,如有需要可以随时修改配置切换为 RTC + Pipeline 模式。

其他问题

  1. 本次测试使用的是 AMD CPU + Mellanox 的网卡,对于其他组合后续拿到其他组合的机型(如 Intel 的 CPU,Intel 或 Broadcom 的网卡等)也需要进行对应的测试,测试性能及验证队列数多于 PCIE 通道数时性能下降的问题,该问题也与 Intel 工程师有过交流。
  2. 跨 CPU 核心访问数据性能下降问题 Intel 工程师表示后续会有新的指令集可能对性能提升会有帮助。
  3. 后续还需要对 TCP DNS,或者说 F-Stack 在 100GE 机型上的短/长链接 TCP 进行测试和优化。

国内主要公共 DNS 支持 ECS 情况测试 – 20210315

本次测试仅通过黑盒测试分析不同公共 DNS 当前(20210315)是否支持 ECS,及方案上的一些差异,相关方案的优劣不进行额外说明。

结论

厂商IP是否支持ECS
腾讯云/DNSPod119.29.29.29、119.28.28.28
阿里223.5.5.5、223.6.6.6
DNS派101.226.4.6、123.125.81.6等
OneDNS117.50.11.11、52.80.66.66
114114.114.114.114、114.114.115.115
CNNIC1.2.4.8、210.2.4.8
百度180.76.76.76

细节差异简析

腾讯云/DNSPod

  1. 绝大部分后端递归节点是支持 ECS 的节点,少部分递归节点为不支持 ECS 的节点;其中不支持 ECS 的节点仅会对本省本运营商的请求进行递归, 支持 ECS 的递归节点可为所有线路进行递归。
  2. 支持 ECS 的递归节点携带用户的实际 IP 作为 ECS IP 向权威 DNS 进行请求,为防源 IP 泄漏,统一格式化为 x.x.x.1/32。
  3. 缓存层按照省份运营商线路(如广东电信)进行缓存,减少缓存量。

阿里

  1. 部分后端递归节点是支持 ECS 的节点,部分递归节点为不支持 ECS 的节点;;其中不支持 ECS 的节点仅会对本省本运营商的请求进行递归, 支持 ECS 的递归节点为未部署后端递归节点的线路进行递归。
  2. 支持 ECS 的递归节点不会携带实际用户的 IP 向权威 DNS 请求,而是携带相关线路固定的 IP 作为 ECS IP 向权威 DNS 进行请求,并格式化为 x.x.x.0/24,目的可能是为了减少递归 DNS 节点的缓存量。
  3. 中国移动线路在未选择到后端递归节点的省份中,似乎都选择了相同的一个移动 IP 作为 ECS IP,没有为不同省份选择不同的 ECS IP。
  4. 缓存层按照省份运营商线路(如广东电信)进行缓存,减少缓存量。

DNS 派

  1. 绝大部分后端递归节点是支持 ECS 的节点,少部分递归节点为不支持 ECS 的节点。
  2. 支持 ECS 的递归节点携带用户的实际 IP 作为 ECS IP 向权威 DNS 进行请求,为防源 IP 泄漏,统一格式化为 x.x.x.0/24。
  3. 无缓存的应答结果可能返回多个 ECS 段。
  4. 缓存层按照省份运营商线路(如广东电信)进行缓存,减少缓存量。
  5. 海外转发至其他公共 DNS,如 CloudFlare 的 1.1.1.1。

OneDNS

  1. 本身的后端递归节点不支持 ECS,部分省份运营商会转发至腾讯云/DNSPod 的公共 DNS 进行解析。
  2. 海外请求全部转发至腾讯云/DNSPod 的公共 DNS 进行解析。

其他

  1. 测试的114、CNNIC、百度等公共 DNS 暂不支持 ECS。
  2. 支持 ECS 的公共 DNS 部分不支持 ECS 的递归节点不一定是真的不支持,不排除 DNS 请求被重定向的可能。

Nginx TCP 多证书透明代理及 Linux/F-Stack(FreeBSD) 路由相关设置

某个 TCP 服务对外有多个域名提供相同的服务,且每个域名都是基于 TLS 的,需要通过 Nginx 对 TLS 进行卸载后转发到实际的上游服务,且上游服务必须使用客户端的源 IP,所以 Nginx 需要使用透明代理。分别需要对Nginx 和系统路由进行配置。

Nginx 配置

需要 Nginx 1.15.9 以上版本,简化配置如下所示,

stream {       
  upstream up_server {
      server 192.168.1.3:8081;
  }
   
  # 通过 map 配置不同域名(SNI)使用不同的证书文件
  # 证书为泛解析证书, 匹配泛解析域名
  # 会降低性能
  map $ssl_server_name $targetCert {
      ~*domain1.com$ /usr/local/cert/domain1.crt;
      ~*domain2.com$ /usr/local/cert/domain2.crt;
      ~*domain2.com$ /usr/local/cert/domain3.crt;
      default /usr/local/cert/domain1.crt;
  }
   
  map $ssl_server_name $targetCertKey {
      ~*domain1.com$ /usr/local/cert/domain1.key;
      ~*domain2.com$ /usr/local/cert/domain2.key;
      ~*domain2.com$ /usr/local/cert/domain3.key;
      default /usr/local/cert/domain1.key;
  }
   
  server {
      listen 8080 ssl reuseport;
   
      ssl_certificate $targetCert;
      ssl_certificate_key $targetCertKey;
      ssl_protocols TLSv1.2 TLSv1.3;
      ssl_session_tickets off;

      proxy_pass up_server;
      proxy_bind $remote_addr transparent; # 透明代理
  }
}

Linux 系统路由配置

因为透明代理的源 IP 是实际客户的 IP,在实际服务接受处理完响应包返回时会返回给实际的客户 IP,所以需要配置将回包发到 Nginx 进行处理,这里的上游服务为本机服务,需进行如下配置。如上游在其他服务器上,可以查看参考资料中文章并进行对应配置。

# 新建一个 DIVERT 给包打标签
iptables -t mangle -N DIVERT;
iptables -t mangle -A DIVERT -j MARK --set-mark 1;
iptables -t mangle -A DIVERT -j ACCEPT;

# 把本机 TCP 服务的回包给 DIVERT 处理
iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 8081 -j DIVERT

# 有标签的包去查名为 100 的路由表
ip rule add fwmark 1 lookup 100

# 100的路由表里就一条默认路由,把所有包都扔到lo网卡上去
ip route add local 0.0.0.0/0 dev lo table 100

F-Stack(FreeBSD) 路由配置

F-Stack(FreeBSD) 上游回包路由配置,

# upstream 为本机时
# 假设 f-stack-0 的 IP 为 192.168.1.3,将 upstream 往外发的所有出包都转发到 F-Stack Nginx 监听的 IP 和端口即可
# 因为转发到本机地址时目的端口会被忽略,可不设置端口
ff_ipfw add 100 fwd 192.168.1.3,8080 tcp from 192.168.1.2 8081 to any out

# upstream 为其他机器时
# 将 upstream 通过设置网关或者 IP 隧道(需额外进行隧道配置)等方式发过来的所有入包都转发到 F-Stack Nginx 监听的 IP 和端口即可
# 因为转发到本机地址时目的端口会被忽略,可不设置端口
ff_ipfw add 100 fwd 192.168.1.3 tcp from 192.168.1.2 8081 to any in via f-stack-0

参考资料

  1. nginx TLS SNI routing, based on subdomain pattern
  2. 使用nginx的proxy_bind选项配置透明的反向代理
  3. [sslh] Using sslh transparent proxy on FreeBSD?

过去的2020

又到了本命年,过去的一年大事发生了很多事,不管是疫情、水灾、航天、矿难,虽一直在默默关注,但个人 能做的有限,还是只简单总结下自己过去的一年吧。

家庭关系好多了,前两年陪孩子太少,现在陪的多了,明显亲密多了,当然想达到孩他妈的程度还是差远了。另外发生了一件比较伤心伤身的事情,孩他妈辛苦了。 身体急剧变差,上半年疫情严重时期长时间在家呆着一直不出门,吃得多,运动少,工作也太拼,造成整个人持续失眠、焦虑、压力大、暴躁易怒、体重剧增20斤,基本每天都是忙到凌晨,躺下睡不着,经常要差不多天亮才能睡着。下半年对工作和生活作息做了很大的调整,精神状态算是慢慢好了不少,身体又出毛病了,经常性的头疼、颈椎疼、腰疼,尤其是腰,弯一会能疼一天,还好各种检查虽然有点小异常,但没啥大问题,经过多休息、调整坐姿站姿少做剧烈运动,换了个有泳池的健身房,大幅减少了跑步,游泳占了运动的一多半,到年底算是有好转,没那么严重了。当然也有好消息,下半年把20斤的体重又减下去了,回到了去年底的水平。

工作上两个主要产品权威DNS和公共DNS/HTTPDNS各开发发布了一个大版本,以及N个小功能小版本,遗憾的是F-Stack未能抽出时间去做一直计划要做的一些大改动,只有一些小改动,发布了一个小版本。21年还会有各自的大版本开发发布,也会在F-Stack上投入更多一点精力。另外因为整个人的精神状态太差,在工作中明显会出现多次急躁不耐烦,语气有时候比较冲的情况,向所有人说句对不起了,会努力调整自己的状态。