44-套路篇:网络性能优化的几个思路(下)





网络性能优化

传输层

传输层最重要的是TCP和UDP协议,所以这儿的优化,其实主要就是对这两种协议的优化

TCP协议的优化

TCP提供了面向连接的可靠传输服务
要优化TCP首先要掌握TCP协议的基本原理
比如流量控制、慢启动、拥塞避免、延迟确认以及状态流图(如下图所示)等

image-20211231140805120
分几类情况详细说明

  1. 第一类,在请求数比较大的场景下,可能会看到大量处于TIME_WAIT状态的连接
    它们会占用大量内存和端口资源
    这时,可以优化与TIME_WAIT状态相关的内核选项,比如采取下面几种措施

    1. 增大处于TIME_WAIT状态的连接数量net.ipv4.tcp_max_tw_buckets
      并增大连接跟踪表的大小net.netfilter.nf_conntrack_max
    2. 减小net.ipv4.tcp_fin_timeout和net.netfilter.nf_conntrack_tcp_timeout_time_wait
      让系统尽快释放它们所占用的资源
    3. 开启端口复用net.ipv4.tcp_tw_reuse
      这样被TIME_WAIT状态占用的端口,还能用到新建的连接中
    4. 增大本地端口的范围net.ipv4.ip_local_port_range
      这样就可以支持更多连接,提高整体的并发能力
    5. 增加最大文件描述符的数量
      可以使用fs.nr_open ,设置系统的最大文件描述符数
      或在应用程序的systemd配置文件中,配置LimitNOFILE,设置应用程序的最大文件描述符数

  2. 第二类,为了缓解SYN FLOOD等
    利用TCP协议特点进行攻击而引发的性能问题,可以考虑优化与SYN状态相关的内核选项
    比如采取下面几种措施

    1. 增大TCP半连接的最大数量net.ipv4.tcp_max_syn_backlog
      或者开启TCP SYN Cookies net.ipv4.tcp_syncookies
      来绕开半连接数量限制的问题(注意这两个选项不可同时使用)
    2. 减少SYN_RECV状态的连接重传SYN+ACK包的次数net.ipv4.tcp_synack_retries

  3. 第三类,在长连接的场景中,通常使用Keepalive来检测TCP连接的状态
    以便对端连接断开后,可以自动回收
    但是系统默认的Keepalive探测间隔和重试次数,一般都无法满足应用程序的性能要求
    所以这时候需要优化与Keepalive相关的内核选项,比如

    1. 缩短最后一次数据包到Keepalive探测包的间隔时间net.ipv4.tcp_keepalive_time
    2. 缩短发送Keepalive探测包的间隔时间net.ipv4.tcp_keepalive_intvl
    3. 减少Keepalive探测失败后,一直到通知应用程序前的重试次数net.ipv4.tcp_keepalive_probes

image-20211231141315492


优化TCP性能时,还要注意,如果同时使用不同优化方法,可能会产生冲突

比如就像网络请求延迟案例中,服务器端开启Nagle算法,客户端开启延迟确认机制,就很容易导致网络延迟增大

另外在使用NAT的服务器上,如果开启net.ipv4.tcp_tw_recycle ,就很容易导致各种连接失败
实际上,由于坑太多,这个选项在内核的4.1版本中已经删除了


UDP协议的优化

UDP提供了面向数据报的网络协议,它不需要网络连接,也不提供可靠性保障
所以UDP 优化,相对于 TCP 来说,要简单得多

  1. 跟上篇套接字部分提到的一样,增大套接字缓冲区大小以及UDP缓冲区范围
  2. 跟前面TCP部分提到的一样,增大本地端口号的范围
  3. 根据MTU大小,调整UDP数据包的大小,减少或者避免分片的发生


网络层

网络层负责网络包的封装、寻址和路由,包括IP、ICMP等常见协议
在网络层最主要的优化,其实就是对路由、 IP分片以及ICMP等进行调优

  1. 第一种从路由和转发的角度出发,可以调整下面的内核选项

    1. 在需要转发的服务器中,比如用作NAT网关的服务器或者使用Docker容器时
      开启IP转发,即设置net.ipv4.ip_forward = 1
    2. 调整数据包的生存周期TTL,比如设置net.ipv4.ip_default_ttl = 64
      注意增大该值会降低系统性能
    3. 开启数据包的反向地址校验,比如设置net.ipv4.conf.eth0.rp_filter = 1
      这样可以防止IP欺骗,并减少伪造IP带来的DDoS问题
  2. 第二种,从分片的角度出发,最主要的是调整MTU(Maximum Transmission Unit)的大小
    通常MTU的大小应该根据以太网的标准来设置
    以太网标准规定一个网络帧最大为1518B,去掉以太网头部的18B后,剩余的1500就是以太网MTU的大小
    在使用VXLAN、GRE等叠加网络技术时,要注意网络叠加会使原来的网络包变大,导致MTU也需要调整

    比如,就以VXLAN为例,它在原来报文的基础上,
    增加了14B的以太网头部、 8B的VXLAN头部、8B的UDP头部以及20B的IP头部
    换句话说,每个包比原来增大了50B

    所以就需要把交换机、路由器等的MTU,增大到1550
    或者把VXLAN封包前 (比如虚拟化环境中的虚拟网卡)的MTU减小为1450

    另外现在很多网络设备都支持巨帧,如果是这种环境,还可以把MTU调大为9000, 以提高网络吞吐量

  3. 第三种,从ICMP的角度出发,为了避免ICMP主机探测、ICMP Flood等各种网络问题
    可以通过内核选项,来限制ICMP的行为

    1. 禁止ICMP协议,即设置net.ipv4.icmp_echo_ignore_all = 1
      这样,外部主机就无法通过ICMP来探测主机
    2. 禁止广播ICMP,即设置net.ipv4.icmp_echo_ignore_broadcasts = 1


