4. 数据包发送的全链路流程

数据包发送流程是Linux网络协议栈的核心上行链路,与接收流程相比,发送流程更强调流量控制、拥塞控制、批量发送优化,也是高并发场景下带宽跑不满、发送延迟高、重传率高的核心排查环节。用户态应用的一次send()调用,会经过Socket层、传输层、网络层、链路层、驱动层、硬件层的全链路处理,最终通过网卡发送到网络中,全程涉及缓冲区管理、协议头封装、硬件卸载、中断处理等多个核心机制。
本章节基于Linux 6.6 LTS内核,源码覆盖net/ipv4/tcp_output.c(TCP发送核心)、net/core/dev.c(发送队列核心)、net/sched/(TC流量控制)、drivers/net/ethernet/(网卡驱动发送逻辑),完整拆解从用户态写入数据,到网卡硬件发送完成的全链路流程,深度解析TSO/GSO、TCP发送队列、流量控制、拥塞控制、驱动发送机制,同时提供生产环境的发送性能调优、丢包排查、避坑指南。

4.1 发送流程的核心设计思想

Linux内核的发送流程设计,始终围绕零拷贝、批量发送、流量可控、拥塞避免四大核心目标,在保证协议规范的前提下,最大化发送吞吐量、最小化发送延迟:
  1. 零拷贝设计:用户态数据通过copy_from_user()仅拷贝一次到内核Socket发送缓冲区,后续全程通过sk_buff指针传递,无需重复拷贝数据;同时支持sendfile()零拷贝系统调用,直接从文件缓存发送数据,无需经过用户态,极大降低CPU开销。
  2. 批量发送优化:通过GSO(Generic Segmentation Offload,通用分段卸载)、TSO(TCP Segmentation Offload,TCP分段卸载),把大段数据的分段工作卸载到网卡硬件,内核只需处理一个大包,减少协议栈处理开销;同时通过NAPI批量发送机制,减少中断次数,提升发送效率。
  3. 端到端流量控制:通过TCP滑动窗口机制,保证发送速率不超过接收端的处理能力,避免发送过多数据包导致接收端缓冲区溢出;同时通过Socket发送缓冲区的水位控制,避免用户态无限制写入数据导致内核内存耗尽。
  4. 网络拥塞控制:通过TCP拥塞控制算法(BBR/CUBIC),实时探测网络的带宽和延迟,动态调整发送速率,避免网络拥塞导致的丢包和重传,最大化利用网络带宽。
  5. 可扩展的流量管控:通过TC(Traffic Control)流量控制框架,在发送链路的关键节点预留钩子,支持限速、流量整形、优先级调度、策略路由等高级功能,满足企业级网络的QoS需求。

4.2 发送流程核心基础组件

在拆解全链路流程之前,先理解3个核心基础组件,它们是整个发送流程的基石:
4.2.1 Socket发送缓冲区与水位控制
Socket发送缓冲区是用户态数据在内核的暂存地,也是发送流程的起点,由struct sock中的三个核心字段管理:
  • sk_sndbuf:发送缓冲区的最大内存上限,可通过setsockopt()的SO_SNDBUF选项修改,对应内核参数net.core.wmem_max、net.ipv4.tcp_wmem;
  • sk_wmem_alloc:发送缓冲区已分配的内存大小,原子变量,实时统计已放入发送队列、还未发送完成的数据包内存占用;
  • sk_write_queue:发送队列,链表结构,存储所有已准备好、等待发送的sk_buff数据包,是TCP发送逻辑的核心队列。
水位控制机制:内核为发送缓冲区设置了三个水位线,用于控制用户态写入的阻塞/唤醒逻辑,平衡发送速度与内存占用:
  1. SNDLOWAT(发送低水位):默认1字节,当发送缓冲区已用内存低于该值时,唤醒等待写入的用户态进程;
  2. SNDBUF:发送缓冲区的上限,当sk_wmem_alloc超过该值时,阻塞用户态的写入操作(阻塞Socket),或返回EAGAIN(非阻塞Socket);
  3. NOTIFY_SPACE:通知阈值,当发送缓冲区释放的内存超过该值时,唤醒等待的用户态进程。
