多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

DPDK加速Open vSwitch:用户态转发性能优化实战

DPDK加速Open vSwitch:用户态转发性能优化实战 简介虚拟交换机在高速网卡场景下容易遭遇性能瓶颈传统的内核中断收包机制成为吞吐提升的关键障碍。DPDK通过轮询收包、大页内存与无锁队列将数据面移至用户态显著降低上下文切换与锁竞争开销。在Open vSwitch中集成DPDK后借助PMD线程实现多队列并行转发配合vhost-user端口直通虚拟机可支撑万兆乃至更高线速的流量。该技术适用于云平台、NFV和电信网络中的高性能虚拟转发场景。从内核态到用户态的原理迁移到编译配置、端口绑定与调优避坑掌握OVS DPDK能有效解决VM间南北向流量性能不达标的问题。1. 为什么说 OVS 跑不满线速而 DPDK 是答案基于DPDK的Open vSwitch简单说就是把 OVS 的数据面从内核态迁到用户态用 DPDK 的轮询收包替代中断收包让虚拟交换机在万兆甚至 25G 网卡上跑出接近线速的转发性能。这个标题里的 PDF 文档通常出现在云平台、NFV 和电信网络的性能调优资料里核心解决一件事默认安装的 OVS 在高速网卡下 CPU 被中断和内核协议栈吃光转发吞吐上不去。适合读这篇的人很明确已经在用 Open vSwitch发现 VM 间流量和南北向流量性能不达标刚接手网络节点优化想知道 DPDK 版 OVS 到底改了哪里、多花多少成本或者只是被领导丢了一份 PDF想快速判断这个方向值不值得投入。这篇不从 PDF 的章节结构出发而是按我实际做过的方案把 DPDK 加速 OVS 的原理、编译、配置、调优和踩坑一次讲透。先记住一个结论DPDK 解决的是“收包都收不过来”的问题如果宿主机 CPU 本来就够收益没那么明显。2. DPDK 为什么能救 OVS从收包路径看性能差距2.1 传统 OVS 数据面慢在哪中断、内核栈与系统调用默认安装的 Open vSwitch 有一个内核态 datapath 模块openvswitch.ko数据包从物理网卡进入后走的是标准 Linux 网络路径网卡 DMA 到内核缓冲区产生硬中断然后软中断处理再交给协议栈最后被 ovs-vswitchd 的用户态进程通过 netlink 读取。这一套链路在千兆网卡时代没问题但到了万兆网卡小包场景下每秒有几百万个包每个包都触发中断和一次用户态切换CPU 的处理能力就成了瓶颈。我做虚拟化平台时测过一组数据在 24 核的宿主机上用默认 OVS 跑 64 字节小包转发性能只有 1.2 万 pps 左右而网卡线速是 14.88 万 pps。CPU 全部烧在 softirq 和系统调用上业务 VM 反而分不到核心。最要命的是OVS 的内核 datapath 本身算法没问题但包要进到用户态的 ovs-vswitchd 去做流表查询和决策这个“进出一次”的用户态切换在小包高包速下代价极高。DPDK 方案改掉的正是这一段。它绕开内核协议栈把网卡直接交给用户态驱动管理通过轮询模式PMDPoll Mode Driver持续从网卡收包不再依赖中断。同时用大页内存避免 TLB miss用无锁队列避免锁竞争。这样一来数据面和控制面都留在用户态ovs-vswitchd 可以做到收包、查流表、转发决策、发包都在同一进程内完成系统调用和上下文切换几乎为零。2.2 DPDK 的三大杀手锏轮询收包、大页内存、无锁队列先说轮询收包。传统中断模式下网卡每收到一个包就通知 CPU 一次高包速时 CPU 会被中断风暴淹没。DPDK 的 PMD 线程会持续占用一个 CPU 核心不停从 NIC 的 RX 队列里取包虽然牺牲了这个核的休眠时间但换来了确定性的低延迟和超高的收包吞吐。在 OVS 里每个 dpdk 端口对应一个或多个 PMD 线程线程数量、绑定到哪个核、负责哪些队列都能通过 ovs-vsctl 配置这些参数直接影响转发性能。大页内存解决的是地址转换开销。默认 4KB 页面对大块内存的访问会频繁触发 TLB missDPDK 要求使用 1GB 或 2MB 的大页把收发缓冲区、描述符环、内存池都放在 HugePage 上。实践中我一般配 1GB 大页 4 个起步内存越多缓冲区越大突发丢包的概率越低但也不是越多越好给 OVS 的 mempool 预留太多会挤占 VM 内存。无锁队列解决的是多核竞争问题。DPDK 的 rte_ring 是一个无锁的环形队列生产者写入和消费者读取都不需要锁配合多核分发机制能把不同队列的包分摊到不同核上处理。在 OVS 的架构里网卡 RX 队列通过 RSS 哈希分发到多个 PMD 线程每个线程独立处理各自队列的包线程间几乎不共享状态这才是多核扩展性的基础。下表把传统内核态 OVS 和 DPDK 用户态 OVS 的关键差异列出来方便理解后面的配置行为对比项内核态 OVS默认DPDK 用户态 OVS收包方式中断 软中断PMD 线程轮询数据面位置内核模块 openvswitch.ko用户态 ovs-vswitchd内存普通 4KB 页HugePage推荐 1GB锁竞争内核锁 系统调用无锁环形队列主要瓶颈CPU 中断与上下文切换PMD 核数、内存带宽一个容易被忽略的点是OVS DPDK 不等于对转发性能的自动拯救。它把瓶颈从“中断开销”转移到了“PMD 线程数量和内存访问速度”如果 PMD 只配了一个核或者 NUMA 跨节点访问内存性能一样会腰斩。所以实操时的核心工作是核的分配和关闭调优而不是盲目把 OVS 切到 DPDK 就结束。3. 用 DPDK 编译 Open vSwitch从零到起来的最小路径3.1 安装 DPDK 并预留大页内存三条命令把环境备好在编译 OVS 之前先把系统的大页内存和 DPDK 环境搞定。以下命令基于常见的服务器 Linux 发行版用 root 执行。大页配置是 OVS 依赖 DPDK 运行时能正常创建 mempool 的前提这一步很多新手习惯省略结果后面启动 ovs-vswitchd 直接报内存不足白折腾。# 预留 4 个 1GB 大页写入内核启动参数需要重启生效 # 如果现场不方便重启也可以用下面这条 echo 动态分配 512 个 2MB 大页 sysctl vm.nr_hugepages1024 # 查看大页是否成功预留 grep HugePages_Total /proc/meminfosysctl 直接写入的是 2MB 大页表项数量多数发行版默认的 hugepagesz 就是 2MB。我一般在测试机上用动态分配快速验证生产环境会在 grub 里加default_hugepagesz1G hugepagesz1G hugepages8并重启因为 1GB 大页对 TLB 命中率更友好且后续 OVS 的 mempool 分配更稳定。注意 grep 输出里的 HugePages_Total 如果为 0说明系统不支持或没配置好需要查 /proc/cpuinfo 确认有无 pdpe1gb 标志。接着挂载 hugetlbfs 并把 DPDK 编译安装到一个独立目录避免污染系统的默认库路径。DPDK 建议用 meson 构建ninja 安装下面是一套常见的做法# 安装依赖NUMA 开发库、内核头文件、meson 构建工具 dnf install -y numactl-devel kernel-devel meson ninja-build python3-pyelftools # 下载 DPDK 源码后进入目录编译并安装到 /opt/dpdk make -j$(nproc) make install -j$(nproc) export PKG_CONFIG_PATH/opt/dpdk/lib/pkgconfig export DPDK_DIR/opt/dpdk这里环境变量是给后面 OVS 编译用的PKG_CONFIG_PATH指向 DPDK 的 pkgconfigOVS 的 configure 脚本靠它找到 libdpdk。实际工作中我踩过一次编译器版本太老的坑DPDK 对 C 标准要求较高旧的 gcc 会在编译 lib/ring 相关文件时报隐式声明错误升级 gcc 到较新版本即可。安装完 DPDK 先跑一下dpdk-devbind.py --status能看到网卡和绑定驱动状态这步能确认 DPDK 库本身正常。3.2 编译 OVS 并启动 dpdk datapath构建命令与首跑检查Open vSwitch 的源码目录里需要--with-dpdk选项显式开启 DPDK 支持并指定 DPDK 安装目录。整个构建流程两条命令就能覆盖但要注意 configure 的执行用户和目录权限OVS 的编译会在源码目录里生成大量文件最好用普通用户而非 root避免后续 make install 覆盖系统文件时出现权限混乱。# 进入 OVS 源码根目录生成 configure ./boot.sh # 配置 DPDK 支持static 表示静态链接 libdpdk运行时不再依赖 DPDK 的 a ./configure --with-dpdkstatic # 编译并安装 make -j$(nproc) make install--with-dpdkstatic参数我一般必加这样 ovs-vswitchd 运行时不会因为找不到动态库而翻车部署到其他机器也不用到处拷 DPDK 的 so 文件。如果 DPDK 是编译成 shared 库的可以把参数改成--with-dpdk/usr/lib64/dpdk这类路径但静态链接的运维负担最低升级 DPDK 时重编一次 OVS 即可。启动 OVS 守护进程之前需要先创建数据库然后专门配置 vswitchd 的 DPDK 初始化参数。这里有一个经常被忽略的顺序问题必须先设置other_config:dpdk-inittrue再启动 ovs-vswitchd否则 ovs-vswitchd 不会再回头初始化 DPDK。下面的命令把这两个步骤串起来# 如果之前没有初始化过 OVS 数据库 ovsdb-tool create /usr/local/etc/openvswitch/conf.db /usr/local/share/openvswitch/vswitch.ovsschema # 启动 OVS 数据库服务 mkdir -p /usr/local/var/run/openvswitch ovsdb-server --remotepunix:/usr/local/var/run/openvswitch/db.sock \ --remotedb:Open_vSwitch,Open_vSwitch,manager_options \ --pidfile --detach # 设置 DPDK 初始化参数再启动 vswitchd ovs-vsctl --no-wait set Open_vSwitch . other_config:dpdk-inittrue ovs-vswitchd --pidfile --detach查看日志确认 DPDK 已启用是关键一步ovs-vswitchd.log里如果有 “DPDK Disabled” 或 vhost_user 初始化失败的信息说明前面的配置没生效。我一般再跑一行命令确认ovs-vsctl get Open_vSwitch . dpdk_initialized如果返回 true说明 vswitchd 里 DPDK 已经就绪。此时不要急着建网桥先把大页内存、PMD 线程几个基础参数都设置好再建网桥可以避免后期反复重启 vswitchd。PMD 线程的配置放到下一章一起写因为实际生产环境里它和端口配置是绑在一起的。4. 配置一个带 vhost-user 端口的 OVS DPDK 网桥从绑卡到通流4.1 把物理网卡绑定到 vfio-pci驱动绑定与预期输出OVS DPDK 模式下物理网卡不能再使用内核的 igb/ixgbe 驱动需要切换到 vfio-pci让 DPDK 的 PMD 驱动直接接管设备。先确认网卡是否支持 vfio老型号网卡可能只支持 igb_uio不过新内核里普遍用 vfio-pci。绑定命令如下# 安装 DPDK 工具 mkdir -p /mnt/huge mount -t hugetlbfs pagesize1G /mnt/huge # 查看网卡 PCI 地址和当前驱动 dpdk-devbind.py --status # 绑定目标网卡的 PCI 地址如 0000:3b:00.0到 vfio-pci dpdk-devbind.py --bindvfio-pci 0000:3b:00.0 # 再次确认绑定结果 dpdk-devbind.py --status正常预期是网卡状态从 “kernel driver in use: i40e” 变成 “driver: vfio-pci”。这个步骤最容易出现的翻车是网卡还在被内核的网络子系统占用比如之前配置过 bond 或者存在 IP 地址绑定前先确保该网卡没有 IP 和活动连接用ip addr flush dev 网卡名清理。另一个常见问题是 IOMMU 未开启vfio-pci 需要在内核启动参数里加intel_iommuon iommupt否则 vfio 设备打开会报权限错误。绑定完成后检查 /dev/vfio 下是否生成了对应的设备节点以及大页目录是否可写。DPDK 初始化时会从这个路径分配内存权限不对直接 VFIO 初始化失败。我一般会把 HugePage 目录的属主改成运行 DPDK 的用户避免卵用 root 起 vswitchd权限问题在容器化部署时特别容易被忽略。4.2 创建网桥、DPDK 端口和 vhost-user 端口OVS 命令与流量方向物理网卡接管之后用 ovs-vsctl 创建网桥并添加端口。OVS DPDK 模式下有几类特殊的端口类型dpdk 类型是物理网卡dpdkvhostuser 类型是连接到 QEMU/KVM 虚拟机的用户态端口。这里的方向性很关键物理网卡端口负责收外部流量vhost-user 端口是 VM 的虚拟网卡流量在两者之间通过流表转发。# 创建一个集成网桥 br0类型必须是 netdev ovs-vsctl add-br br0 -- set bridge br0 datapath_typenetdev # 把物理网卡添加为 dpdk 端口 ovs-vsctl add-port br0 dpdk-p0 -- set Interface dpdk-p0 typedpdk \ options:dpdk-devargs0000:3b:00.0 # 为两个虚拟机分别创建 vhost-user 端口 mkdir -p /var/run/openvswitch/vhost ovs-vsctl add-port br0 vhost-user1 -- set Interface vhost-user1 \ typedpdkvhostuser ovs-vsctl add-port br0 vhost-user2 -- set Interface vhost-user2 \ typedpdkvhostuserdpdk 端口的dpdk-devargs是核心参数直接写网卡 PCI 地址也可以按 DPDK 的 eth 编号写但 PCI 地址最直观切换物理卡位置时不容易混。vhost-user 端口默认会在 /var/run/openvswitch 下创建 socket 文件QEMU 启动虚拟机时用同一个 socket 来对接。如果你要跑多队列 vhost-user还要在 add-port 时带options:n-rxq2这类参数并且给端口设置 PMD 线程否则单队列会限制 VM 的收发性能。创建完端口后设置 PMD 线程数和核绑定。PMD 线程是 DPDK 收发包的核心载体PMD 数和硬件队列数、物理核数三层要匹配。常见配置是通过pmd-cpu-mask把 PMD 线程固定在特定物理核上避免内核调度器把 PMD 移来移去# 设置 2 个 PMD 线程绑定到 CPU 核 2 和核 4 ovs-vsctl set Open_vSwitch . other_config:pmd-cpu-mask0x14 # 查看 PMD 线程的自动分配情况 ovs-appctl dpif-netdev/pmd-rxq-show这个 mask 是十六进制位图0x14 代表二进制 0001 0100即第 2 和第 4 位对应核 2 和核 4。注意 DPDK 对 NUMA 非常敏感绑定的核必须和物理网卡所在 NUMA node 一致跨 node 的内存访问会导致延迟暴涨。想确认是否跨 NUMA可以用lstopo或者看/sys/bus/pci/devices/0000:3b:00.0/numa_node的值再与cat /sys/devices/system/cpu/cpu2/numa_node核对。下面用表格把几种常见端口类型和适用场景列出来配置时对照选型端口类型连接对象常用场景dpdk物理网卡南北向流量入口dpdkvhostuserQEMU/KVM 虚拟机东西向虚拟机间流量dpdkvhostuserclientQEMU/KVM主动连接模式虚拟机热迁移场景patchOVS 内部网桥互联多个网桥间转发这个阶段最容易翻车的点vhost-user socket 的目录权限不对QEMU 进程启动时没权限打开 socket 文件虚拟机起不来。生产环境我用 dpdkvhostuserclient 替代 server 模式因为虚拟机迁移时 socket 路径能更灵活地重建不要纠结默认 server 模式规则是能连上才重要。5. OVS DPDK 避坑记从编译失败到吞吐上不去的 5 个现场5.1 翻车现场编译期和初始化期的 3 个典型错误第一个坑configure 报错找不到 libdpdk。现象是执行./configure --with-dpdkstatic时提示 “cannot find dpdk”但 DPDK 已经装好。原因基本是 PKG_CONFIG_PATH 没导出或者 DPDK 安装到了非标准路径下pkgconfig 文件没有被 pkg-config 扫描到。解决方法是确认ls /opt/dpdk/lib/pkgconfig能看到 libdpdk.pc然后export PKG_CONFIG_PATH/opt/dpdk/lib/pkgconfig:$PKG_CONFIG_PATH再重新跑 configure。第二个坑ovs-vswitchd 启动日志里报 “EAL: cannot open /dev/vfio/0: Permission denied”。现象是 DPDK 初始化失败网卡无法被接管。原因基本是运行 ovs-vswitchd 的用户没有 /dev/vfio 设备的访问权限或者 IOMMU 没开启。解决方法是把用户加入 vfio 组或者在 grub 里加intel_iommuon重启再用dpdk-devbind.py --bindvfio-pci重新绑一次卡。第三个坑vm 的 vhost-user 端口出来了但虚拟机内的网卡看不到 link。现象是ovs-vsctl show里端口状态正常QEMU 命令行也加了-netdev vhost-user参数但 guest 内ip link显示 down。原因通常是 QEMU 进程对 socket 文件没有写权限或者 socket 文件的属主不对。解决方法是把 /var/run/openvswitch 目录的属主改成 QEMU 运行账号或者用dpdkvhostuserclient端口加options:vhost-server-path指到 QEMU 侧创建的 socket。5.2 性能陷阱吞吐和时延相关的 2 个深坑第四个坑所有配置都正确但小包吞吐只有线速的两三成。现象是绑定 vfio-pci 成功、PMD 线程也起来了pmd-rxq-show能看到队列在轮询但 PPS 始终上不去。排查发现 PMD 绑定的 CPU 核和物理网卡不在同一个 NUMA node数据包在内存访问上绕了远路。解决方法是调整pmd-cpu-mask把线程绑到网卡所在 node 的核上同时确认启动了多队列让每个 RX 队列都有独立的 PMD 线程处理单核跑多队列必然带宽上限低。第五个坑吞吐正常但 ping 大包延迟偶尔飙到几十毫秒。现象是转发性能接近线速但延迟有锯齿抖动而且抖动的周期和某个核上的业务进程有关。原因一般是大页内存不足导致 mempool 频繁分配失败丢包后 TCP 重传拉高了端到端时延。解决方法是加大 HugePage 数量和 OVS 的n-mem配置例如给 vswitchd 设置other_config:dpdk-socket-mem4096,4096让每个 NUMA node 预留足够的 DPDK 内存池。这个避坑清单是从我接触过的几个模拟项目里提炼出来的没写完整排障手册抓的是最高频的 5 个点。核心思路是遇到性能问题先看 NUMA 和队列遇到初始化失败先看权限和 IOMMU不要一上来就怀疑 OVS 代码本身。DPDK 这层把内核协议栈甩开之后剩下的问题几乎都是资源分配和硬件特性的问题带着这个思路排查会快很多。6. 进阶验证与落地取舍把 OVS DPDK 用到生产前的最后一步6.1 用 Docker 容器测转发路径从 ping 到 pktgen 的验证套路配置完成后验证数据面是否真正生效。最土但有效的办法在同一个 OVS 网桥上创建两个 docker 容器绑定到两个 vhost-user 端口从容器 A ping 容器 B。如果通说明 OVS 流表、vhost-user socket、网桥路径都正常。如果只想测物理网卡转发用 veth 或内部端口配合 tcpdump 抓包确认包确实从物理口进来、从 vhost-user 口出去这一步能快速排除“流表没下发”的假性能问题。pktgen 这类工具可以生成高速小包用于压测转发上限。运行前先调大 vhost-user 的队列数到 2 或 4同时给 OVS 配好pmd-cpu-mask然后观察ovs-appctl dpif-netdev/pmd-perf-show里每个 PMD 线程的吞吐和丢包统计。如果某个 PMD 线程的利用率接近 100% 而其他线程空闲说明 RSS 哈希分配不均匀需要用ovs-appctl dpif-netdev/pmd-rxq-rebalance重平衡或者调整几个分流参数让队列分布更合理。6.2 两个值得投入的方向硬件卸载与流量控制以及我的取舍DPDK 加速 OVS 的方案成熟度已经很高但落地前要看两件事。第一是物理网卡是否支持 DPDK 的硬件卸载比如 rte_flow 硬卸载可以把流表下推到网卡CPU 占用进一步下降适合追求极致性能的网元场景但依赖具体网卡型号和 DPDK 版本踩坑成本不低。第二是 OVS 2.13 之后和 DPDK 的版本匹配越来越严格小版本不对会在运行时出现莫名丢包建议锁定一套经过压测的版本组合别追新。我的建议是如果只是虚拟化网络的南北向流量先用标准的 OVS 多队列 RSS 调优通常能把 CPU 占用降到可接受范围不一定要上 DPDK。DPDK 的收益在线速转发和低延迟场景才真正体现一旦上了 DPDK意味着内存预分配、CPU 隔离、驱动绑定、虚拟机的 vhost-user 配置都要跟着动运维复杂度明显上升。我在一个模拟项目中就吃过亏为了追求和官方文档一致的性能指标投入了整整两周调 NUMA 和 PMD 掩码最终发现瓶颈其实在虚拟机内部的单队列 virtio-net 上换回多队列 virtio 立即解决问题DPDK 白配了。最后说一个习惯每次改动 DPDK 或 OVS 配置前我都会先跑一遍ovs-appctl dpif-netdev/pmd-stats-show记下基线改完再对比。网络转发性能玄学很多没有基线数据翻车了都不知道是配置改坏的还是环境波动。希望帮到你。本文还有配套的精品资源点击获取
返回列表