44-套路篇:网络性能优化的几个思路(下)
网络性能优化
传输层
传输层最重要的是TCP和UDP协议,所以这儿的优化,其实主要就是对这两种协议的优化
TCP协议的优化
TCP提供了面向连接的可靠传输服务
要优化TCP首先要掌握TCP协议的基本原理
比如流量控制、慢启动、拥塞避免、延迟确认以及状态流图(如下图所示)等
分几类情况详细说明
-
第一类,在请求数比较大的场景下,可能会看到大量处于TIME_WAIT状态的连接
它们会占用大量内存和端口资源
这时,可以优化与TIME_WAIT状态相关的内核选项,比如采取下面几种措施- 增大处于TIME_WAIT状态的连接数量net.ipv4.tcp_max_tw_buckets
并增大连接跟踪表的大小net.netfilter.nf_conntrack_max - 减小net.ipv4.tcp_fin_timeout和net.netfilter.nf_conntrack_tcp_timeout_time_wait
让系统尽快释放它们所占用的资源 - 开启端口复用net.ipv4.tcp_tw_reuse
这样被TIME_WAIT状态占用的端口,还能用到新建的连接中 - 增大本地端口的范围net.ipv4.ip_local_port_range
这样就可以支持更多连接,提高整体的并发能力 - 增加最大文件描述符的数量
可以使用fs.nr_open ,设置系统的最大文件描述符数
或在应用程序的systemd配置文件中,配置LimitNOFILE,设置应用程序的最大文件描述符数
- 增大处于TIME_WAIT状态的连接数量net.ipv4.tcp_max_tw_buckets
-
第二类,为了缓解SYN FLOOD等
利用TCP协议特点进行攻击而引发的性能问题,可以考虑优化与SYN状态相关的内核选项
比如采取下面几种措施- 增大TCP半连接的最大数量net.ipv4.tcp_max_syn_backlog
或者开启TCP SYN Cookies net.ipv4.tcp_syncookies
来绕开半连接数量限制的问题(注意这两个选项不可同时使用) - 减少SYN_RECV状态的连接重传SYN+ACK包的次数net.ipv4.tcp_synack_retries
- 增大TCP半连接的最大数量net.ipv4.tcp_max_syn_backlog
-
第三类,在长连接的场景中,通常使用Keepalive来检测TCP连接的状态
以便对端连接断开后,可以自动回收
但是系统默认的Keepalive探测间隔和重试次数,一般都无法满足应用程序的性能要求
所以这时候需要优化与Keepalive相关的内核选项,比如- 缩短最后一次数据包到Keepalive探测包的间隔时间net.ipv4.tcp_keepalive_time
- 缩短发送Keepalive探测包的间隔时间net.ipv4.tcp_keepalive_intvl
- 减少Keepalive探测失败后,一直到通知应用程序前的重试次数net.ipv4.tcp_keepalive_probes
优化TCP性能时,还要注意,如果同时使用不同优化方法,可能会产生冲突
比如就像网络请求延迟案例中,服务器端开启Nagle算法,客户端开启延迟确认机制,就很容易导致网络延迟增大
另外在使用NAT的服务器上,如果开启net.ipv4.tcp_tw_recycle ,就很容易导致各种连接失败
实际上,由于坑太多,这个选项在内核的4.1版本中已经删除了
UDP协议的优化
UDP提供了面向数据报的网络协议,它不需要网络连接,也不提供可靠性保障
所以UDP 优化,相对于 TCP 来说,要简单得多
- 跟上篇套接字部分提到的一样,增大套接字缓冲区大小以及UDP缓冲区范围
- 跟前面TCP部分提到的一样,增大本地端口号的范围
- 根据MTU大小,调整UDP数据包的大小,减少或者避免分片的发生
网络层
网络层负责网络包的封装、寻址和路由,包括IP、ICMP等常见协议
在网络层最主要的优化,其实就是对路由、 IP分片以及ICMP等进行调优
-
第一种从路由和转发的角度出发,可以调整下面的内核选项
- 在需要转发的服务器中,比如用作NAT网关的服务器或者使用Docker容器时
开启IP转发,即设置net.ipv4.ip_forward = 1 - 调整数据包的生存周期TTL,比如设置net.ipv4.ip_default_ttl = 64
注意增大该值会降低系统性能 - 开启数据包的反向地址校验,比如设置net.ipv4.conf.eth0.rp_filter = 1
这样可以防止IP欺骗,并减少伪造IP带来的DDoS问题
- 在需要转发的服务器中,比如用作NAT网关的服务器或者使用Docker容器时
-
第二种,从分片的角度出发,最主要的是调整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, 以提高网络吞吐量
-
第三种,从ICMP的角度出发,为了避免ICMP主机探测、ICMP Flood等各种网络问题
可以通过内核选项,来限制ICMP的行为- 禁止ICMP协议,即设置net.ipv4.icmp_echo_ignore_all = 1
这样,外部主机就无法通过ICMP来探测主机 - 禁止广播ICMP,即设置net.ipv4.icmp_echo_ignore_broadcasts = 1
- 禁止ICMP协议,即设置net.ipv4.icmp_echo_ignore_all = 1
链路层
网络层的下面是链路层,所以最后再来看链路层的优化方法
链路层负责网络包在物理网络中的传输,比如MAC寻址、错误侦测以及通过网卡传输网络帧等
自然链路层的优化,也是围绕这些基本功能进行的
由于网卡收包后调用的中断处理程序(特别是软中断),需要消耗大量的CPU
所以将这些中断处理程序调度到不同的CPU上执行,就可以显著提高网络吞吐量
这通常可以采 用下面两种方法
- 可以为网卡硬中断配置CPU亲和性(smp_affinity),或者开启irqbalance服务
- 可以开启RPS(Receive Packet Steering)和RFS(Receive Flow Steering)
将应用程序和软中断的处理调度到相同CPU上,这样就可以增加CPU缓存命中率,减少网络延迟
另外现在的网卡都有很丰富的功能,原来在内核中通过软件处理的功能,可以卸载到网卡中,通过硬件来执行
- TSO(TCP Segmentation Offload)和UFO(UDP Fragmentation Offload)
在TCP/UDP协议中直接发送大包
而TCP包的分段(按照MSS分段)和UDP的分片 (按照MTU分片)功能,由网卡来完成 - GSO(Generic Segmentation Offload)
在网卡不支持TSO/UFO时,将TCP/UDP包的分段,延迟到进入网卡前再执行
这样,不仅可以减少CPU的消耗,还可以在发生丢包时只重传分段后的包 - LRO(Large Receive Offload)
在接收TCP分段包时,由网卡将其组装合并后,再交给上层网络处理
不过要注意,在需要IP转发的情况下,不能开启LRO
因为如果多个包的头部信息不一致,LRO合并会导致网络包的校验错误 - GRO(Generic Receive Offload)
GRO修复了LRO的缺陷,并且更为通用,同时支持TCP和UDP - RSS(Receive Side Scaling)
也称为多队列接收,它基于硬件的多个接收队列,来分配网络接收进程,这样可以让多个 CPU 来处理接收到的网络包 - VXLAN 卸载
也就是让网卡来完成VXLAN的组包功能
最后对于网络接口本身,也有很多方法,可以优化网络的吞吐量
- 可以开启网络接口的多队列功能
这样每个队列就可以用不同的中断号,调度到不同CPU上执行,从而提升网络的吞吐量 - 可以增大网络接口的缓冲区大小,以及队列长度等,提升网络传输的吞吐量 (注意,这可能导致延迟增大)
- 可以使用Traffic Control工具,为不同网络流量配置QoS
到这里就从应用程序、套接字、传输层、网络层,再到链路层,分别介绍了相应的网络性能优化方法
通过这些方法的优化后,网络性能就可以满足绝大部分场景了
最后别忘了一种极限场景,C10M问题吗?
在单机并发1000万的场景中,对Linux网络协议栈进行的各种优化策略,基本都没有太大效果
因为这种情况下,网络协议栈的冗长流程,其实才是最主要的性能负担
这时可以用两种方式来优化
- 第一种,使用 DPDK 技术,跳过内核协议栈,直接由用户态进程用轮询的方式,来处理网络请求
同时,再结合大页、CPU绑定、内存对齐、流水线并发等多种机制,优化网络包的处理效率 - 第二种,使用内核自带的XDP技术,在网络包进入内核协议栈前,就对其进行处理
这样也可以实现很好的性能
小结
在优化网络的性能时
可以结合Linux系统的网络协议栈和网络收发流程,从应用程 序、套接字、传输层、网络层再到链路层等
对每个层次进行逐层优化
实际上分析和定位网络瓶颈,也是基于这些网络层进行的
而定位出网络性能瓶颈后,就可以根据瓶颈所在的协议层,进行优化
- 在应用程序中,主要是优化I/O模型、工作模型以及应用层的网络协议
- 在套接字层中,主要是优化套接字的缓冲区大小
- 在传输层中,主要是优化TCP和UDP协议
- 在网络层中,主要是优化路由、转发、分片以及ICMP协议
- 在链路层中,主要是优化网络包的收发、网络功能卸载以及网卡选项
如果这些方法依然不能满足要求,那就可以考虑,使用DPDK等用户态方式,绕过内核协议栈
或者,使用XDP,在网络包进入内核协议栈前进行处理