4.2.2 GSO/TSO分段卸载机制
GSO/TSO是Linux内核提升大包发送性能的核心机制,解决了「TCP必须按MSS分段发送」带来的协议栈处理开销问题。
  • TSO(TCP Segmentation Offload):TCP分段卸载,网卡硬件级别的优化。内核把超过MSS的大段TCP数据,封装成一个大的sk_buff,无需在内核中分段,直接交给网卡硬件,由网卡硬件拆分为多个符合MSS大小的TCP包,计算校验和,添加序列号,发送到网络中。
  • GSO(Generic Segmentation Offload):通用分段卸载,内核软件层面的通用优化,是TSO的软件补充。对于不支持TSO的网卡,内核在发送到驱动之前,才把大包拆分为多个小包,尽可能延迟分段操作,减少协议栈的处理次数;对于支持TSO的网卡,GSO直接把大包交给网卡,开启TSO。
核心优势:
  • 内核只需处理一个大包,代替多个小包,协议栈处理开销降低80%以上,CPU占用显著下降;
  • 网卡硬件分段的效率远高于内核软件分段,发送吞吐量提升30%以上;
  • 减少了内核中的锁竞争、内存分配次数,提升了高并发场景下的稳定性。
配置方式:默认开启,可通过ethtool -K eth0 tso on gso on开启,off关闭。生产环境万兆网卡场景必须开启,除非有特殊的底层网络开发需求。
4.2.3 TC流量控制框架
TC(Traffic Control)是Linux内核的流量控制框架,位于链路层发送流程的核心位置,是实现限速、流量整形、优先级调度、QoS保障的核心工具。
  • 核心位置:数据包从网络层下来后,会先经过TC的入队规则处理,放入对应的队列(Qdisc,排队规则),然后才会交给网卡驱动发送;
  • 核心组件:
  1. Qdisc(排队规则):数据包的排队算法,比如fq_codel(默认,低延迟公平排队)、htb(分层令牌桶,限速+优先级)、pfifo_fast(传统先进先出);
  2. Class(类):用于分层流量控制,每个类可以设置独立的带宽、优先级,对应不同的业务流量;
  3. Filter(过滤器):用于把不同的数据包(按IP、端口、DSCP标记)分类到不同的Class中,实现差异化的流量控制。
  • 核心作用:生产环境中用于限制业务的发送带宽、保障关键业务的优先级、降低网络延迟毛刺、避免单业务占用所有出口带宽。

4.3 数据包发送的完整全链路流程

基于Linux 6.6内核,从用户态应用调用send()/write()系统调用,到网卡硬件发送完成、释放缓冲区,完整的链路分为8个核心阶段,每个阶段的内核动作、关键函数、核心逻辑都清晰可追溯。
1. 用户态写入数据 → 2. Socket层拷贝与校验 → 3. 传输层封装与控制 → 4. 网络层封装与路由 → 5. 链路层处理与TC流量控制 → 6. 驱动层入队与DMA映射 → 7. 网卡硬件发送 → 8. 发送完成中断与资源释放

阶段1:用户态写入数据
  1. 用户态应用调用send()/sendto()/write()系统调用,传入待发送的数据缓冲区、长度、目标地址等参数;
  2. 系统调用触发CPU从用户态陷入内核态,进入Socket层的核心处理函数。
零拷贝优化:如果使用sendfile()系统调用,数据直接从文件页缓存发送,无需拷贝到用户态,再拷贝到内核,减少一次数据拷贝,CPU开销降低50%以上,是大文件传输、静态资源服务的核心优化手段。
阶段2:Socket层拷贝与校验
Socket层是发送流程的入口,核心是完成数据从用户态到内核的拷贝,以及Socket状态、权限的校验。
  1. 合法性校验:检查Socket的状态(TCP必须是已连接状态)、文件描述符权限、用户态缓冲区的地址合法性,非法请求直接返回错误;
  2. 发送缓冲区水位检查:检查Socket发送缓冲区的剩余空间,如果已用内存超过sk_sndbuf上限:
  • 阻塞Socket:进程进入睡眠状态,等待发送缓冲区有足够的空闲空间;
  • 非阻塞Socket:立即返回EAGAIN错误;
  1. 数据拷贝:调用copy_from_user(),把数据从用户态缓冲区拷贝到内核分配的sk_buff结构体中,同时初始化sk_buff的核心字段,关联对应的Socket;
  2. 放入发送队列:把填充好的sk_buff加入到Socket的sk_write_queue发送队列尾部,更新sk_wmem_alloc已用内存计数;
  3. 调用传输层发送接口:根据Socket的协议类型,调用对应的传输层发送函数,TCP对应tcp_sendmsg(),UDP对应udp_sendmsg()。
