DPDK、TC 和 XDP:Linux 高性能网络到底在优化什么
在看 Linux 高性能网络、软路由、负载均衡或者网络性能优化时,经常会看到几个词:
text
DPDK
TC
XDP
eBPF
Kernel Bypass
Zero Copy
刚开始接触时,很容易把 DPDK、TC 和 XDP 当成三个互相竞争的技术。
但实际上它们解决的问题并不完全一样。
如果先给一个比较粗的理解:
text
TC
主要在 Linux 网络协议栈内部做流量控制和数据包处理
XDP
尽可能早地在网卡收到数据包时进行处理
DPDK
直接绕开 Linux 内核网络协议栈,由用户态程序接管数据包
它们之间最关键的区别,其实是:
数据包到底在网络路径的哪个位置被处理。
1. 先看普通 Linux 网络路径
先不考虑 DPDK、TC 和 XDP。
假设网卡收到一个数据包,普通 Linux 网络路径可以粗略理解成:
text
NIC
|
DMA
|
Driver
|
Linux Kernel
|
Network Stack
|
TCP / UDP
|
Socket
|
Application
例如一个普通的 TCP Server:
cpp
int fd = socket(...);
bind(fd, ...);
listen(fd, ...);
accept(fd, ...);
recv(fd, ...);
应用程序最终通过 Socket 接收数据。
在这个过程中,数据包需要经过:
text
网卡
↓
网卡驱动
↓
Linux 网络协议栈
↓
IP
↓
TCP / UDP
↓
Socket
↓
用户程序
这种方式最大的优点就是简单。
Linux 已经帮我们完成了:
text
路由
TCP
UDP
拥塞控制
重传
防火墙
Socket
网卡驱动
应用程序只需要使用 Socket API。
但是对于一些极端高性能场景来说,这条路径里面存在不少额外开销。
例如:
text
中断处理
协议栈处理
skb 分配和释放
上下文切换
内核态 / 用户态切换
Cache Miss
数据结构遍历
单个数据包看起来没什么。
但假设服务器每秒需要处理:
text
10M packets
20M packets
甚至更高
这些开销就会被不断放大。
所以高性能网络优化的一个核心思想就是:
能不能让数据包少走一点路?
TC、XDP 和 DPDK,本质上都是围绕这个问题展开的。
2. TC 是什么
TC 全称:
text
Traffic Control
也就是 Linux 的流量控制系统。
很多人第一次看到 TC,可能是在这样的命令里:
bash
tc qdisc
或者:
bash
tc filter
传统上,TC 主要用于:
text
限速
流量整形
队列管理
QoS
丢包模拟
延迟模拟
例如可以人为给网卡增加 100ms 延迟:
bash
tc qdisc add dev eth0 root netem delay 100ms
也可以限制带宽。
但后来随着 eBPF 的发展,TC 不只是做简单的流量控制了。
我们还可以把 eBPF 程序挂到 TC 上。
例如:
text
NIC
|
Driver
|
Linux Network Stack
|
TC ingress
|
...
|
TC egress
|
Driver
|
NIC
TC 主要有两个比较重要的位置:
text
ingress
egress
也就是:
text
数据包进入主机
数据包离开主机
因此可以在这些位置做:
text
包过滤
包统计
修改 Header
流量分类
重定向
限速
网络监控
例如做一个简单的网络监控程序:
text
eth0
|
TC ingress
|
eBPF Program
|
统计 src IP
统计 dst IP
统计 bytes
统计 packets
这样就可以直接在内核网络路径中统计流量。
TC 的一个特点是:
它仍然工作在 Linux Kernel Network Stack 体系里面。
也就是说,它并没有绕开 Linux 内核。
3. TC eBPF 的基本位置
假设一个包进入 Linux:
text
NIC
|
Driver
|
XDP
|
skb
|
TC ingress
|
Routing / TCP / UDP
|
Socket
|
Application
这里可以看到:
TC 的执行位置已经比较靠前。
但它还是晚于 XDP。
一个比较关键的原因是:
TC 通常处理的是已经转换成 sk_buff 的数据包。
Linux 网络协议栈里面有一个非常核心的数据结构:
cpp
struct sk_buff
也经常简称:
text
skb
Linux 会使用 skb 描述一个网络数据包。
里面包含:
text
数据地址
数据长度
协议类型
网卡信息
路由信息
Header 信息
各种 Metadata
它非常强大。
但强大的代价就是:
它比较重。
如果我们只是想判断:
text
这个包要不要丢
却先构造一个完整 skb,然后再处理,就可能有一些额外开销。
所以 XDP 的设计思路就是:
能不能在 skb 都还没创建的时候,就把事情处理掉?
4. XDP 是什么
XDP 全称:
text
eXpress Data Path
它是 Linux 内核里面一个非常靠前的数据包处理机制。
可以把普通接收路径理解成:
text
NIC
|
Driver
|
XDP
|
skb allocation
|
TC ingress
|
Network Stack
|
Socket
|
Application
XDP 最大的特点就是:
执行位置非常早。
网卡收到数据包以后,在进入完整 Linux 网络协议栈之前,就可以执行 XDP 程序。
所以如果有一个恶意包:
text
NIC
|
XDP
|
DROP
它甚至不需要进入后面的 Linux 网络协议栈。
也不需要:
text
构造 skb
路由查找
iptables
TCP / UDP
Socket
所以 XDP 特别适合处理:
text
DDoS 防护
包过滤
高性能转发
负载均衡
流量统计
网络安全
5. 一个 XDP 程序能干什么
XDP 程序执行以后,通常会返回几个典型 Action。
例如:
text
XDP_PASS
XDP_DROP
XDP_TX
XDP_REDIRECT
XDP_PASS
表示:
text
这个包没问题
继续交给 Linux 网络协议栈
路径变成:
text
NIC
|
XDP
|
PASS
|
Linux Network Stack
|
Application
XDP_DROP
直接丢包:
text
NIC
|
XDP
|
DROP
后面的协议栈完全不用处理。
这也是 XDP 做 DDoS 防护效率比较高的原因。
XDP_TX
表示:
text
从当前网卡直接把包发回去
例如某些简单的响应或者转发场景。
XDP_REDIRECT
表示:
text
把数据包重定向到其他地方
例如:
text
另一张网卡
另一个 CPU
AF_XDP Socket
这在负载均衡、软路由等场景里面很常见。
6. 为什么 XDP 会比 TC 更快
这里不要简单理解成:
text
XDP 一定比 TC 好
更加准确的说法应该是:
XDP 的执行位置更靠前,因此在一些场景下开销更低。
路径可以对比一下。
TC:
text
NIC
|
Driver
|
skb allocation
|
TC
|
Network Stack
XDP:
text
NIC
|
Driver
|
XDP
如果 XDP 在这里直接:
text
DROP
那么后面的:
text
skb allocation
Network Stack
Socket
全部不用执行。
所以对于:
text
大量需要丢弃的数据包
XDP 优势就会非常明显。
例如 DDoS 攻击。
假设每秒进来几百万个垃圾包。
如果全部进入完整 Linux 协议栈:
text
CPU
Memory
skb
协议栈
都会产生压力。
而 XDP 可以直接:
text
收到包
↓
判断
↓
DROP
所以成本低很多。
7. 那为什么还需要 TC
既然 XDP 更早、更快,那为什么不全部使用 XDP?
因为越早处理,能拿到的信息通常也越少。
XDP 执行时,数据包还没有走完整个 Linux 网络协议栈。
所以它更适合处理:
text
Ethernet
IP
TCP / UDP Header
简单状态
高速过滤
高速转发
如果需要:
text
更复杂的网络协议栈信息
skb metadata
Linux Routing 信息
更成熟的流量控制
egress 处理
TC 往往更合适。
尤其是一个非常明显的区别:
XDP 主要关注:
text
Ingress
也就是包刚进入机器的时候。
而 TC 可以处理:
text
Ingress
+
Egress
所以像:
text
出口流量限速
QoS
Traffic Shaping
这类场景,TC 更自然。
因此:
text
XDP 和 TC 不是谁取代谁
而是:
text
处理位置不同
适合的问题不同
8. DPDK 又是什么
DPDK 全称:
text
Data Plane Development Kit
它的思路和 TC、XDP 又不太一样。
前面讲的 TC 和 XDP:
text
都还运行在 Linux Kernel 体系里面
只是挂载位置不同。
DPDK 则更加激进。
它的核心思想是:
我直接不走 Linux 网络协议栈了。
普通 Linux:
text
NIC
|
Kernel Driver
|
Linux Network Stack
|
Socket
|
Application
DPDK:
text
NIC
|
DPDK Driver
|
Userspace Application
中间的:
text
Linux Network Stack
Socket
基本被绕过去了。
这种模式通常被称为:
text
Kernel Bypass
也就是绕过内核。
9. 为什么绕开内核会更快
Linux 网络协议栈本身其实已经非常成熟。
问题不是 Linux "慢"。
而是:
Linux 必须支持非常通用的场景。
例如:
text
TCP
UDP
IPv4
IPv6
iptables
netfilter
namespace
routing
socket
拥塞控制
各种设备
各种协议
为了做到通用,就必然需要很多:
text
抽象
数据结构
检查
调度
锁
内存管理
DPDK 面向的是:
text
我知道自己要干什么
我只追求高吞吐
所以它可以砍掉很多通用逻辑。
例如一个软路由程序只需要:
text
收包
↓
查路由表
↓
改 MAC
↓
发包
那就没必要让数据包经历完整的 Linux TCP/IP 网络协议栈。
10. DPDK 的 Poll Mode
DPDK 里面一个非常关键的设计叫:
text
Poll Mode Driver
普通网卡收包经常依赖:
text
Interrupt
大致就是:
text
网卡收到包
↓
触发中断
↓
CPU 处理中断
↓
处理数据包
如果数据包很少,这种方式非常合理。
因为 CPU 不需要一直盯着网卡。
但在高流量情况下:
text
packet
packet
packet
packet
packet
...
大量中断本身也会产生很高的开销。
DPDK 干脆换了一个思路:
text
CPU 不睡觉
while (true) {
看网卡有没有包
有就处理
}
这就是 Polling。
也就是轮询。
可以简单理解成:
cpp
while (true)
{
recv_packets();
process_packets();
send_packets();
}
这样就避免了频繁中断和调度。
代价也很明显:
CPU Core 基本会被占满。
即使暂时没有数据包,它还是在轮询。
所以 DPDK 的使用场景通常不是普通业务 Server。
而是:
text
软路由
防火墙
虚拟交换机
电信网络
NFV
高性能负载均衡
这些场景本身就愿意:
text
拿几个 CPU Core
专门处理网络
11. DPDK 为什么经常绑核
DPDK 项目里面经常会看到:
text
CPU Pinning
Core Binding
NUMA Awareness
HugePage
这些技术其实都是围绕同一个目标:
尽量减少不确定性。
例如一个处理线程:
text
Thread
|
CPU 2
如果 Linux 不断把它调度:
text
CPU 2
↓
CPU 15
↓
CPU 7
就容易产生:
text
Cache Miss
调度开销
NUMA Remote Access
所以 DPDK 通常会直接:
text
一个线程
固定一个 Core
类似:
text
RX Queue 0 -> Core 0
RX Queue 1 -> Core 1
RX Queue 2 -> Core 2
每个 Core 专门处理自己的队列。
这样整个数据路径就更加稳定。
12. DPDK 和 NUMA 的关系
DPDK 里面 NUMA 也非常重要。
假设:
text
NIC 在 NUMA Node 0
处理 NIC 的线程也在:
text
CPU Node 0
Buffer 也分配在:
text
Memory Node 0
那么路径就是:
text
NIC
|
Node 0 CPU
|
Node 0 Memory
基本都是 Local Access。
但如果:
text
NIC -> Node 0
CPU -> Node 1
Memory -> Node 1
大量数据就要跨 NUMA Node。
于是会经过 CPU Interconnect:
text
Node 0
|
Interconnect
|
Node 1
对于 DPDK 这种每秒处理几百万、几千万包的程序来说,这种 Remote Memory Access 会被不断放大。
所以 DPDK 经常会同时考虑:
text
NIC NUMA Affinity
CPU Pinning
Memory Placement
也就是:
网卡、CPU 和内存尽量放在同一个 NUMA Node。
13. HugePage 又是为什么
DPDK 里面还经常看到 HugePage。
普通 Linux Page 一般是:
text
4 KB
如果应用需要管理非常大的内存,例如:
text
10 GB
需要维护非常多 Page。
这会增加:
text
Page Table
TLB
地址转换
方面的压力。
所以 DPDK 经常使用:
text
2 MB HugePage
甚至
1 GB HugePage
减少 Page 数量。
例如:
text
1 GB Memory
如果使用 4 KB Page:
text
262144 个 Page
如果使用 2 MB HugePage:
text
512 个 Page
数量差别非常大。
因此:
text
HugePage
CPU Pinning
NUMA
Polling
Batch Processing
这些技术经常同时出现在 DPDK 里面。
14. DPDK、XDP 和 TC 放在一起看
现在重新把三者放在整个 Linux 网络路径上。
可以粗略画成:
text
普通 Linux
NIC
|
Driver
|
XDP
|
skb
|
TC ingress
|
Linux Network Stack
|
Socket
|
Application
|
TC egress
|
Driver
|
NIC
而 DPDK 则是另一条路:
text
NIC
|
DPDK PMD
|
Userspace Application
所以三者最大的差别可以理解成:
text
TC
还在 Linux 网络协议栈中
位置相对靠后
功能丰富
XDP
还在 Linux 内核中
但是位置非常靠前
适合高速过滤和转发
DPDK
直接绕过 Linux 网络协议栈
用户态直接收发包
追求极致性能
15. 三者性能为什么不同
这里可以从"数据包走了多少路"来理解。
TC
大概是:
text
NIC
↓
Driver
↓
skb
↓
TC
↓
Network Stack
路径相对完整。
XDP
大概是:
text
NIC
↓
Driver
↓
XDP
如果直接 DROP 或 REDIRECT,就可以提前结束。
DPDK
大概是:
text
NIC
↓
Userspace
直接绕过内核协议栈。
因此从理论上的极致性能来看,一般会认为:
text
DPDK
>
XDP
>
传统 Kernel Network Path
但这里不能简单理解成:
text
性能越高
技术就越好
因为它们的成本完全不一样。
16. DPDK 的代价
DPDK 性能非常高,但复杂度也很高。
使用 DPDK 以后,Linux 原来的很多能力不能直接使用。
例如:
text
Socket
iptables
内核 TCP/IP Stack
很多 Linux 网络工具
你需要自己处理更多东西。
如果程序只是:
text
HTTP Server
RPC Server
普通 TCP Server
为了追求一点性能就直接上 DPDK,通常没有太大必要。
因为开发成本和维护成本会明显增加。
所以 DPDK 更适合:
text
网络本身就是核心业务
的场景。
17. XDP 的一个优势:还能继续使用 Linux
XDP 相比 DPDK,一个很大的优势就是:
它没有完全绕开 Linux。
例如:
text
NIC
|
XDP
|
判断是否需要快速处理
如果是普通流量:
text
XDP_PASS
继续交给:
text
Linux TCP/IP Stack
如果是特殊流量:
text
XDP_REDIRECT
走高速路径。
这样可以同时保留:
text
Linux 网络协议栈的通用性
+
XDP 的高速处理能力
因此很多现代网络系统会采用:
text
Fast Path
+
Slow Path
这种设计。
Fast Path:
text
XDP / eBPF
Slow Path:
text
Linux Network Stack
简单的、高频的数据包走 Fast Path。
复杂的数据包继续交给 Linux 处理。
18. AF_XDP
XDP 和 DPDK 之间还有一个比较有意思的技术:
text
AF_XDP
AF_XDP 可以让 XDP 把数据包直接送到用户态。
路径大概是:
text
NIC
|
XDP
|
AF_XDP
|
Userspace
相比普通 Socket:
text
NIC
|
Kernel Network Stack
|
Socket
|
Userspace
路径会短很多。
所以 AF_XDP 可以理解成:
Linux 提供的一种高性能用户态收发包机制。
它在某些场景下可以作为:
text
普通 Socket
和
DPDK
之间的一个选择。
既希望获得比较高的 Packet Processing 性能,又不想完全进入 DPDK 那套独立生态,就可以考虑 AF_XDP。
19. TC、XDP 和 DPDK 应该怎么选
如果只是普通业务服务:
text
HTTP
RPC
TCP Server
一般直接使用:
text
Linux Socket
就够了。
如果需要:
text
流量控制
QoS
出口限速
网络监控
可以考虑:
text
TC / TC eBPF
如果需要:
text
DDoS 防护
高性能包过滤
负载均衡
高速转发
可以考虑:
text
XDP
如果是:
text
软路由
电信数据面
高性能交换
NFV
极致 Packet Processing
可以考虑:
text
DPDK
并不是所有项目都应该:
text
DPDK > XDP > TC
更加准确的理解应该是:
text
普通 Socket
|
功能最完整
使用最简单
TC
|
深入 Linux 网络栈处理
XDP
|
更靠近 Driver
更快处理 Packet
DPDK
|
直接接管数据平面
性能最高
复杂度也最高
20. 总结
把 DPDK、TC 和 XDP 放到一条网络路径里面,其实就比较容易理解了。
普通 Linux:
text
NIC
|
Driver
|
XDP
|
skb
|
TC
|
TCP/IP Stack
|
Socket
|
Application
DPDK:
text
NIC
|
Userspace Application
因此:
text
TC
主要是在 Linux 网络协议栈中对流量进行控制和处理。
text
XDP
是在数据包刚进入 Driver 附近时进行非常早期的处理,尽量避免不必要的协议栈开销。
text
DPDK
则进一步绕开 Linux 内核网络协议栈,让用户态程序直接处理数据包。
三者背后的核心思想其实都一样:
让数据包尽可能少走没有必要的路径。
只不过它们选择的位置不同。
TC 选择:
text
继续使用 Linux 网络协议栈
在协议栈里面做处理
XDP 选择:
text
在进入完整协议栈之前
先做一次快速决策
DPDK 选择:
text
干脆绕过协议栈
用户态直接处理 Packet
理解了这一点,再去看:
text
eBPF
AF_XDP
Kernel Bypass
Zero Copy
RSS
RPS
CPU Pinning
NUMA
HugePage
这些高性能网络相关技术时,就不会再觉得它们是一些互相独立的概念。
它们本质上都在解决一个问题:
怎样让 Packet Processing 更快、更稳定,同时减少 CPU、Memory 和 Kernel Path 上不必要的开销。