如何解决Linux实例内核softnet backlog队列溢出导致的丢包问题

更新时间:
复制为 MD 格式

问题现象

  • 业务层面感知:应用程序出现网络超时、连接中断或数据传输失败。

  • 系统层面丢包:通过cat命令持续观察/proc/net/softnet_stat文件时,发现第二列(dropped)或第三列(squeezed)的计数值持续快速增长。

    # 每一行对应一个CPU核心。
    # 第一列:收到的网络帧总数。
    # 第二列:因为backlog队列满而被丢弃的数据包数 (dropped)。
    # 第三列:因为处理时间耗尽而延迟处理的数据包数 (squeezed)。
    $ cat /proc/net/softnet_stat
    000bb344 00000471 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
    000bc76f 00000305 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
  • CPU软中断高:使用topmpstat等工具,观察到si(softirq)的CPU使用率异常高。

问题原因

当网卡驱动收到数据包后,会通过软中断(NET_RX_SOFTIRQ)通知内核协议栈处理。为缓冲突发流量,内核为每个CPU核心维护了一个softnet backlog队列。丢包主要由以下两个原因引起:

  • softnet backlog队列过小:在高网络吞吐场景下,如果数据包进入队列的速率持续高于CPU处理速率,队列会很快被填满。此时,新到达的数据包将被直接丢弃,导致/proc/net/softnet_stat文件中第二列的dropped计数增加。

  • CPU处理能力不足:即使队列大小充足,但如果CPU核心无法在分配的时间预算内(net.core.netdev_budget)完成对softnet backlog队列中数据包的处理,也会导致处理中断。这种情况被称为time_squeeze,会导致/proc/net/softnet_stat文件中第三列的squeezed计数增加。这表明瓶颈在于CPU算力,而非队列大小。

解决方案

首先通过监控/proc/net/softnet_stat的动态变化,判断丢包是由于队列大小不足(dropped增加)还是CPU处理瓶颈(squeezed增加),然后采取针对性的优化措施。

步骤一:诊断丢包根本原因

实时监控softnet状态,区分是dropped问题还是squeezed问题。

  1. 登录ECS Linux实例。

  2. 执行以下命令,每秒刷新一次softnet的统计数据,重点关注droppedsqueezed列的增量

    /proc/net/softnet_stat中的计数值是自系统启动以来的累计值。仅当观察到数值持续快速增长时,才表明当前存在问题。
    watch -d 'awk "{print \"CPU\"(NR-1)\": dropped=\"\$2\", squeezed=\"\$3}" /proc/net/softnet_stat'
  3. 根据命令输出结果,判断问题类型:

步骤二:调整backlog队列大小

通过调大net.core.netdev_max_backlog参数,可以增加队列容量,缓解因突发流量导致的丢包问题。

  1. 执行以下命令查看当前的netdev_max_backlog值。默认值通常为1000。

    sysctl net.core.netdev_max_backlog
  2. 根据实例网络带宽,参考下表设置一个合理的netdev_max_backlog值。

    重要:不合理的巨大数值会增加内存消耗并可能引入网络延迟,请谨慎设置。 内存占用估算公式:内存占用(Bytes) ≈ netdev_max_backlog × 平均包长 × CPU核心数

    设置的大小主要取决于你的网络带宽业务场景

    业务场景

    带宽环境

    推荐值

    说明

    默认/低配

    1Gbps

    1000(默认)

    默认值通常为 1000,对于普通流量够用。

    中等负载

    1Gbps ~ 10Gbps

    5000 ~ 10000

    适用于大部分标准 Web 服务器、应用服务器。

    高并发/高吞吐

    10Gbps

    30000

    适用于 Nginx 网关、Redis、高频 API 服务。

    极高性能

    40Gbps+

    60000 ~ 100000

    适用于核心交换节点、DDoS 清洗、超高频交易系统。

  3. 临时修改参数值使其立即生效。请将NETDEV_MAX_BACKLOG_NUMBER替换为实际规划的数值。

    sysctl -w net.core.netdev_max_backlog=NETDEV_MAX_BACKLOG_NUMBER
  4. 为确保服务器重启后配置依然生效,需要将配置持久化。

    • 方法一:推荐 创建或修改/etc/sysctl.d/99-network-tuning.conf文件,并加入以下内容:

      # Increase kernel softnet backlog queue size
      net.core.netdev_max_backlog = NETDEV_MAX_BACKLOG_NUMBER
    • 方法二:/etc/sysctl.conf文件末尾添加以下内容:

      net.core.netdev_max_backlog = NETDEV_MAX_BACKLOG_NUMBER

      然后执行sysctl -p使配置生效。

  5. 返回步骤一,重新观察dropped计数是否停止增长,以验证优化效果。

步骤三:优化CPU处理能力

利用多核CPU并行处理网络软中断,即启用RPS(Receive Packet Steering)。

  1. 确认实例拥有vCPU数量。RPS在单核实例上无效。

  2. 找到需要优化的网卡名称,通常为eth0eth1

  3. 启用RPS,将网络软中断分发到所有CPU核心进行处理。

    rps_cpus的值是一个CPU位掩码。例如,一个8CPU的掩码是ff(二进制11111111),16核是ffff。可以根据CPU核心数计算,或直接使用一个足够大的值(如ffffffff)来覆盖所有核心。
    # 将<interface>替换为网卡名,例如eth0
    # 将<cpu_mask>替换为计算出的CPU掩码,例如ff (8核)
    echo <cpu_mask> > /sys/class/net/<interface>/queues/rx-0/rps_cpus
    
    # 示例:为8CPUeth0网卡启用RPS
    echo ff > /sys/class/net/eth0/queues/rx-0/rps_cpus
  4. 为使配置在重启后依然生效,需要将上述命令添加到启动脚本中,例如/etc/rc.local

  5. 返回步骤一,重新观察squeezed计数是否停止增长,以验证优化效果。

后续建议

  • 设置监控告警:在阿里云云监控中,为ECS实例配置自定义监控项,监控/proc/net/softnet_statdroppedsqueezed的增长率,并设置告警规则,以便在问题发生时第一时间收到通知。

  • 选择合适的实例规格:对于网络密集型应用,推荐选用网络增强型实例规格(如c7ne、g7ne等),它们提供更高的网络PPS(包转发率)和带宽性能。