本阶段核心注意点:数据拷贝是发送流程中CPU开销最大的环节之一,高并发场景下,应尽量减少小数据包的频繁发送,采用攒批发送的方式,减少系统调用和数据拷贝次数。
阶段3:传输层封装与控制
传输层是发送流程的核心控制层,TCP/UDP的处理逻辑差异极大,这里分别拆解TCP(面向连接的可靠传输)和UDP(无连接的不可靠传输)的核心处理逻辑。
3.1 TCP传输层核心处理
TCP是面向连接的可靠传输协议,发送逻辑最为复杂,核心是流量控制、拥塞控制、序列号管理、重传机制,核心处理函数是tcp_sendmsg()和tcp_write_xmit()。
  1. TCP头封装:为每个sk_buff添加TCP头,填充源端口、目的端口、序列号、确认号、窗口大小、标志位等字段;
  2. MSS与GSO/TSO处理:如果数据长度超过MSS(最大分段大小),开启TSO/GSO的情况下,内核不会分段,直接封装成一个大包,交给网卡硬件分段;未开启TSO的情况下,内核把数据拆分为多个MSS大小的sk_buff;
  3. 流量控制检查:检查接收端的滑动窗口大小,确保发送的数据量不超过接收端的接收窗口,避免数据包被接收端丢弃;
  4. 拥塞控制检查:根据拥塞控制算法(BBR/CUBIC)计算的拥塞窗口大小,控制发送速率,确保不超过网络的承载能力,避免拥塞丢包;
  5. Nagle算法处理:如果开启了Nagle算法(默认开启),会合并小的TCP包,减少网络中的小包数量,降低带宽浪费;低延迟场景可通过TCP_NODELAY选项关闭;
  6. 数据包下发:把封装好的sk_buff交给网络层,调用ip_queue_xmit()函数,同时把数据包保存在TCP的重传队列中,等待接收端的ACK确认,收到ACK后才会释放对应的sk_buff。
3.2 UDP传输层核心处理
UDP是无连接的不可靠传输协议,发送逻辑非常简单,核心处理函数是udp_sendmsg():
  1. UDP头封装:为sk_buff添加UDP头,填充源端口、目的端口、长度、校验和字段;
  2. 长度检查:如果数据包长度超过MTU,会进行IP分片(默认开启),或者返回EMSGSIZE错误(开启MSG_DONTROUTE);
  3. 数据包下发:直接把封装好的sk_buff交给网络层,调用ip_send_skb()函数,不会保存重传副本,发送完成后直接释放sk_buff。
本阶段丢包点:
  • TCP发送窗口为0,接收端缓冲区满,无法发送数据;
  • TCP拥塞窗口过小,网络拥塞导致发送速率被限制;
  • UDP数据包超过MTU,且DF(不分片)标志位被设置,数据包被丢弃;
  • Socket发送缓冲区满,用户态写入失败。
阶段4:网络层封装与路由
数据包进入网络层后,核心完成IP头封装、路由查找、分片处理、Netfilter防火墙规则处理,核心函数是ip_queue_xmit()(IPv4)和ip6_xmit()(IPv6)。

1.路由查找:根据目的IP地址,查找路由表,确定下一跳IP地址、出接口网卡、源IP地址,路由查找失败的数据包直接丢弃;

2.IP头封装:为sk_buff添加IP头,填充版本、头长度、服务类型、总长度、标识、标志位、TTL、协议类型、源IP、目的IP等字段,计算IP头校验和;

3.Netfilter钩子处理:执行Netfilter的NF_INET_LOCAL_OUT钩子点,处理iptables/nftables的OUTPUT链规则,规则拒绝的数据包直接丢弃;

4.PMTU发现与分片处理:检查数据包长度是否超过出接口的MTU,如果超过:

  • 开启了PMTU发现,且DF标志位被设置:返回EMSGSIZE错误,通知用户态;
  • 未设置DF标志位:把数据包拆分为多个MTU大小的IP分片,每个分片添加独立的IP头;

5.数据包下发:把处理完成的sk_buff交给链路层,调用dev_queue_xmit()函数。