链路层

网络层的下面是链路层,所以最后再来看链路层的优化方法

链路层负责网络包在物理网络中的传输,比如MAC寻址、错误侦测以及通过网卡传输网络帧等
自然链路层的优化,也是围绕这些基本功能进行的

由于网卡收包后调用的中断处理程序(特别是软中断),需要消耗大量的CPU
所以将这些中断处理程序调度到不同的CPU上执行,就可以显著提高网络吞吐量
这通常可以采 用下面两种方法

  1. 可以为网卡硬中断配置CPU亲和性(smp_affinity),或者开启irqbalance服务
  2. 可以开启RPS(Receive Packet Steering)和RFS(Receive Flow Steering)
    将应用程序和软中断的处理调度到相同CPU上,这样就可以增加CPU缓存命中率,减少网络延迟

另外现在的网卡都有很丰富的功能,原来在内核中通过软件处理的功能,可以卸载到网卡中,通过硬件来执行

  1. TSO(TCP Segmentation Offload)和UFO(UDP Fragmentation Offload)
    在TCP/UDP协议中直接发送大包
    而TCP包的分段(按照MSS分段)和UDP的分片 (按照MTU分片)功能,由网卡来完成
  2. GSO(Generic Segmentation Offload)
    在网卡不支持TSO/UFO时,将TCP/UDP包的分段,延迟到进入网卡前再执行
    这样,不仅可以减少CPU的消耗,还可以在发生丢包时只重传分段后的包
  3. LRO(Large Receive Offload)
    在接收TCP分段包时,由网卡将其组装合并后,再交给上层网络处理
    不过要注意,在需要IP转发的情况下,不能开启LRO
    因为如果多个包的头部信息不一致,LRO合并会导致网络包的校验错误
  4. GRO(Generic Receive Offload)
    GRO修复了LRO的缺陷,并且更为通用,同时支持TCP和UDP
  5. RSS(Receive Side Scaling)
    也称为多队列接收,它基于硬件的多个接收队列,来分配网络接收进程,这样可以让多个 CPU 来处理接收到的网络包
  6. VXLAN 卸载
    也就是让网卡来完成VXLAN的组包功能

最后对于网络接口本身,也有很多方法,可以优化网络的吞吐量

  1. 可以开启网络接口的多队列功能
    这样每个队列就可以用不同的中断号,调度到不同CPU上执行,从而提升网络的吞吐量
  2. 可以增大网络接口的缓冲区大小,以及队列长度等,提升网络传输的吞吐量 (注意,这可能导致延迟增大)
  3. 可以使用Traffic Control工具,为不同网络流量配置QoS

到这里就从应用程序、套接字、传输层、网络层,再到链路层,分别介绍了相应的网络性能优化方法
通过这些方法的优化后,网络性能就可以满足绝大部分场景了

最后别忘了一种极限场景,C10M问题吗?
在单机并发1000万的场景中,对Linux网络协议栈进行的各种优化策略,基本都没有太大效果
因为这种情况下,网络协议栈的冗长流程,其实才是最主要的性能负担
这时可以用两种方式来优化

  1. 第一种,使用 DPDK 技术,跳过内核协议栈,直接由用户态进程用轮询的方式,来处理网络请求
    同时,再结合大页、CPU绑定、内存对齐、流水线并发等多种机制,优化网络包的处理效率
  2. 第二种,使用内核自带的XDP技术,在网络包进入内核协议栈前,就对其进行处理
    这样也可以实现很好的性能



小结

在优化网络的性能时
可以结合Linux系统的网络协议栈和网络收发流程,从应用程 序、套接字、传输层、网络层再到链路层等
对每个层次进行逐层优化

实际上分析和定位网络瓶颈,也是基于这些网络层进行的
而定位出网络性能瓶颈后,就可以根据瓶颈所在的协议层,进行优化

  1. 在应用程序中,主要是优化I/O模型、工作模型以及应用层的网络协议
  2. 在套接字层中,主要是优化套接字的缓冲区大小
  3. 在传输层中,主要是优化TCP和UDP协议
  4. 在网络层中,主要是优化路由、转发、分片以及ICMP协议
  5. 在链路层中,主要是优化网络包的收发、网络功能卸载以及网卡选项

如果这些方法依然不能满足要求,那就可以考虑,使用DPDK等用户态方式,绕过内核协议栈
或者,使用XDP,在网络包进入内核协议栈前进行处理


posted @ 2021-12-31 14:32  李成果  阅读(431)  评论(0编辑  收藏  举报