Linux性能分析及优化--网络

发布于 2017-12-26 · 本文总共 11509 字 · 阅读大约需要 33 分钟

Linux 网络收发流程

网络包的处理非常复杂。所以,网卡硬中断只处理最核心的网卡数据读取或发送,而协议栈中的大部分逻辑,都会放到软中断中处理。

网络包的接收流程

当一个网络帧到达网卡后,网卡会通过 DMA 方式,把这个网络包放到收包队列中;然后通过硬中断,告诉中断处理程序已经收到了网络包。

接着,网卡中断处理程序会为网络帧分配内核数据结构(sk_buff),并将其拷贝到 sk_buff 缓冲区中;然后再通过软中断,通知内核收到了新的网络帧。

接下来,内核协议栈从缓冲区中取出网络帧,并通过网络协议栈,从下到上逐层处理这个网络帧。

1.在链路层检查报文的合法性,找出上层协议的类型(比如 IPv4 还是 IPv6), 再去掉帧头、帧尾,然后交给网络层。

2.网络层取出 IP 头,判断网络包下一步的走向,比如是交给上层处理还是转发。 当网络层确认这个包是要发送到本机后,就会取出上层协议的类型(比如 TCP 还是 UDP),去掉 IP 头,再交给传输层处理。

3.传输层取出 TCP 头或者 UDP 头后,根据 < 源 IP、源端口、目的 IP、目的端口 > 四元组作为标识, 找出对应的 Socket,并把数据拷贝到 Socket 的接收缓存中。

4.最后,应用程序就可以使用 Socket 接口,读取到新接收到的数据了。

网络包的发送流程

1.首先,应用程序调用Socket API(比如 sendmsg)发送网络包。

2.由于这是一个系统调用,所以会陷入到内核态的套接字层中。 套接字层会把数据包放到 Socket 发送缓冲区中。

3.接下来,网络协议栈从 Socket 发送缓冲区中,取出数据包; 再按照 TCP/IP 栈,从上到下逐层处理。比如,传输层和网络层,分别为其增加 TCP 头和 IP 头, 执行路由查找确认下一跳的 IP,并按照 MTU 大小进行分片。

4.分片后的网络包,再送到网络接口层,进行物理地址寻址,以找到下一跳的 MAC 地址。 然后添加帧头和帧尾,放到发包队列中。这一切完成后,会有软中断通知驱动程序:发包队列中有新的网络帧需要发送。

5.最后,驱动程序通过DMA,从发包队列中读出网络帧,并通过物理网卡把它发送出去。

总结网卡收发报文的过程

  1. 内核分配一个主内存地址段(DMA缓冲区),网卡设备可以在DMA缓冲区中读写数据

  2. 当来了一个网络包,网卡将网络包写入DMA缓冲区,写完后通知CPU产生硬中断

  3. 硬中断处理程序锁定当前DMA缓冲区,然后将网络包拷贝到另一块内存区,清空并解锁当前DMA缓冲区,然后通知软中断去处理网络包。

当发送数据包时,与上述相反。链路层将数据包封装完毕后,放入网卡的DMA缓冲区,并调用系统硬中断,通知网卡从缓冲区读取并发送数据。

衡量 Linux 的网络性能

性能指标

  • 带宽,表示链路的最大传输速率,单位通常为 b/s (比特 / 秒)。

  • 吞吐量,表示单位时间内成功传输的数据量,单位通常为 b/s(比特 / 秒)或者 B/s(字节 / 秒)。 吞吐量受带宽限制,而吞吐量 / 带宽,也就是该网络的使用率。

  • 延时,表示从网络请求发出后,一直到收到远端响应,所需要的时间延迟。 在不同场景中,这一指标可能会有不同含义。比如,它可以表示,建立连接需要的时间(比如 TCP 握手延时), 或一个数据包往返所需的时间(比如 RTT)。

  • PPS,是 Packet Per Second(包 / 秒)的缩写,表示以网络包为单位的传输速率。 PPS 通常用来评估网络的转发能力,比如硬件交换机,通常可以达到线性转发(即 PPS 可以达到或者接近理论最大值)。 而基于 Linux 服务器的转发,则容易受网络包大小的影响。

除了这些指标,网络的可用性(网络能否正常通信)、并发连接数(TCP 连接数量)、丢包率(丢包百分比)、 重传率(重新传输的网络包比例)等也是常用的性能指标。