本阶段丢包点:
  • 路由查找失败,没有到达目的IP的路由,数据包被丢弃;
  • iptables/nftables的OUTPUT链规则DROP了数据包;
  • 数据包超过MTU,且DF标志位被设置,无法分片,数据包被丢弃;
  • TTL耗尽,数据包被丢弃。
阶段5:链路层处理与TC流量控制
数据包进入链路层后,核心完成以太网帧头封装、ARP地址解析、TC流量控制、VLAN标签处理,是发送到驱动之前的最后一个处理环节。
  1. 下一跳MAC地址解析:根据下一跳IP地址,查找ARP表,获取对应的MAC地址;如果ARP表中没有对应的条目,触发ARP请求,暂停发送,等待ARP应答;
  2. 以太网帧头封装:为sk_buff添加以太网帧头,填充目的MAC地址、源MAC地址、上层协议类型(IPv4/IPv6/ARP);
  3. VLAN/隧道处理:添加VLAN标签,封装VXLAN/IPsec/GRE等隧道协议头;
  4. Netfilter钩子处理:执行Netfilter的NF_INET_POST_ROUTING钩子点,处理iptables/nftables的POSTROUTING链规则,比如SNAT、MASQUERADE地址转换,规则拒绝的数据包直接丢弃;
  5. TC流量控制入队:把数据包交给TC流量控制框架,执行入队规则,放入对应的Qdisc排队规则中,进行限速、流量整形、优先级调度处理;
  6. 驱动下发:TC调度器选中数据包后,调用网卡驱动的ndo_start_xmit()函数,把数据包交给网卡驱动层。
本阶段丢包点:
  • ARP解析失败,无法获取下一跳的MAC地址,数据包被丢弃;
  • iptables/nftables的POSTROUTING链规则DROP了数据包;
  • TC限速规则,超过带宽的数据包被丢弃;
  • 出接口网卡down,无法发送数据包。
阶段6:驱动层入队与DMA映射
网卡驱动层是内核与硬件的桥梁,核心完成sk_buff的合法性校验、DMA映射、放入发送环形缓冲区(TX Ring Buffer),通知硬件发送。
  1. 合法性校验:检查数据包的长度、出接口状态,非法的数据包直接释放,返回错误;
  2. DMA映射:把sk_buff的数据缓冲区映射为网卡可访问的DMA地址,保证网卡硬件可以通过DMA直接读取内存中的数据,无需CPU参与;
  3. 放入TX Ring Buffer:从发送环形缓冲区中取出一个空闲的描述符,填充DMA地址、数据包长度、控制标志,标记为「待发送」;
  4. 唤醒硬件发送:写入网卡寄存器,通知硬件有新的数据包待发送,触发硬件的DMA传输;
  5. 更新统计信息:更新网卡的发送字节数、包数统计。
本阶段丢包点:
  • TX Ring Buffer空闲描述符耗尽,驱动无法放入新的数据包,数据包被丢弃,对应ethtool -S eth0中的tx_fifo_errors、tx_dropped计数增长;
  • DMA映射失败,内存地址不可访问,数据包被丢弃;
  • 网卡硬件故障、链路down,数据包无法发送。
阶段7:网卡硬件发送
  1. 网卡硬件从TX Ring Buffer中读取「待发送」的描述符,通过DMA总线,从内核内存中读取数据包内容,写入网卡内部的发送FIFO缓冲区;
  2. MAC层添加帧校验序列(FCS),把以太网帧转换为电信号/光信号,通过PHY层发送到网线/光纤中;
  3. 发送完成后,把对应的描述符标记为「已发送」,触发硬件中断,通知CPU发送完成。
阶段8:发送完成中断与资源释放
  1. 网卡触发发送完成硬中断,CPU跳转到驱动的中断处理函数;
  2. 中断顶半部快速应答中断,关闭发送中断,触发NAPI调度,唤醒软中断;
  3. 软中断执行NAPI的poll函数,遍历TX Ring Buffer中「已发送」的描述符;
  4. 解除DMA映射,释放对应的sk_buff结构体,更新Socket发送缓冲区的sk_wmem_alloc计数,减少已用内存;
  5. 如果发送缓冲区的空闲空间超过了通知阈值,唤醒等待在Socket上的用户态进程,通知可以继续写入数据;
  6. 所有描述符处理完成后,重新开启发送中断,回到空闲状态。
