Linux性能优化实战学习笔记:第四十四讲

1、上节回顾

上一节,咱们学了网络性能优化的几个思路,我先带你简单复习一下。算法

在优化网络的性能时,你能够结合 Linux 系统的网络协议栈和网络收发流程,而后从应用程序、套接字、传输层、网络层再到链路层等每一个层次,进行逐层优化。上一期咱们主要
学习了应用程序和套接字的优化思路,好比:缓存

  • 在应用程序中,主要优化 I/O 模型、工做模型以及应用层的网络协议;
  • 在套接字层中,主要优化套接字的缓冲区大小。

今天,咱们顺着 TCP/IP 网络模型,继续向下,看看如何从传输层、网络层以及链路层中,优化 Linux 网络性能。性能优化

2、网络性能优化-传输层

传输层最重要的是 TCP 和 UDP 协议,因此这儿的优化,其实主要就是对这两种协议的优化。服务器

咱们首先来看 TCP 协议的优化。cookie

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

关于这些原理的细节,我就再也不展开讲解了。若是你尚未彻底掌握,建议你先学完这些基本原理后再来优化,而不是囫囵吞枣地乱抄乱试。并发

掌握这些原理后,你就能够在不破坏 TCP 正常工做的基础上,对它进行优化。下面,我分几类状况详细说明。tcp

第一类,在请求数比较大的场景下,你可能会看到大量处于 TIME_WAIT 状态的链接,它们会占用大量内存和端口资源。这时,咱们能够优化与 TIME_WAIT 状态相关的内核选
项,好比采起下面几种措施。ide

  • 增大处于 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 ,设置应用程序的最大文件描述符数。

第二类,为了缓解 SYN FLOOD 等,利用 TCP 协议特色进行攻击而引起的性能问题,你能够考虑优化与 SYN 状态相关的内核选项,好比采起下面几种措施。工具

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

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

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

讲了这么多 TCP 优化方法,我也把它们整理成了一个表格,方便你在须要时参考(数值仅供参考,具体配置还要结合你的实际场景来调整):

优化 TCP 性能时,你还要注意,若是同时使用不一样优化方法,可能会产生冲突。

好比,就像网络请求延迟的案例中咱们曾经分析过的,服务器端开启 Nagle 算法,而客户端开启延迟确认机制,就很容易致使网络延迟增大。

另外,在使用 NAT 的服务器上,若是开启 net.ipv4.tcp_tw_recycle ,就很容易致使各类链接失败。实际上,因为坑太多,这个选项在内核的 4.1 版本中已经删除了。
说完 TCP,咱们再来看 UDP 的优化。

UDP 提供了面向数据报的网络协议,它不须要网络链接,也不提供可靠性保障。因此,UDP 优化,相对于 TCP 来讲,要简单得多。这里我也总结了常见的几种优化方案。

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

3、网络性能优化-网络层

接下来,咱们再来看网络层的优化。

网络层,负责网络包的封装、寻址和路由,包括 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 问题。

第二种,从分片的角度出发,最主要的是调整 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。

4、网络性能优化-链路层

网络层的下面是链路层,因此最后,咱们再来看链路层的优化方法。

链路层负责网络包在物理网络中的传输,好比 MAC 寻址、错误侦测以及经过网卡传输网络帧等。天然,链路层的优化,也是围绕这些基本功能进行的。接下来,咱们从不一样的几
个方面分别来看。

将这些中断处理程序调度到不一样的 CPU 上执行

因为网卡收包后调用的中断处理程序(特别是软中断),须要消耗大量的 CPU。因此,将这些中断处理程序调度到不一样的 CPU 上执行,就能够显著提升网络吞吐量。这一般能够采
用下面两种方法。

好比,你能够为网卡硬中断配置 CPU 亲和性(smp_affinity),或者开启 irqbalance服务。

再如,你能够开启 RPS(Receive Packet Steering)和 RFS(Receive FlowSteering),将应用程序和软中断的处理,调度到相同 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 技术,在网络包进入内核协议栈前,就对其进行处理,这样也能够实现很好的性能。

5、小结

这两节课,咱们一块儿梳理了常见的 Linux 网络性能优化方法。

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

实际上,咱们分析和定位网络瓶颈,也是基于这些网络层进行的。而定位出网络性能瓶颈后,咱们就能够根据瓶颈所在的协议层,进行优化。具体而言:

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

若是这些方法依然不能知足你的要求,那就能够考虑,使用 DPDK 等用户态方式,绕过内核协议栈;或者,使用 XDP,在网络包进入内核协议栈前进行处理。