网络面试题 Part2:高性能网络优化
Published:
0x03 高性能网络优化
10. 什么是 busy-polling 和 DPDK?
传统网络模型及限制
传统网络模型(Linux< 2.5.44)中,一个数据包的发送了流程可以参考下图:
graph TD
subgraph UserSpace["用户态 (User Space)"]
A["应用程序 (Application)"]
B["Socket API"]
end;
subgraph KernelSpace["内核态 (Kernel Space)"]
C["TCP/UDP 协议栈"]
D["IP Layer"]
E["MAC/以太网 Layer"]
F["NIC Driver"]
end
subgraph HW["NIC Hardware"]
G["硬件队列 (TX/RX Queues)"]
H["物理介质 (Wire)"]
end
A --> B --> C --> D --> E --> F --> G --> H
H --> G --> F --> E --> D --> C --> B --> A
可以看到,一个数据包从用户空间到物理网卡至少要经过两次拷贝:
- 第一次拷贝:从用户空间缓冲区 → 内核空间缓冲区
- 通过 系统调用触发,如
send() - 涉及用户态到内核态的上下文切换
- 通过 系统调用触发,如
- 第二次拷贝:从内核空间缓冲区 → 网卡DMA缓冲区
- 内核协议栈处理完成后
- 通过 DMA 引擎传输,但仍需 CPU 中断
这中间,当来自 User Space 的新数据包到达时,产生一次中断,网卡完成数据发送,产生第二次时中断 此外,每次 Kernel Space 将数据拷贝到 硬件缓冲 都会引发硬件中断,当大量数据包拥堵时,反复中断引发的上下文拷贝,产生大量的冗余消耗,也就是中断风暴。
内核旁路技术:以 Intel DPDK 为例
graph LR
%% 从左到右的数据流
subgraph Hardware["网卡硬件"]
G["物理介质"]
F["硬件队列"]
end
subgraph Kernel["内核态"]
E["UIO/VFIO"]
end
subgraph UserSpace["用户态"]
C["PMD驱动"]
D["内存管理"]
B["DPDK库"]
A["应用程序"]
end
%% 数据流向
G --> F --> C --> D --> B --> A
E -->|映射| C
E -->|DMA| F
C --> B --> A
DPDK(Data Plane Development Kit)是一个开源工具包,提供了一组数据平面库和网络接口控制器轮询模式驱动程序。
可以将 TCP 数据包的处理,从内核空间中转移到用户空间中的进程里完成。
DPDK 特点就是:
- 用户态绕过内核协议栈直接访问 NIC Buffer
- 零拷贝 + 多核并发 + 高吞吐
- 适用于高频交易、SDN
优化内核中断机制
epoll 机制:在内核空间中,构建epoll对象,每次中断,将只需要保存指定的上下文描述
busy-polling 机制:开启忙轮询设置后,CPU 将会在空闲状态下,主动每隔一段时间主动对缓存进行查看。
// 启用socket级别的忙轮询
int enable_busy_poll(int sockfd) {
int val;
// SO_BUSY_POLL: 当没有数据时,内核轮询驱动的时间(微秒)
val = 50; // 50微秒
if (setsockopt(sockfd, SOL_SOCKET, SO_BUSY_POLL, &val, sizeof(val)) < 0) {
perror("setsockopt SO_BUSY_POLL");
}
return 0;
}
上面socket级别的忙轮询依然是从用户态发起的,内核级别的忙轮询可以参考下面的配置。
# 启用内核级别的忙轮询
echo 0 > /proc/sys/net/core/netdev_budget # 减少NAPI处理量
echo 1 > /proc/sys/net/core/busy_poll # 启用繁忙轮询
echo 50 > /proc/sys/net/core/busy_read # 繁忙读取超时(微秒)
11. 数据包的延迟会出现在哪些环节?
sequenceDiagram
autonumber
participant UA as 用户应用(User Space)
participant K as 系统内核(Kernel)
participant NIC as 驱动 / 网卡(Driver & NIC)
participant L as 物理链路(光纤/铜缆)
participant SR as 交换/路由设备(中间节点,若多跳可理解为多台)
participant RNIC as 远端网卡
participant RK as 远端内核
participant RA as 远端应用
UA->>K: send()/write() 等系统调用
Note over UA,K: [A] 系统层级延迟
K->>K: TCP/IP 协议栈处理
Note right of K: [B] 内核层级延迟
K->>NIC: 报文下发到驱动/NIC
Note over K,NIC: [C] Drive/NIC 延迟
NIC->>L: Byte上线
L->>SR: 入交换/路由设备
Note over NIC,SR: [D1] 物理传播延迟
SR->>SR: 设备处理/转发
Note right of SR: [D2] 交换/路由转发
SR-->>SR: 排队等待(突发/拥塞时)
Note right of SR: [D3] 排队延迟(Bufferbloat)
SR->>L: 出口端口发出
Note right of L: [D1] 继续传播(若多跳则重复 D1/D2/D3)
L->>RNIC: 到达远端网卡
RNIC->>RK: DMA 上送/中断
Note over RNIC,RK: [C] 对端 Drive/NIC 延迟
RK->>RK: 远端 TCP/IP 协议栈处理
Note right of RK: [B] 对端内核协议栈延迟
RK->>RA: 递交到远端应用
Note over RK,RA: [A] 对端系统层级延迟
- [A] 系统层级延迟:从 User Space 到 Kernal Space 调用延迟。
- [B] 内核层级延迟:内核协议栈,如 TCP/IP,协议的处理延迟。
- [C] Drive/NIC 层级延迟:硬件驱动层 到 网卡(NIC)造成的延迟。
- [D] 物理层级传输延迟:
- [D1] 光纤延迟:约 5 μs/km。
- [D2] 交换/路由器处理延迟。
- [D3] 数据包排队延迟Bufferbloat。
12. 可以采用哪些方案优化延迟?
上面提到的延迟来源,每个阶段都有优化方案。
[A] 系统层级延迟优化
- 使用内核旁路 Kernel Bypass 方案绕过 Kernal Space,使用 DPDK 编写发包模块。
- 优化线程的 CPU Affinity ,将关键的进程、线程,物理中断(IRQ),绑定到特定的 CPU Core 上,避免操作系统调度器将系统任务在核心间迁移。从而保证缓存热度、减少上下文切换。
sched_setaffinity系统调用来设置线程的CPU亲和性绑定 CPU core。# 在 GRUB 配置中将指定 CPU 隔离:isolcpus=1,2,3 # 将 my_program 启动,并将其绑定到核心 2 和 3 上 taskset -c 2,3 ./my_program # 对于一个已经运行的进程 PID=12345,将其绑定到核心 1 taskset -pc 1 12345 # 将中断号90绑定到CPU核心2 echo 2 > /proc/irq/90/smp_affinity
[B] 内核/协议层级延迟优化
- 关闭 TCP 协议中 Nagle 配置
- 使用
SO_RCVBUF/SO_SNDBUF调整 socket 缓冲区// 小缓冲区策略 - 减少数据在内核中的停留时间 set_socket_buffers(sockfd, 64 * 1024, 32 * 1024); // 64KB接收,32KB发送 // 大缓冲区策略 - 提高批量传输效率 set_socket_buffers(sockfd, 4 * 1024 * 1024, 2 * 1024 * 1024); // 4MB接收,2MB发送
[C] Drive/NIC 层级延迟优化
- DPDK 内核旁路模式,User Space 直接从 NIC Buffer 的映射中读取数据。
- 轮询策略优化:
epoll:使用 epoll 取代 select 和poll 模式,提升内核态处理中断的效率。busypoll:使用busy_poll套接字接口读取数据,牺牲 CPU 性能,换取最快读取时间。
- 缓存优化:
- 使用巨页(Huge Pages)提高内存访问效率,降低快表(TLB)缺失的概率。