TCP特殊处理:TCP的数据包发送完成后,不会立即释放sk_buff,而是保存在重传队列中,直到收到接收端的ACK确认,才会释放;如果超时未收到ACK,会触发重传机制,重新发送对应的数据包。

4.4 高并发发送核心优化机制深度拆解

生产环境中,要实现万兆网卡线速发送、低延迟高并发的发送能力,必须依赖以下几个核心优化机制,这里深度拆解其原理与配置方法。
4.4.1 TSO/GSO/LRO硬件卸载
这是提升发送性能的最核心优化,生产环境万兆网卡必须开启:
  • TSO/GSO:如前所述,把TCP分段工作卸载到网卡硬件,减少内核协议栈的处理开销,CPU占用降低50%以上,发送吞吐量提升30%以上;
  • GSO(Generic Segmentation Offload):不仅支持TCP,还支持UDP、SCTP等其他协议,是通用的分段卸载机制;
  • 校验和卸载:把TCP/UDP/IP头的校验和计算卸载到网卡硬件,无需CPU计算,进一步降低CPU开销;
  • 配置命令:
# 查看网卡的硬件卸载特性 ethtool -k eth0 # 开启TSO、GSO、校验和卸载 ethtool -K eth0 tso on gso on tx-checksum-ip-generic on
4.4.2 多队列网卡与XPS(Transmit Packet Steering)
XPS是发送端的CPU亲和性优化机制,对应接收端的RSS,解决了多队列网卡发送的CPU瓶颈问题。
  • 核心原理:多队列网卡有多个TX发送队列,每个队列对应一个CPU核心。XPS通过配置,把特定CPU核心发起的数据包,固定发送到对应的TX队列,触发对应CPU核心的发送中断,实现:
  1. 发送处理的CPU与中断处理的CPU是同一个核心,提升CPU缓存命中率,降低延迟;
  2. 多个CPU核心对应多个TX队列,实现多核并行发送,发送性能随CPU核心数线性扩展;
  • 配置方法:
# 配置XPS,把TX队列0绑定到CPU0,TX队列1绑定到CPU1,以此类推(8核CPU)
echo 1 > /sys/class/net/eth0/queues/tx-0/xps_cpus
echo 2 > /sys/class/net/eth0/queues/tx-1/xps_cpus
echo 4 > /sys/class/net/eth0/queues/tx-2/xps_cpus
echo 8 > /sys/class/net/eth0/queues/tx-3/xps_cpus
echo 10 > /sys/class/net/eth0/queues/tx-4/xps_cpus
echo 20 > /sys/class/net/eth0/queues/tx-5/xps_cpus
echo 40 > /sys/class/net/eth0/queues/tx-6/xps_cpus
echo 80 > /sys/class/net/eth0/queues/tx-7/xps_cpus

  • 最佳实践:TX队列数量设置为等于CPU核心数,每个TX队列绑定到对应的CPU核心,关闭irqbalance服务,手动绑定发送中断到对应CPU核心。
4.4.3 TC排队规则优化
TC的排队规则(Qdisc)直接决定了发送的延迟、公平性和带宽利用率,生产环境需根据业务场景选择合适的Qdisc:
  1. fq_codel:Linux内核默认的排队规则,是「公平排队+受控延迟」的组合,完美平衡了吞吐量和延迟,解决了bufferbloat(缓冲区膨胀)问题,适合绝大多数通用服务器场景、低延迟业务场景;
  2. htb:分层令牌桶,支持分层限速、带宽分配、优先级调度,适合需要对不同业务、不同IP/端口做差异化带宽保障的场景,比如多租户服务器、网关设备;
  3. pfifo_fast:传统的先进先出排队规则,优先级基于IP的TOS字段,吞吐量高,但延迟抖动大,容易出现bufferbloat,不推荐现代网络环境使用;
  4. mq:多队列排队规则,专为多队列网卡设计,每个TX队列对应一个独立的子Qdisc,实现多核并行发送,无锁竞争,多队列网卡必须开启。
配置示例:
# 查看当前网卡的Qdisc配置
tc qdisc show dev eth0
# 把网卡的Qdisc替换为mq+fq_codel,适配多队列网卡,优化低延迟
tc qdisc replace dev eth0 root mq
for i in $(seq 0 7); do
  tc qdisc add dev eth0 parent $(printf "800%d:$(($i+1))" $i) fq_codel
