亚马逊AWS官方博客
DPDK技术深度解析:AWS上的Kernel Bypass网络优化基础
概述
在高频交易、实时游戏、视频流处理等对延迟极其敏感的应用场景中,传统的Linux网络栈往往成为性能瓶颈。本文将深入介绍DPDK (Data Plane Development Kit) 技术原理,以及如何在AWS环境中实现kernel bypass网络优化。
什么是Kernel Bypass?
传统网络栈的性能问题
先说说传统Linux网络栈为什么慢。当一个数据包到达网卡时,会触发硬件中断,CPU停下手头的活去处理这个中断。内核的网络协议栈接管后,要做一堆事情:解析协议头、路由查找、防火墙规则检查等等。处理完之后,数据还得从内核空间拷贝到用户空间,应用程序才能拿到。
这整个流程里,性能损耗主要来自几个地方:
首先是内存拷贝。数据包从网卡DMA到内核缓冲区,然后又要拷贝到应用程序的缓冲区。每次拷贝都要消耗CPU周期和内存带宽。
其次是上下文切换。内核态和用户态之间的切换本身就有开销,要保存和恢复寄存器状态、刷新TLB。如果数据包很小、频率很高,这个开销就会变得很明显。
还有中断处理的延迟。硬件中断虽然优先级高,但调度本身需要时间。在高负载下,中断风暴会让CPU疲于应付,真正的业务逻辑反而得不到执行。
对于普通应用来说,这些开销可以接受。但如果你在做高频交易,或者处理实时视频流,每多一微秒的延迟都可能是问题。
Kernel Bypass怎么解决这些问题
Kernel bypass的思路很直接:既然内核是瓶颈,那就绕过它。让应用程序直接操作网卡硬件,把数据包的接收和发送都放到用户空间来做。
这样做有几个好处。零拷贝是最直观的——数据包从网卡DMA到用户空间的内存,中间不经过内核,省掉了一次拷贝。轮询模式替代中断,应用程序主动去网卡上取数据包,而不是被动等中断通知。虽然轮询会占用CPU,但延迟更可控。
另外还可以做批量处理。一次从网卡取多个数据包,分摊掉一些固定开销。再配合CPU亲和性绑定,把网络处理线程固定在某个核心上,避免线程迁移导致的缓存失效。
当然,这不是没有代价的。轮询模式意味着要独占CPU核心,资源利用率会下降。而且绕过内核后,很多内核提供的功能(比如防火墙、流量控制)都得自己实现。所以kernel bypass更适合那种”我知道我在做什么”的场景,而不是通用解决方案。
DPDK技术深度解析
DPDK是什么
DPDK (Data Plane Development Kit) 最初由Intel主导开发并开源,目前由Linux基金会下的DPDK项目维护的用户态网络处理框架。简单说,它提供了在用户空间直接操作网卡的能力,把kernel bypass的理念落地成了可以用的代码库。
架构上看,DPDK分几层:最底层是PMD (Poll Mode Driver),这是用户态的网卡驱动,负责直接和硬件打交道。往上是DPDK的核心库,提供内存管理、队列操作、数据包处理等基础功能。最上层是你的应用程序,通过DPDK的API来收发数据包。
![]() |
整个数据路径都在用户空间,不经过内核。这就是DPDK能做到低延迟的根本原因。
几个关键技术点
轮询模式驱动 (PMD)
PMD是DPDK的核心。传统驱动靠中断通知,PMD则是不停地轮询网卡的收发队列(RX/TX rings),看有没有新数据包。这听起来很浪费CPU,但在高吞吐场景下反而更高效——中断本身的开销、上下文切换的开销都省了。而且延迟更稳定,不会因为中断调度的不确定性而产生抖动。
PMD直接操作网卡的收发队列,数据包通过DMA写到用户空间的内存里,应用程序直接读取,真正做到了零拷贝。
内存管理
DPDK对内存管理做了很多优化。首先是用HugePages(大页内存),Linux默认的页大小是4KB,HugePages可以用2MB甚至1GB的页。这样能大幅减少TLB (Translation Lookaside Buffer) miss,因为同样大小的内存需要的页表项更少。
其次是内存池机制。DPDK启动时会预分配一大块内存,后续的数据包缓冲区都从这个池子里取,避免运行时的动态分配。动态分配不仅慢,还可能触发系统调用,破坏了用户态处理的初衷。
还有NUMA感知。在多路CPU的服务器上,每个CPU有自己的本地内存。DPDK会尽量让网卡、CPU、内存都在同一个NUMA节点上,避免跨节点访问带来的延迟。
CPU亲和性
DPDK通常会把网络处理线程绑定到特定的CPU核心上。这样做有两个好处:一是避免线程在不同核心间迁移,导致L1/L2缓存失效;二是可以配合CPU隔离(isolcpus),让这些核心专门用于网络处理,不被其他进程打扰。
在高性能场景下,DPDK还会用无锁数据结构(比如无锁环形队列)来避免多线程竞争。锁的开销在微秒级延迟的场景下是不可接受的。
AWS环境中的DPDK部署
1. 选择并启动合适的EC2实例
推荐实例类型
- C7i系列:最新Intel处理器,优化的网络性能
- C7a系列:AMD EPYC处理器,高性价比选择
- C8g系列:ARM Graviton处理器,能效优化
实例规格建议
# 固定带宽实例(推荐生产环境)
c7a.8xlarge # 32 vCPU, 64GB RAM, 12.5Gbps网络
c7a.16xlarge # 64 vCPU, 128GB RAM, 25Gbps网络
c7a.32xlarge # 128 vCPU, 256GB RAM, 50Gbps网络
本篇文章中,以 c7a.8xlarge 实例为例,演示DPDK在Amazon linux 2023 上的部署以及测试流程。
步骤1:创建EC2实例
首先创建一台新的EC2,网络设置里指定特定子网(固定AZ),同时创建一个新的安全组。
![]() |
步骤2:配置安全组规则
创建完成后,在新创建的安全组中添加一条新的入站规则,允许所有流量,允许源设置为安全组自身(方便后续测试)。
![]() |
步骤3:创建并附加ENI网络接口
控制台中启动EC2完成后,再在控制台新建一个网络接口(ENI),注意子网选择和已启动的EC2一致,同时选择相同的安全组。此时实例上应该有两个网络接口,一个用于正常网络连接,另一个用于DPDK。
![]() |
2. 系统环境准备
Amazon Linux 2023环境安装
# 切换好root权限
sudo -i
# 安装开发工具组和git
dnf groupinstall "development tools" -y
dnf install git -y
# 安装NUMA相关依赖
dnf install numactl numactl-devel -y
# 安装Python3和现代构建工具
dnf install python3-pip -y
pip3 install meson ninja pyelftools
# 更新环境变量
echo 'export PATH="/usr/local/bin:$PATH"' >> /etc/profile
source /etc/profile
内存优化及CPU隔离
# 编辑GRUB配置
vim /etc/default/grub
# 在 GRUB_CMDLINE_LINUX_DEFAULT 中追加以下参数(保持你现有的其他参数不变)
# 注意:c7a系列使用AMD处理器,使用idle=poll;Intel处理器使用intel_idle.max_cstate=0
# 例(AMD处理器 - c7a系列):
GRUB_CMDLINE_LINUX_DEFAULT="... default_hugepagesz=1G hugepagesz=1G hugepages=4 isolcpus=1-3 nohz_full=1-3 rcu_nocbs=1-3 idle=poll ..."
# 例(Intel处理器 - c7i系列):
# GRUB_CMDLINE_LINUX_DEFAULT="... default_hugepagesz=1G hugepagesz=1G hugepages=4 isolcpus=1-3 nohz_full=1-3 rcu_nocbs=1-3 intel_idle.max_cstate=0 ..."
# 更新GRUB配置
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
# 重启之后执行
sudo -i
mkdir -p /mnt/huge_1gb
echo "nodev /mnt/huge_1gb hugetlbfs pagesize=1GB 0 0" | tee -a /etc/fstab
mount -a
# 检查是否生效
mount | grep huge
cat /proc/meminfo | grep Huge
3. DPDK编译安装部署
使用现代构建系统
# 下载DPDK 23.11.3 LTS版本
wget https://fast.dpdk.org/rel/dpdk-23.11.3.tar.xz
tar xJf dpdk-23.11.3.tar.xz
cd dpdk-stable-23.11.3/
# 使用meson构建系统(推荐)
meson setup build # x86_64架构
# ARM架构使用:meson setup build -Dplatform=generic
# 编译和安装
cd build
ninja
ninja install
ldconfig
VFIO 驱动安装
# 推荐:使用VFIO驱动(现代化选择)
# 切换到home目录
cd ~
# 获取Amazon优化的VFIO驱动
git clone https://github.com/amzn/amzn-drivers.git
cd amzn-drivers/userspace/dpdk/enav2-vfio-patch
./get-vfio-with-wc.sh
# 加载VFIO模块
modprobe vfio
modprobe vfio-pci
echo 1 > /sys/module/vfio/parameters/enable_unsafe_noiommu_mode
网卡绑定与配置
# 切换到DPDK目录
cd ~/dpdk-stable-23.11.3
# 查看网卡状态,找到第二块网卡的接口名和PCI地址
usertools/dpdk-devbind.py --status
# 示例输出:
# Network devices using kernel driver
# ===================================
# 0000:27:00.0 'Elastic Network Adapter (ENA) ec20' if=enp39s0 drv=ena unused= *Active*
# 0000:28:00.0 'Elastic Network Adapter (ENA) ec20' if=enp40s0 drv=ena unused=
# 关闭要绑定的网卡(替换为你的接口名)
ifconfig enp40s0 down
# 绑定到VFIO驱动(替换为你的PCI地址)
usertools/dpdk-devbind.py --bind=vfio-pci 28:00.0
# 再次查看状态,确认绑定成功
usertools/dpdk-devbind.py --status
# 验证绑定结果(替换为你的PCI地址)
lspci -v -s 28:00.0
4. DPDK基础测试与验证
环境变量配置
# 设置开发环境变量
export PKG_CONFIG_PATH=/usr/local/lib64/pkgconfig:$PKG_CONFIG_PATH
export LD_LIBRARY_PATH=/usr/local/lib64:$LD_LIBRARY_PATH
HelloWorld示例测试
# 安装libatomic库
dnf install libatomic -y
# 切换到DPDK示例目录
cd ~/dpdk-stable-23.11.3/examples/helloworld
# 编译示例程序
make
# 运行HelloWorld示例
./build/helloworld
# 预期输出:
# EAL: Detected XX CPU cores
# hello from core 0
# hello from core 1
# ...
性能基准测试
# 切换到DPDK构建目录
cd ~/dpdk-stable-23.11.3/build
# 使用testpmd自带的icmp转发模式测试
./app/dpdk-testpmd -l 0-1 -n 4 --huge-dir=/mnt/huge_1gb --proc-type=primary -- --interactive --forward-mode=icmpecho
# 在testpmd交互界面中,输入start启动转发
testpmd> start
# 预期输出:
# icmpecho packet forwarding - ports=1 - cores=1 - streams=1
# ...
# 此时不要关闭,等待测试
# 测试完成后,输入stop和quit退出
testpmd> stop
testpmd> quit
为了验证DPDK的性能优势,我们需要对比普通网卡和DPDK管理网卡的延迟差异。
在同一可用区启动另一台EC2实例作为测试客户端,绑定上与待测试EC2相同的安全组(方便通信)。
# 在测试客户端安装必要工具
sudo dnf install iputils fping python3-pip -y
sudo pip3 install numpy
注意:在运行延迟测试前,需要先在DPDK服务器上启动testpmd的icmp转发模式(参考上一节),确保DPDK网卡能够响应ping请求。
将下面延迟测试脚本和延迟分析代码,粘贴进测试客户端实例,同时添加执行权限。
延迟测试脚本
#!/bin/bash
# 延迟测试脚本 latency_test.sh
# 使用方法:./latency_test.sh
# 配置IP地址(请根据实际情况修改)
NORMAL_IP="172.31.37.195" # DPDK服务器的普通网卡IP(第一块网卡)
DPDK_IP="172.31.39.205" # DPDK服务器的DPDK网卡IP(第二块网卡)
TEST_COUNT=100
# 生成时间戳用于文件命名
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# 清理旧的测试结果
rm -f normal_full.txt dpdk_full.txt normal_stats.txt dpdk_stats.txt
echo "开始延迟测试对比 - $TIMESTAMP"
echo "测试次数: $TEST_COUNT"
echo ""
# 测试普通网卡延迟(实时显示并保存)
echo "=========================================="
echo "测试普通网卡延迟 ($NORMAL_IP):"
echo "=========================================="
ping -c $TEST_COUNT $NORMAL_IP | tee normal_full.txt
grep "min/avg/max" normal_full.txt > normal_stats.txt
echo ""
echo "=========================================="
echo "测试DPDK网卡延迟 ($DPDK_IP):"
echo "=========================================="
ping -c $TEST_COUNT $DPDK_IP | tee dpdk_full.txt
grep "min/avg/max" dpdk_full.txt > dpdk_stats.txt
echo ""
echo "=========================================="
echo "生成统计分析报告..."
echo "=========================================="
# 生成详细统计报告
python3 analyze_latency.py
# 可选:保存历史记录
mkdir -p latency_history
cp normal_full.txt latency_history/normal_${TIMESTAMP}.txt
cp dpdk_full.txt latency_history/dpdk_${TIMESTAMP}.txt
echo ""
echo "历史记录已保存到 latency_history/ 目录"
延迟分析脚本
# analyze_latency.py
import re
import numpy as np
def parse_ping_latencies(filename):
"""解析ping输出中的每个RTT值"""
latencies = []
with open(filename, 'r') as f:
for line in f:
# 匹配 "time=0.123 ms" 格式
match = re.search(r'time=([\d.]+) ms', line)
if match:
latencies.append(float(match.group(1)))
return latencies
def calculate_stats(latencies):
"""计算详细统计信息,包括P90和P99"""
if not latencies:
return None
arr = np.array(latencies)
return {
'min': np.min(arr),
'avg': np.mean(arr),
'max': np.max(arr),
'p50': np.percentile(arr, 50),
'p90': np.percentile(arr, 90),
'p99': np.percentile(arr, 99),
'stddev': np.std(arr)
}
# 解析原始延迟数据
print("正在解析延迟数据...")
normal_latencies = parse_ping_latencies('normal_full.txt')
dpdk_latencies = parse_ping_latencies('dpdk_full.txt')
# 计算统计信息
normal_stats = calculate_stats(normal_latencies)
dpdk_stats = calculate_stats(dpdk_latencies)
# 输出结果
print("\n延迟测试对比结果:")
print("=" * 70)
print(f"{'指标':<10} {'普通网卡(ms)':<18} {'DPDK网卡(ms)':<18} {'改善率':<12}")
print("-" * 70)
if normal_stats and dpdk_stats:
metrics = ['min', 'avg', 'p50', 'p90', 'p99', 'max', 'stddev']
for metric in metrics:
normal_val = normal_stats[metric]
dpdk_val = dpdk_stats[metric]
improvement = ((normal_val - dpdk_val) / normal_val) * 100
print(f"{metric.upper():<10} {normal_val:<18.3f} {dpdk_val:<18.3f} {improvement:>9.1f}%")
print(f"\n样本数量: 普通网卡={len(normal_latencies)}, DPDK网卡={len(dpdk_latencies)}")
else:
print("错误:无法解析延迟数据")
预期测试结果
延迟测试对比结果:
==========================================================
指标 普通网卡(ms) DPDK网卡(ms) 改善率
----------------------------------------------------------
MIN 0.159 0.107 32.7%
AVG 0.186 0.140 24.4%
P50 0.186 0.140 24.7%
P90 0.196 0.149 24.0%
P99 0.207 0.161 22.1%
MAX 0.277 0.161 41.9%
STDDEV 0.012 0.008 38.3%
测试结论
从测试数据来看,DPDK的优化效果很明显。平均延迟从0.186ms降到0.140ms,降了24%左右。最大延迟从0.277ms降到0.161ms,标准差也从0.012降到0.008。 DPDK不仅能大幅降低延迟,还能显著提升延迟波动。
大实例NUMA优化策略
NUMA架构基础
NUMA(非统一内存访问)架构中,每个CPU有自己的本地内存。访问本地内存快(~100ns),访问远程内存慢(~200-300ns)。正确的NUMA配置可以避免2-3倍的内存访问延迟,在高负载场景下性能提升可达20-50%。
单NUMA环境(c7a.8xlarge、c7a.16xlarge等):本文前面的配置步骤已经足够,无需额外优化。
多NUMA环境(c7a.32xlarge及以上):如果你使用更大的实例,需要按以下步骤调整配置。
步骤1:查看NUMA拓扑和网卡位置
# 查看完整的NUMA拓扑
numactl --hardware
# 示例输出(c7a.32xlarge,2个NUMA节点,128 vCPU):
# available: 2 nodes (0-1)
# node 0 cpus: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
# node 0 size: 126165 MB
# node 0 free: 125549 MB
# node 1 cpus: 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
# node 1 size: 126285 MB
# node 1 free: 125542 MB
# node distances:
# node 0 1
# 0: 10 32
# 1: 32 10
lscpu | grep NUMA
# 示例输出
# NUMA node(s): 2
# NUMA node0 CPU(s): 0-63
# NUMA node1 CPU(s): 64-127
# 确认DPDK网卡在哪个NUMA节点(替换为你的PCI地址)
cat /sys/bus/pci/devices/0000:60:00.0/numa_node
# 输出:1(表示网卡在NUMA节点1)
步骤2:调整GRUB配置(需要重启)
根据步骤1的结果,调整GRUB配置。假设网卡在NUMA节点1(CPU 64-127),需要:
- 增加HugePages数量(系统会自动平均分配到各NUMA节点)
- 隔离网卡所在NUMA节点的部分CPU核心
# 编辑GRUB配置
vim /etc/default/grub
# 修改 GRUB_CMDLINE_LINUX_DEFAULT,调整以下参数:
# - hugepages=4 改为 hugepages=8(每个节点4GB,足够DPDK使用)
# - isolcpus=1-3 改为 isolcpus=65-71(隔离NUMA1的8个核心用于DPDK,保留核心64给系统)
# 示例(AMD处理器 - c7a系列):
GRUB_CMDLINE_LINUX_DEFAULT="... default_hugepagesz=1G hugepagesz=1G hugepages=8 isolcpus=65-71 nohz_full=65-71 rcu_nocbs=65-71 idle=poll ..."
# 更新GRUB配置并重启
grub2-mkconfig -o /boot/grub2/grub.cfg
reboot
步骤3:重启后验证配置
注意:重启后需要重新执行以下操作(参考前面章节的具体命令):
- 加载VFIO模块
- 将网卡绑定到VFIO驱动
- 设置环境变量(PKG_CONFIG_PATH和LD_LIBRARY_PATH)
验证HugePages配置:
# 验证HugePages已自动分配到各NUMA节点
cat /sys/devices/system/node/node*/hugepages/hugepages-1048576kB/nr_hugepages
# 输出示例(8个大页平均分配):
# node0: 4
# node1: 4
步骤4:运行DPDK应用时绑定NUMA节点
运行DPDK应用时,必须确保CPU、内存、网卡都在同一NUMA节点:
# 切换到DPDK目录
cd ~/dpdk-stable-23.11.3/build
# 使用numactl绑定到网卡所在的NUMA节点(假设是节点1)
numactl --cpunodebind=1 --membind=1 \
./app/dpdk-testpmd -l 65-67 -n 4 \
--socket-mem=0,2048 \
--huge-dir=/mnt/huge_1gb \
--proc-type=primary \
-- --interactive --forward-mode=icmpecho
# 关键参数说明:
# numactl --cpunodebind=1 : 绑定到NUMA节点1的CPU
# numactl --membind=1 : 内存只从NUMA节点1分配
# --socket-mem=0,2048 : NUMA0分配0,NUMA1分配2GB(避免跨节点访问)
# -l 65-67 : 使用CPU核心65-67(NUMA1上的隔离核心,测试环境3个核心足够)
# 在testpmd交互界面启动
testpmd> start
步骤5:验证NUMA优化效果
在DPDK应用运行时,打开另一个终端验证:
# 查看进程的NUMA内存分配(确认内存都在节点1,节点0为0或很少)
numastat -p $(pidof dpdk-testpmd)
# 预期输出示例(c7a.32xlarge,2个NUMA节点,网卡在节点1):
# Per-node process memory usage (in MBs) for PID xxx (dpdk-testpmd)
# Node 0 Node 1 Total
# ------ ------ -----
# Huge 0 2048 2048
# Heap 0 0.64 0.64
# Stack 0 0.13 0.13
# Private 18.77 134.93 153.70
# ------- ------ ------ -----
# Total 18.77 2183.70 2202.48
NUMA优化核心原则:网卡在哪个NUMA节点,就把所有资源(CPU、内存、进程)都绑定到那个节点,避免任何跨NUMA节点的访问。这是多NUMA环境下获得最佳性能的关键。
总结
DPDK技术为AWS云环境中的高性能网络应用提供了强大的基础设施。通过深入理解kernel bypass原理、合理配置系统环境、选择适当的硬件和驱动,可以在云环境中实现接近裸机的网络性能。
本文介绍的DPDK基础知识和配置方法为后续的F-Stack等高级网络栈应用奠定了坚实基础。在下一篇文章中,我们将详细介绍如何基于DPDK构建和部署F-Stack高性能网络应用。
参考资源
*前述特定亚马逊云科技生成式人工智能相关的服务目前在亚马逊云科技海外区域可用。亚马逊云科技中国区域相关云服务由西云数据和光环新网运营,具体信息以中国区域官网为准。
本篇作者
AWS 架构师中心: 云端创新的引领者探索 AWS 架构师中心,获取经实战验证的最佳实践与架构指南,助您高效构建安全、可靠的云上应用 |
![]() |





