现在车内网络都开始内卷到1Gbps
了, 有同学给我反馈说以太网的吞吐量上不来, 跟理论带宽差距很大, 之前虽然优化了一波TCP
相关的参数, 但估计不能解决全部问题. 遂决定重新学习下网络优化, 从底层链路对开发平台上的网络进行改善. 趁着这个机会, 索性写一个系列文章-Linux网络优化, 用来总结下Linux网络优化的一些方法与技术, 目前计划从如下几篇文章展开(希望不要放飞了):
- Linux网络优化之数据链路层优化: 数据链路层
L2
的优化, 如何从收发两个方面优化网卡吞吐量,即本篇。 - Linux网络优化之TCP优化: 以TCP协议为例, 说明
L3
协议栈优化 - Linux网络优化之高速网络优化: 基于
DPDK/XDP
解决高速网络传输延迟问题 - Linux网络优化之AVB: 介绍以太网中用于音视频传输的低时延
TSN/AVB
协议
这篇文章主要讲第一个话题: Linux是如何在数据链路层L2
对网络数据的接收与发送进行优化的.
我们都知道, Linux的网络协议栈L4
到L2
(传输层)以下都是在内核实现的, 其主要存在的问题是日益增加的网络带宽与CPU处理能力(包括内核协议栈)之间矛盾, 随着后续100G甚至400G的网卡出现, 这个矛盾只会更加突出. 针对这种速度上的不平衡, 涌现出了各种解决方案, 有些是基于Linux内核协议栈, 在内核的基础上进行优化, 比如将部分耗时的操作丢给网卡处理(offload
); 有些则是基于L4协议做优化, 比如优化TCP参数, 使用更好的拥塞控制算法, 使用zero-copy
技术减少内存拷贝; 有些则完全跳过了内核协议, 直接把网卡的数据送往用户进程, 进而避免了内核处理慢的问题, 比如XDP/DPDK
; 还有一些是从L2
到应用层针对音视频数据传输做了大量优化, 确保低延迟以及足够的预留带宽, 这就是TSN/AVB
协议.
这篇文章, 就来看下那些Linux网络驱动以及协议栈中各种网络优化技术, 包括offload
与scaling
两个部分. 先来看下发送端的offload
优化方案.
链路层优化-offload技术
内核协议栈在传输数据时(内核数据的传输流程可以参考之前的文章Linux网络协议之数据发送流程, 除了从内核空间拷贝数据耗时外, 还有两件事情需要消耗CPU资源:
- 数据包传送到网络层L2时, 需要按照网卡的MTU对数据包进行分片
- 传送数据时需要对数据包进行校验和计算(checksum)
一般来说网卡的MTU都默认设置为1500
字节, 一旦TCP/UDP等L3的数据包超过了设定的MTU, 网络层L2就需要将其分割成MTU大小的数据段. 比如为了传输3200bytes
的数据, 通常需要分割成3个包进行发送. 为了避免分片, 可以通过设置一个大的MTU, 比如在需要高速网络传输时将MTU设置为9000(JUBO frame), 但这个在广域网中很难行的通, 因为不同的通讯节点很难保持统一的MTU设置. 这时就需要用到segmentation offload
(分片卸荷)技术. offloads
本质上就是将原本需要CPU处理的部分丢给网卡来处理, 其最高可支持64KB
大小的数据包, 这样可以减少网络包的数量, 降低网络中断数量, 增加网络吞吐量.
常见的offload
有如下几种:
- TSO(
TCP Segmentation Offload
): 开启TSO功能的网卡可以将一个大的单帧数据分割成固定大小(MTU)多个帧. TSO一般需要与网卡的checksum offload
以及scatter-gather
功能一起配置使用. - UFO(
UDP Fragmentation Offload
): 网卡可以将一个大的UDP报文分割成固定MTU大小的数据包; UFO目前已经废弃不用, 大部分内核不再支持. - GSO(
Generic Segmentation Offload
): GSO可以看作是TSO的补充, 当网卡无法支持TSO/UFO时, GSO可以确保数据包进入驱动处理时按照MTU的大小进行分片处理, 从而避免数据包无法发送的情况. GSO一般要与GRO(Generic Receive Offload
)一起使用. - LRO(
Large Receive Offload
): 在数据包进入协议栈之前进行重新组合成一个更大的报文, 以减少数据包的数量, 降低CPU负载. LRO通常会忽略数据包头中的差异以及其他存在的错误. 通常来说, LRO无法与内核中的IP转发(IP Forwarding
)功能一起使用, 因此在开启LRO时需要关闭IP转发功能. - GRO(
Generic Receive Offload
): 与LRO类似, 不过在合成包时会对每个数据包进行更严格的检查, 比如会检查数据包的MAC头; 查看TCP的时间戳. GRO可以通过软件或者在网卡中实现.
在Linux中, 可以通过ethtool
工具来查看以及设定网卡的offload
配置:
1 |
|
可以看到有如下输出:
1 |
|
数据链路层优化-scaling技术
上面讲到的offload
技术主要是针对数据传输(包括接收与发送)的优化, 这里说的scaling
(伸缩,即具备弹性,随着系统的增大而性能不会收到影响)技术则主要是针对接收的优化, 其主要是用于增加多核CPU系统中网络并发处理能力, 改善网络性能.
了解Linux网络协议栈数据接收的流程(参考从NAPI说一说Linux内核数据的接收流程, 我们知道, 网卡接收到数据后, 会以DMA的形式拷贝到内存, 然后发送中断告知CPU进行处理. 对于多核CPU系统来说, 有如下几个问题需要仔细考量:
- 如何减少网络中断: 在高速网卡中, 可能有大量的中断产生, 过多的中断会影响CPU的性能, 进而影响网络吞吐量. Linux内核协议栈采用了
NAPI
技术来达到减少中断的目的; 除了NAPI
技术, 很多网卡也会提供中断合并(Interrupt Coalescence
)方法来减少中断数量, 简单来说, 中断合并是指网卡会等某个超时时间或者传送了某个数量的帧后才会向CPU发送一个数据接收的中断. - 中断在哪个CPU上处理: 多队列网卡上, 每个队列都可能有自己的中断, 如何将中断均衡的分配到每个CPU上在一定程度上影响网络性能.
- 数据包要在哪个CPU上处理: 默认情况下, 数据包会发送给处理中断的CPU上, 但这对多队列网卡来说, 这会造成一定程度上的CPU负载不均衡, 从而影响系统性能.
内核中的scaling
技术就是针对这后面两个情况进行优化的. 常见的scaling
技术有如下几种:
- RSS(
Receive Side Scaling
): 对现在的网卡来说, 可能存在多个队列, 每个队列又会有一个中断; 如何把数据包均衡的分布到每个队列, 将多个网卡中断均衡发送到每个CPU上就是RSS需要解决的问题. 对于数据包的均衡, 需要通过网卡中的indirection table
来实现; 中断均衡可以通过设置中断的CPU亲和性来实现. - RPS(
Receive Packet Steering
): RPS可以看作是RSS的软件实现版, 区别在于RPS是在中断处理之后CPU处理数据包时进行. RPS旨在把网卡发送的数据包发送给给定的CPU进行处理, 从而实现数据包的均衡.相比RSS, RPS具有更好的灵活性, 可以适配任何网卡与协议, 并且不会增加物理设备的中断频率. - RPS Flow Limit(
RFS
): 在某些情况下, 网络数据可能呈现不均衡的情况, 一些数据流可能更大, 从而造成小数据流处理慢的情况. 针对这个问题, RFS在某个CPU输入队列的数据长度超过限值(net.core.netdev_max_backlog
)时, 新的数据包会被丢弃. RFS默认是关闭的. - RFS(
Receive Flow Steering
): RPS只是根据包的哈希值来选择处理数据包的CPU, 但没有考虑数据包接收端的本地性(application locality
), 就是说处理的CPU可能并不是用户进程接收数据包的CPU. RFS正是用于解决该问题, 确保处理数据包的与用户接收进程同在一个CPU上, 从而增强数据缓存命中率. - aRFS(
Accelerated Receive Flow Steering
): aRFS之于RFS类似于RSS之于RPS; aRFS就是RFS的硬件实现, 需要网卡支持硬件加速功能才能实现. 在一定程度上, 由于数据包是直接发送给消费进程所在的本地CPU(进程同一CPU, 或对应缓存架构上应用线程本地的CPU), aRFS在性能上要优于RFS. - XPS(
Transmit Packet Steering
): XPS是针对多队列网卡如何选择发送队列问题的优化策略. 开启XPS时, 内核会记录网卡队列与CPU之间的映射关系, 确保网卡的某个队列与固定的CPU之间绑定, 确保传输完成的中断由对应队列的CPU处理, 这样做一可以减少CPU在网卡队列上的等待时间; 一可以减少内存缓存缺失.
综合来看, 无论是RSS/RPS, 还是RFS/XPS, 都是尝试在多CPU与多队列网卡之间的数据包处理上达成均衡分配的目的, 以减少CPU处理压力, 增加内存缓存命中率, 提高网卡的吞吐量. 接下来就来看看如何在Linux中配置scaling
的优化方案.
- RSS配置: 将数据包均匀的发送到不同CPU上可以减少网卡的队列长度, 希望优化网络延迟的情况下可以打开该配置. 如果网卡本身支持RSS, 可以尝试通过
ethtool
来设置; 对中断均衡, 可以通过配置网卡中断亲和性来确保RSS起作用, 比如echo 0xff > /proc/irq/<irq_no>/smp_affinity
. - RPS配置: RPS需要内核配置
CONFIG_RPS
才会编译; 如果要开启, 还需要显式的指定网卡队列需要绑定的CPU:/sys/class/net/<dev>/queues/rx-<n>/rps_cpus
, 如果这个值是0, RPS是关闭状态, 这时数据包会发送到处理中断的CPU. 如果网卡本身是多队列的, 并且每个队列都对应映射到每个CPU上, RPS可以不用配置; 但如果网卡队列数量少于CPU数量, RPS仍然是有益的. - RPS Flow Limit配置: RPS限流内核配置(
CONFIG_NET_FLOW_LIMIT
)是默认打开的, 但是功能本身是关闭状态, 需要通过设置/proc/sys/net/core/flow_limit_cpu_bitmap
(与rps_cpus
设置一样)来开启该功能. - RFS配置: RFS只有在
CONFIG_RPS
打开的情况下才可用, 该功能需要配置两个参数, 一个用于配置全局流表,/proc/sys/net/core/rps_sock_flow_entries
; 一个是用于配置网卡每个队列的限流数量,/sys/class/net/<dev>/queues/rx-<n>/rps_flow_cnt
. - aRFS配置: aRFS需要使能内核配置
CONFIG_RFS_ACCEL
并且需要网卡本身支持. 有硬件加速功能个的网卡通常都实现了ndo_rx_flow_steer
这个函数. - XPS配置: XPS需要配置内核
CONFIG_XPS
, 如果要打开该功能, 配置CPU的位图即可/sys/class/net/<dev>/queues/tx-<n>/xps_cpus
.
有关上述网络优化方案的详细配置可以参考内核文档Documentation/networking/scaling.txt
.
参考资料
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/network-rfs
- Linux Documentation/networking(scaling/segmentation-offloads)
- https://www.linkedin.com/pulse/modern-high-speed-networking-techniques-hardware-john-velegrakis
- GSO: Generic Segmentation Offload
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/network-nic-offloads