done

4.4.4 TCP拥塞控制算法优化
TCP拥塞控制算法直接决定了发送速率、带宽利用率、重传率,是广域网发送性能的核心优化点:
  1. CUBIC:Linux内核默认的拥塞控制算法,基于三次函数的拥塞窗口增长,在高带宽长延迟网络中表现稳定,适合绝大多数通用场景;
  2. BBR:基于带宽和延迟探测的拥塞控制算法,不依赖丢包来调整发送速率,在有丢包的广域网、跨地域网络中,带宽利用率远高于CUBIC,重传率更低,适合跨地域服务、CDN、大文件传输场景;
  3. 配置方法:
# 查看内核支持的拥塞控制算法
cat /proc/sys/net/ipv4/tcp_available_congestion_control
# 设置默认拥塞控制算法为BBR
net.ipv4.tcp_congestion_control = bbr
# 开启TCP窗口缩放,支持大带宽长延迟网络
net.ipv4.tcp_window_scaling = 1
# 开启TCP时间戳,提升RTT计算精度
net.ipv4.tcp_timestamps = 1


4.5 工程实践:发送性能调优与丢包排查

生产环境中,发送带宽跑不满、发送延迟高、重传率高、丢包等问题,绝大多数都出现在发送流程中,这里提供标准化的排查流程、调优方案与避坑指南。
4.5.1 发送丢包标准化排查流程
发送丢包的排查原则是从上到下,逐层排查,从用户态→Socket层→传输层→网络层→链路层→驱动→硬件,逐层定位丢包点。

      第一步:Socket层发送缓冲区排查

# 查看Socket的发送队列积压情况,Send-Q列是发送队列的积压包数
ss -tulnp
# 查看TCP连接的发送缓冲区使用情况、发送窗口、拥塞窗口
ss -ti

  • 如果Send-Q持续不为0,说明内核发送速度跟不上用户态写入速度,或者网络链路有瓶颈;
  • 解决方式:调大Socket发送缓冲区net.core.wmem_max、net.ipv4.tcp_wmem,优化用户态写入逻辑,避免无限制写入。

     第二步:传输层丢包排查

# 查看TCP/UDP协议的发送统计信息
cat /proc/net/snmp
cat /proc/net/netstat
# 重点关注:
# TcpOutSegs:TCP发送的总段数
# TcpRetransSegs:TCP重传的段数,重传率过高说明网络拥塞
# TcpLostSegments:TCP丢失的段数
# UdpOutDatagrams:UDP发送的数据包数
# UdpSendbufErrors:UDP发送缓冲区满丢包

  • 如果TCP重传率超过2%,说明网络链路有拥塞、丢包,需要优化拥塞控制算法,检查链路质量;
  • 解决方式:切换到BBR拥塞控制算法,调整TCP发送窗口大小,检查链路的带宽和延迟。

 第三步:网络层与防火墙丢包排查

# 查看iptables规则的发送统计,确认是否有规则DROP了数据包
iptables -nvL OUTPUT
iptables -nvL POSTROUTING
# 查看路由表,确认是否有到达目的地址的路由
ip route show
# 查看ARP表,确认下一跳MAC地址是否正常
arp -n

  • 重点关注OUTPUT、POSTROUTING链的DROP规则,确认是否有误配置;
  • 检查ARP表是否有异常,下一跳IP是否能解析到MAC地址,ARP解析失败会导致发送失败。

第四步:链路层与TC流量控制丢包排查

# 查看TC排队规则配置,确认是否有限速规则
tc qdisc show dev eth0
tc -s class show dev eth0
# 查看TC的丢包统计
tc -s qdisc show dev eth0

  • 如果TC配置了限速规则,超过带宽的数据包会被丢弃,导致发送带宽跑不满;
  • 解决方式:调整TC限速带宽,或者更换更合适的Qdisc规则。

第五步:驱动与硬件层丢包排查