网络配置

ifconfig\ip

ip:

 ip -s addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:16:3e:08:01:9e brd ff:ff:ff:ff:ff:ff
    inet 172.18.185.81/20 brd 172.18.191.255 scope global dynamic eth0
       valid_lft 288517359sec preferred_lft 288517359sec
    RX: bytes  packets  errors  dropped overrun mcast   
    4122721285 12768002 0       0       0       0       
    TX: bytes  packets  errors  dropped carrier collsns
    6272842703 9871745  0       0       0       0      

ifconfig

ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.185.81  netmask 255.255.240.0  broadcast 172.18.191.255
        ether 00:16:3e:08:01:9e  txqueuelen 1000  (Ethernet)
        RX packets 12768065  bytes 4122727091 (3.8 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 9871781  bytes 6272851053 (5.8 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

第一,网络接口的状态标志。ifconfig 输出中的 RUNNING ,或 ip 输出中的 LOWER_UP ,都表示物理网络是连通的, 即网卡已经连接到了交换机或者路由器中。如果你看不到它们,通常表示网线被拔掉了。

第二,MTU 的大小。MTU 默认大小是 1500,根据网络架构的不同(比如是否使用了 VXLAN 等叠加网络),你可能需要调大或者调小 MTU 的数值。

第三,网络接口的 IP 地址、子网以及 MAC 地址。这些都是保障网络功能正常工作所必需的,你需要确保配置正确。

第四,网络收发的字节数、包数、错误数以及丢包情况, 特别是 TX 和 RX 部分的 errors、dropped、overruns、carrier 以及 collisions 等指标不为 0 时,通常表示出现了网络 I/O 问题

  • errors 表示发生错误的数据包数,比如校验错误、帧同步错误等;
  • dropped 表示丢弃的数据包数,即数据包已经收到了 Ring Buffer,但因为内存不足等原因丢包;
  • overruns 表示超限数据包数,即网络 I/O 速度过快,导致 Ring Buffer 中的数据包来不及处理(队列满)而导致的丢包;
  • carrier 表示发生 carrirer 错误的数据包数,比如双工模式不匹配、物理电缆出现问题等;
  • collisions 表示碰撞数据包数。

套接字信息

netstat/ss

ss:

# -l 表示只显示监听套接字
# -t 表示只显示 TCP 套接字
# -n 表示显示数字地址和端口(而不是名字)
# -p 表示显示进程信息
ss -ltnp | head -n 3
State      Recv-Q Send-Q Local Address:Port               Peer Address:Port              
LISTEN     0      128          *:80                       *:*                   users:(("jekyll",pid=21810,fd=8))
LISTEN     0      128          *:22                       *:*                   users:(("sshd",pid=23471,fd=3))

netstat:

# -l 表示只显示监听套接字
# -n 表示显示数字地址和端口(而不是名字)
# -p 表示显示进程信息
netstat -nlp | head -n 3
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      21810/ruby

当套接字处于连接状态(Established)时,

  • Recv-Q 表示套接字缓冲还没有被应用程序取走的字节数(即接收队列长度)。
  • Send-Q 表示还没有被远端主机确认的字节数(即发送队列长度)。

当套接字处于监听状态(Listening)时,

  • Recv-Q 表示全连接队列的长度。
  • Send-Q 表示全连接队列的最大长度。

全连接:

是指服务器收到了客户端的 ACK,完成了 TCP 三次握手,然后就会把这个连接挪到全连接队列中。 这些全连接中的套接字,还需要被 accept() 系统调用取走,服务器才可以开始真正处理客户端的请求。

半连接:

是指还没有完成 TCP 三次握手的连接,连接只进行了一半。 服务器收到了客户端的 SYN 包后,就会把这个连接放到半连接队列中,然后再向客户端发送 SYN+ACK 包。

协议栈统计信息

ss -s

ss 只显示已经连接、关闭、孤儿套接字等简要统计,而 netstat 则提供的是更详细的网络协议栈信息。

Total: 124 (kernel 180)
TCP:   11 (estab 2, closed 3, orphaned 0, synrecv 0, timewait 0/0), ports 0

Transport Total     IP        IPv6
*         180       -         -        
RAW       0         0         0        
UDP       3         2         1        
TCP       8         4         4        
INET      11        6         5        
FRAG      0         0         0    

netstat -s

Tcp:
    88273 active connections openings
    87754 passive connection openings
    616284 failed connection attempts
    7539 connection resets received
    2 connections established
    10395954 segments received
    9034554 segments send out
    1390202 segments retransmited
    745512 bad segments received.
    605861 resets sent
    InCsumErrors: 744477

网络吞吐和 PPS

sar 增加 -n 参数就可以查看网络的统计信息,比如网络接口(DEV)、网络接口错误(EDEV)、TCP、UDP、ICMP 等等。 执行下面的命令,你就可以得到网络接口统计信息:

sar -n DEV 1
Linux 3.10.0-957.21.3.el7.x86_64 (qwq)  05/25/2020      _x86_64_        (1 CPU)

05:45:23 PM     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s
05:45:24 PM      eth0      2.00      2.00      0.12      0.32      0.00      0.00      0.00
05:45:24 PM        lo      0.00      0.00      0.00      0.00      0.00      0.00      0.00
05:45:24 PM   docker0      0.00      0.00      0.00      0.00      0.00      0.00      0.00

rxpck/s 和 txpck/s 分别是接收和发送的 PPS,单位为包 / 秒。

rxkB/s 和 txkB/s 分别是接收和发送的吞吐量,单位是 KB/ 秒。

rxcmp/s 和 txcmp/s 分别是接收和发送的压缩数据包数,单位是包 / 秒。

%ifutil 是网络接口的使用率,即半双工模式下为 (rxkB/s+txkB/s)/Bandwidth, 而全双工模式下为 max(rxkB/s, txkB/s)/Bandwidth。

连通性和延时

ping

C10K

在 C10K 以前,Linux 中网络处理都用同步阻塞的方式,也就是每个请求都分配一个进程或者线程。 请求数只有 100 个时,这种方式自然没问题, 但增加到 10000 个请求时,10000 个进程或线程的调度、上下文切换乃至它们占用的内存,都会成为瓶颈。

每个请求分配一个线程的方式不合适;

1.怎样在一个线程内处理多个请求,也就是要在一个线程内响应多个网络 I/O。

2.怎么更节省资源地处理客户请求,也就是要用更少的线程来服务这些请求。 是不是可以继续用原来的 100 个或者更少的线程,来服务现在的 10000 个请求呢?

I/O 模型优化

两种 I/O 事件通知的方式:
水平触发和边缘触发,它们常用在套接字接口的文件描述符中。

1.水平触发:
只要文件描述符可以非阻塞地执行 I/O ,就会触发通知。 也就是说,应用程序可以随时检查文件描述符的状态, 然后再根据状态,进行I/O 操作。

2.边缘触发:
只有在文件描述符的状态发生改变(也就是 I/O 请求达到)时,才发送一次通知。 这时候,应用程序需要尽可能多地执行 I/O,直到无法继续读写,才可以停止。 如果I/O 没执行完,或者因为某种原因没来得及处理,那么这次通知也就丢失了。

I/O 多路复用的方法:

1.使用非阻塞 I/O 和水平触发通知,比如使用 select 或者 poll。

select和poll需要从文件描述符列表中,找出哪些可以执行I/O,然后进行真正的网络 I/O 读写。 由于I/O是非阻塞的,一个线程中就可以同时监控一批套接字的文件描述符,这样就达到了单线程处理多请求的目的。

这种方式的最大优点,是对应用程序比较友好,它的API非常简单。 但是,应用软件使用select和poll时,需要对这些文件描述符列表进行轮询,这样,请求数多的时候就会比较耗时。 并且,select 和 poll 还有一些其他的限制。

select使用固定长度的位相量,表示文件描述符的集合,因此会有最大描述符数量的限制。

poll改进了select的表示方法,换成了一个没有固定长度的数组, 这样就没有了最大描述符数量的限制(当然还会受到系统文件描述符限制)。 但应用程序在使用poll时,同样需要对文件描述符列表进行轮询,这样,处理耗时跟描述符数量就是O(N) 的关系。

应用程序每次调用select和poll时,还需要把文件描述符的集合, 从用户空间传入内核空间,由内核修改后,再传出到用户空间中。 这一来一回的内核空间与用户空间切换,也增加了处理成本。

2.使用非阻塞I/O和边缘触发通知,比如 epoll。

epoll使用红黑树,在内核中管理文件描述符的集合,这样,就不需要应用程序在每次操作时都传入、传出这个集合。

epoll使用事件驱动的机制,只关注有I/O事件发生的文件描述符,不需要轮询扫描整个集合。

3.使用异步I/O(Asynchronous I/O,简称为 AIO)

异步I/O允许应用程序同时发起很多I/O 操作,而不用等待这些操作完成。 而在I/O 完成后,系统会用事件通知(比如信号或者回调函数)的方式,告诉应用程序。 这时,应用程序才会去查询 I/O 操作的结果。

工作模型优化

使用I/O多路复用后,就可以在一个进程或线程中处理多个请求,其中,又有下面两种不同的工作模型。

1.主进程 + 多个 worker 子进程,这也是最常用的一种模型。(Nginx)

  • 主进程执行 bind() + listen() 后,创建多个子进程;

  • 然后,在每个子进程中,都通过 accept() 或 epoll_wait() ,来处理相同的套接字。

accept() 和 epoll_wait() 调用,还存在一个惊群的问题。 换句话说,当网络 I/O 事件发生时,多个进程被同时唤醒, 但实际上只有一个进程来响应这个事件,其他被唤醒的进程都会重新休眠。 其中,accept() 的惊群问题,已经在 Linux 2.6 中解决了; 而 epoll 的问题,到了 Linux 4.5 ,才通过 EPOLLEXCLUSIVE 解决。

为了避免惊群问题, Nginx 在每个 worker 进程中,都增加一个了全局锁(accept_mutex)。 这些 worker 进程需要首先竞争到锁,只有竞争到锁的进程,才会加入到 epoll 中, 这样就确保只有一个 worker 子进程被唤醒。

  • 进程的管理、调度、上下文切换的成本非常高。那为什么使用多进程模式的Nginx,却具有非常好的性能呢?

这些 worker 进程,实际上并不需要经常创建和销毁,而是在没任务时休眠, 有任务时唤醒。 只有在worker由于某些异常退出时,主进程才需要创建新的进程来代替它。

2.监听到相同端口的多进程模型。(Nginx 1.9.1)

所有的进程都监听相同的接口,并且开启SO_REUSEPORT选项, 由内核负责将请求负载均衡到这些监听进程中去。

C1000K

从物理资源使用上来说,100 万个请求需要大量的系统资源。

比如,假设每个请求需要 16KB 内存的话,那么总共就需要大约 15 GB 内存。 而从带宽上来说,假设只有 20% 活跃连接,即使每个连接只需要 1KB/s 的吞吐量,总共也需要 1.6 Gb/s 的吞吐量。千兆网卡显然满足不了这么大的吞吐量,所以还需要配置万兆网卡,或者基于多网卡 Bonding 承载更大的吞吐量。

从软件资源上来说,大量的连接也会占用大量的软件资源, 比如文件描述符的数量、连接状态的跟踪(CONNTRACK)、 网络协议栈的缓存大小(比如套接字读写缓存、TCP 读写缓存)等等

大量请求带来的中断处理,也会带来非常高的处理成本。 这样,就需要多队列网卡、中断负载均衡、CPU 绑定、RPS/RFS(软中断负载均衡到多个 CPU 核上), 以及将网络包的处理卸载(Offload)到网络设备(如 TSO/GSO、LRO/GRO、VXLAN OFFLOAD)等各种硬件和软件的优化。

C1000K 的解决方法,本质上还是构建在 epoll 的非阻塞 I/O 模型上。只不过,除了 I/O 模型之外,还需要从应用程序到 Linux 内核、再到 CPU、内存和网络等各个层次的深度优化,特别是需要借助硬件,来卸载那些原来通过软件处理的大量功能。

C10M

在 C1000K 问题中,各种软件、硬件的优化很可能都已经做到头了。特别是当升级完硬件(比如足够多的内存、带宽足够大的网卡、更多的网络功能卸载等)后,你可能会发现,无论你怎么优化应用程序和内核中的各种网络参数,想实现 1000 万请求的并发,都是极其困难的。

Linux 内核协议栈做了太多太繁重的工作。从网卡中断带来的硬中断处理程序开始,到软中断中的各层网络协议处理,最后再到应用程序,这个路径实在是太长了,就会导致网络包的处理优化,到了一定程度后,就无法更进一步了。

跳过内核协议栈的冗长路径,把网络包直接送到要处理的应用程序那里去。这里有两种常见的机制,DPDK 和 XDP。

1.DPDK,是用户态网络的标准。

它跳过内核协议栈,直接由用户态进程通过轮询的方式,来处理网络接收。

2.XDP(eXpress Data Path),

则是Linux内核提供的一种高性能网络数据路径。 它允许网络包,在进入内核协议栈之前,就进行处理,也可以带来更高的性能。 XDP底层跟bcc-tools一样,都是基于 Linux内核的eBPF 机制实现的。

XDP对内核的要求比较高,需要的是Linux 4.8以上版本,并且它也不提供缓存队列。 基于XDP的应用程序通常是专用的网络应用,常见的有IDS(入侵检测系统)、DDoS 防御、 cilium容器网络插件等。

总结

C10K —- epoll

C10K 到 C100K ,可能只需要增加系统的物理资源就可以满足

C100K到C1000K ,就不仅仅是增加物理资源就能解决的问题了。这时,就需要多方面的优化工作了, 从硬件的中断处理和网络功能卸载、到网络协议栈的文件描述符数量、连接状态跟踪、缓存队列等内核的优化, 再到应用程序的工作模型优化,都是考虑的重点。

C10M,就不只是增加物理资源,或者优化内核和应用程序可以解决的问题了。 这时候,就需要用XDP的方式,在内核协议栈之前处理网络包; 或者用DPDK直接跳过网络协议栈,在用户空间通过轮询的方式直接处理网络包。

优化网络性能

性能评估是优化网络性能的前提,只有在你发现网络性能瓶颈时,才需要进行网络性能优化。 根据 TCP/IP 协议栈的原理,不同协议层关注的性能重点不完全一样,也就对应不同的性能测试方法。 比如,在应用层,你可以使用 wrk、Jmeter 等模拟用户的负载,测试应用程序的每秒请求数、处理延迟、错误数等; 而在传输层,则可以使用 iperf 等工具,测试 TCP 的吞吐情况; 再向下,你还可以用 Linux 内核自带的 pktgen ,测试服务器的 PPS。 由于低层协议是高层协议的基础。所以,一般情况下,需要从上到下,对每个协议层进行性能测试,然后根据性能测试的结果, 结合 Linux 网络协议栈的原理,找出导致性能瓶颈的根源,进而优化网络性能。

网络性能测试

  • 在应用层

应用层,最需要关注的是吞吐量(BPS)、每秒请求数以及延迟等指标。

用 wrk、ab 等工具,来测试应用程序的性能。

你可以使用 wrk、Jmeter 等模拟用户的负载,测试应用程序的每秒请求数、处理延迟、错误数等;

  • 传输层

传输层的 TCP 和 UDP,它们主要负责网络传输。对它们而言,吞吐量(BPS)、连接数以及延迟,就是最重要的性能指标。

可以用 iperf 或 netperf ,来测试传输层的性能。

则可以使用 iperf 等工具,测试 TCP 的吞吐情况;

  • 网络接口层和网络层

它们主要负责网络包的封装、寻址、路由,以及发送和接收。每秒可处理的网络包数 PPS,就是它们最重要的性能指标(特别是在小包的情况下) 再向下,你还可以用 Linux 内核自带的 pktgen ,测试服务器的 PPS。

网络性能优化的基本思路

应用程序

对网络I/O和进程自身的工作模型的优化

从网络I/O的角度来说,主要有下面两种优化思路:

1.最常用的 I/O 多路复用技术epoll,主要用来取代select和poll。 这其实是解决C10K 问题的关键,也是目前很多网络应用默认使用的机制。

2.使用异步 I/O(Asynchronous I/O,AIO)。 AIO 允许应用程序同时发起很多I/O操作,而不用等待这些操作完成。 等到I/O完成后,系统会用事件通知的方式,告诉应用程序结果。 不过,AIO 的使用比较复杂,你需要小心处理很多边缘情况。

从进程的工作模型来说,也有两种不同的模型用来优化:

1.主进程 + 多个worker子进程。 其中,主进程负责管理网络连接,而子进程负责实际的业务处理。这也是最常用的一种模型。

2.监听到相同端口的多进程模型。 在这种模型下,所有进程都会监听相同接口,并且开启SO_REUSEPORT 选项,由内核负责,把请求负载均衡到这些监听进程中去。

应用层的网络协议优化:

1.使用长连接取代短连接,可以显著降低TCP 建立连接的成本。在每秒请求次数较多时,这样做的效果非常明显。

2.使用内存等方式,来缓存不常变化的数据,可以降低网络I/O次数,同时加快应用程序的响应速度。

3.使用Protocol Buffer等序列化的方式,压缩网络I/O的数据量,可以提高应用程序的吞吐。

4.使用DNS 缓存、预取、HTTPDNS 等方式,减少DNS解析的延迟,也可以提升网络I/O的整体速度。

套接字

套接字可以屏蔽掉Linux内核中不同协议的差异,为应用程序提供统一的访问接口。每个套接字,都有一个读写缓冲区。

  • 读缓冲区

缓存了远端发过来的数据。如果读缓冲区已满,就不能再接收新的数据。

  • 写缓冲区

缓存了要发出去的数据。如果写缓冲区已满,应用程序的写操作就会被阻塞。

调整这些缓冲区的大小: 增大每个套接字的缓冲区大小 net.core.optmem_max;

增大套接字接收缓冲区大小net.core.rmem_max 和发送缓冲区大小 net.core.wmem_max;

增大TCP接收缓冲区大小net.ipv4.tcp_rmem 和发送缓冲区大小 net.ipv4.tcp_wmem。

套接字接口还提供了一些配置选项,用来修改网络连接的行为:

为 TCP 连接设置TCP_NODELAY后,就可以禁用 Nagle 算法;

为 TCP 连接开启TCP_CORK后,可以让小包聚合成大包后再发送(注意会阻塞小包的发送);

使用 SO_SNDBUF 和 SO_RCVBUF ,可以分别调整套接字发送缓冲区和接收缓冲区的大小。

传输层

TCP

1.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 和 fs.file-max , 分别增大进程和系统的最大文件描述符数;或在应用程序的 systemd 配置文件中, 配置 LimitNOFILE ,设置应用程序的最大文件描述符数。

2。缓解SYN FLOOD?

增大 TCP 半连接的最大数量 net.ipv4.tcp_max_syn_backlog , 或者开启 TCP SYN Cookies net.ipv4.tcp_syncookies , 来绕开半连接数量限制的问题(注意,这两个选项不可同时使用)。

减少 SYN_RECV 状态的连接重传 SYN+ACK 包的次数 net.ipv4.tcp_synack_retries。

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

缩短最后一次数据包到 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_time;

缩短发送 Keepalive 探测包的间隔时间 net.ipv4.tcp_keepalive_intvl;

减少 Keepalive 探测失败后,一直到通知应用程序前的重试次数 net.ipv4.tcp_keepalive_probes。

服务器端开启 Nagle 算法,而客户端开启延迟确认机制,就很容易导致网络延迟增大。

UDP

跟上篇套接字部分提到的一样,增大套接字缓冲区大小以及 UDP 缓冲区范围;

跟前面 TCP 部分提到的一样,增大本地端口号的范围;

根据 MTU 大小,调整 UDP 数据包的大小,减少或者避免分片的发生。

网络层

路由转发

在需要转发的服务器中,比如用作 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协议,即设置net.ipv4.icmp_echo_ignore_all = 1。 这样,外部主机就无法通过ICMP来探测主机。

或者,你还可以禁止广播ICMP,即设置net.ipv4.icmp_echo_ignore_broadcasts = 1。

链路层

总结

在应用程序中,主要是优化 I/O 模型、工作模型以及应用层的网络协议;

在套接字层中,主要是优化套接字的缓冲区大小;

在传输层中,主要是优化 TCP 和 UDP 协议;

在网络层中,主要是优化路由、转发、分片以及 ICMP 协议;

最后,在链路层中,主要是优化网络包的收发、网络功能卸载以及网卡选项。




本博客所有文章采用的授权方式为 自由转载-非商用-非衍生-保持署名 ,转载请务必注明出处,谢谢。
声明:
本博客欢迎转发,但请保留原作者信息!
博客地址:邱文奇(qiuwenqi)的博客;
内容系本人学习、研究和总结,如有雷同,实属荣幸!
阅读次数:

文章评论

comments powered by Disqus


章节列表