# 查看网卡的发送统计信息,定位硬件层丢包
ethtool -S eth0
# 重点关注:
# tx_errors:总发送错误数
# tx_dropped:驱动层丢弃的数据包数
# tx_fifo_errors:TX Ring Buffer满丢包
# tx_aborted_errors:发送中止错误
# tx_carrier_errors:链路载波错误,通常是网线、光模块故障

  • 如果tx_fifo_errors计数增长,说明TX Ring Buffer太小,突发流量导致缓冲区满丢包;
  • 解决方式:调大TX Ring Buffer大小ethtool -G eth0 tx 4096,开启多队列TX,提升发送能力。
4.5.2 高并发发送性能调优最佳实践

硬件层面调优

  • 开启网卡的多队列TX,TX队列数量设置为等于CPU核心数;
  • 配置XPS,把每个TX队列绑定到对应的CPU核心,实现发送亲和性;
  • 调大TX Ring Buffer大小,根据突发流量情况,设置为2048~4096;
  • 开启网卡的硬件卸载特性:TSO、GSO、校验和卸载、LRO;
  • 更换高质量的网线、光模块,确保链路协商到全双工万兆/千兆模式,避免半双工、协商错误导致的发送性能下降。

内核参数调优

# /etc/sysctl.conf 发送核心调优参数
# Socket发送缓冲区默认值/最小值/最大值(字节)
net.core.wmem_default = 262144
net.core.wmem_max = 16777216
# TCP发送缓冲区:最小值/默认值/最大值
net.ipv4.tcp_wmem = 4096 262144 16777216
# UDP发送缓冲区最小值
net.ipv4.udp_wmem_min = 8192
# TCP拥塞控制算法
net.ipv4.tcp_congestion_control = bbr
# 开启TCP窗口缩放,支持大带宽长延迟网络
net.ipv4.tcp_window_scaling = 1
# 开启TCP时间戳,提升RTT计算精度
net.ipv4.tcp_timestamps = 1
# 开启SACK选择性确认,提升丢包重传效率
net.ipv4.tcp_sack = 1
# TCP最大SYN重传次数
net.ipv4.tcp_syn_retries = 3
# TCP数据重传次数
net.ipv4.tcp_retries2 = 8

CPU与NUMA架构调优

  • 多NUMA节点服务器上,把业务进程、发送中断、TX队列,都绑定到同一个NUMA节点的CPU核心上,避免跨NUMA节点的内存访问,降低发送延迟;
  • 关闭CPU变频,设置为性能模式,避免CPU频率波动导致的发送延迟毛刺;
  • 业务进程绑定到固定的CPU核心,提升CPU缓存命中率,减少上下文切换。

应用层调优

  • 采用攒批发送的方式,避免频繁发送小数据包,减少系统调用和协议栈处理开销;
  • 低延迟场景,开启TCP_NODELAY选项,关闭Nagle算法,避免小包合并导致的延迟;
  • 大文件传输场景,使用sendfile()零拷贝系统调用,减少数据拷贝次数,降低CPU开销;
  • 非阻塞Socket+epoll边缘触发模式,批量写入数据,减少系统调用次数;
  • 避免在发送线程中执行耗时操作,把业务逻辑放到独立的工作线程中,保证发送速度。
4.5.3 常见避坑指南
  1. TX Ring Buffer调得过大:和接收队列一样,过大的TX Ring Buffer会导致数据包排队时间过长,延迟升高,出现bufferbloat问题,生产环境建议最大不超过4096;
  2. 关闭TSO/GSO导致CPU占用过高:万兆网卡场景下,关闭TSO/GSO会导致内核分段开销急剧上升,CPU占用100%,发送带宽跑不满,生产环境必须开启;
  3. Nagle算法与TCP_NODELAY冲突:默认开启的Nagle算法会合并小包,导致小数据包发送延迟升高,低延迟场景(如游戏、金融交易)必须开启TCP_NODELAY关闭Nagle算法;
  4. TCP发送窗口过小:广域网场景下,TCP发送窗口受接收端窗口和拥塞窗口限制,导致带宽跑不满,需要开启TCP窗口缩放,调大发送缓冲区,优化拥塞控制算法;
  5. TC限速规则配置错误:错误的TC限速规则会导致发送带宽被限制,甚至大量丢包,配置TC前必须在测试环境验证,避免影响线上业务;
  6. UDP大包发送不处理分片:UDP发送超过MTU的大包时,会触发IP分片,只要有一个分片丢失,整个数据包就会被丢弃,可靠性急剧下降,UDP场景下建议把数据包大小控制在MTU以内(通常1400字节)。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