Linux 性能优化思路–套路篇

服务器性能优化,是我以前一提起来就发憷的东西,觉的很泛。不知道该怎么搞,搞哪些 看着群里的大神们都侃侃而谈。我就在某客学习了一些套路,总结下来。用以备忘。

1 确定优化目标

实际上,虽然网络性能优化的整体目标,是降低网络延迟(如 RTT)和提高吞吐量(如 BPS 和 PPS),但具体到不同应用中,每个指标的优化标准可能会不同,优先级顺序也大相径庭。

数据库,缓存系统,快速完成网络收发,低延迟,是主要性能目标。

web服务,需要同时兼顾吞吐量和延迟

image.png

明白了这一点,在进行基准测试时,我们就可以按照协议栈的每一层来测试。由于底层是其上方各层的基础,底层性能也就决定了高层性能。所以我们要清楚,底层性能指标,其实就是对应高层的极限性能。我们从下到上来理解这一点。

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

再向上到传输层的 TCP 和 UDP,它们主要负责网络传输。对它们而言,吞吐量(BPS)、连接数以及延迟,就是最重要的性能指标。你可以用 iperf 或 netperf ,来测试传输层的性能。

不过要注意,网络包的大小,会直接影响这些指标的值。所以,通常,你需要测试一系列不同大小网络包的性能。

最后,再往上到了应用层,最需要关注的是吞吐量(BPS)、每秒请求数以及延迟等指标。你可以用 wrk、ab 等工具,来测试应用程序的性能。

image.png

image.png

2 应用程序

从网络I/O的角度来说:

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

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

从进程的工作模型来说:

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

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

应用层的网络协议优化:

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

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

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

使用 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。

image.png

tcp_rmem 和 tcp_wmem 的三个数值分别是 min,default,max,系统会根据这些设置,自动调整 TCP 接收 / 发送缓冲区的大小。

udp_mem 的三个数值分别是 min,pressure,max,系统会根据这些设置,自动调整 UDP 发送缓冲区的大小。

当然,表格中的数值只提供参考价值,具体应该设置多少,还需要你根据实际的网络状况来确定。比如,发送缓冲区大小,理想数值是吞吐量 * 延迟,这样才可以达到最大网络利用率。

为TCP连接设置TCP_NODELAY,就可以禁用Nagle(纳格算法)

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

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

3 传输层

image.png

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

开启端口复用 net.ipv4.tcp_tw_reuse。这样,被 TIME_WAIT 状态占用的端口,还能用到新建的连接中。

查看方式: 

sysctl -a |grep tcp_tw_reuse 默认是 0

修改方式

sysctl -w net.ipv4.tcp_tw_reuse = 1

2 增大处于TIME_WAIT 状态的连接数量

net.ipv4.tcp_max_tw_buckets :增大处于TIME_WAIT 状态的连接数量

sysctl -a |grep tcp_max_tw_buckets 查看方式,默认值是 4096

建议修改值:

sysctl -w net.ipv4.tcp_max_tw_buckets = 1048576

缩短处于TIME_WAIT状态的超时时间

sysctl -a |grep net.ipv4.tcp_fin_timeout

默认是 60 参考值 是15s

3 增大连接跟踪表的大小

sysctl -a | grep net.netfilter.nf_conntrack_max

31800

增大方式

sysctl -w net.netfilter.nf_conntrack_max = 1048576

4 增大本地端口的范围 net.ipv4.ip_local_port_range ,

根据网络套接字的原理,当客户端连接服务器端时,需要分配一个临时端口号,而  Nginx 正是 PHP-FPM 的客户端。端口号的范围并不是无限的,最多也只有 6 万多。

sysctl -a |grep net.ipv4.ip_local_port_range 默认是 32768 - 61000

net.ipv4.ip_local_port_range = 32768 61000

增大本地端口范围,可以支持更多连接,提高整体的并发能力

增大本地端口的范围

sysctl -w net.ipv4.ip_local_port_range = "10000 65535"

5 增加最大文件描述符的数量

可以打开最大文件描述符的数量:ulimit -n  默认是 1024个文件描述符

(1024

这表示当前用户的每个进程最多允许同时打开1024个文件。

这1024个文件中还得除去每个进程必然打开的标准输入,

标准输出,标准错误,服务器监听 socket,进程间通讯的unix域socket等文件,

那么剩下的可用于客户端socket连接的文件数就只有大概1024-10=1014个左右。

也就是说缺省情况下,基于Linux的通讯程序最多

允许同时1014个TCP并发连接)

临时修改  ulimit -n 4086 

永久修改: 具体格式参考 /etc/security/limits.conf,里面有详细说明

fs.nr_open 

NR_OPEN是一个进程可以打开的最大文件数

sysctl -a  |grep fs.nr_open

fs.nr_open = 1048576

修改方式:

sysctl -w fs.nr_open = 1048576

系统最大文件描述符数:

fs.file-max

sysctl -a  |grep fs.file-max

fs.file-max = 99906

修改方式:

sysctl -w  fs.file-max=99907

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

增大 TCP 半连接的最大数量 net.ipv4.tcp_max_syn_backlog ,

sysctl -a |grep net.ipv4.tcp_max_syn_backlog

net.ipv4.tcp_max_syn_backlog = 128 默认128

sysctl -w net.ipv4.tcp_max_syn_backlog=1024

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

sysctl -a |grep net.ipv4.tcp_syncookies 默认是 0 

sysctl -w net.ipv4.tcp_syncookies=1

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

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

查看方法 sysctl -a |grep net.ipv4.tcp_keepalive_.*

net.ipv4.tcp_keepalive_intvl = 75 //:探测消息发送的频率,每75s发送一次。建议值 30

sysctl -w net.ipv4.tcp_keepalive_intvl=30

net.ipv4.tcp_keepalive_probes = 9 // 探测数量 建议值 3

sysctl -w net.ipv4.tcp_keepalive_probes=9

net.ipv4.tcp_keepalive_time = 7200 //tcp 发送keepalive的消息频率 建议值600 

sysctl -w net.ipv4.tcp_keepalive_time=600

意思是如果某个TCP连接在idle 2个小时后,内核才发起probe.如果probe 9次(每次75秒)不成功,内核才彻底放弃,认为该连接已失效.对服务器而言,显然上述值太大

image.png

4 网络层优化

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

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

Linux系统默认是禁止数据包转发的。所谓转发即当主机拥有多于一块的网卡时,其中一块收到数据包,根据数据包的目的ip地址将数据包发往本机另一块网卡,该网卡根据路由表继续发送数据包。这通常是路由器所要实现的功能

1 开启ip转发

net.ipv4.ip_forward = 1

2 增大数据包的生存周期

sysctl -a |grep net.ipv4.ip_default_ttl 默认值是64,这个其实会降低系统性能

3  开启数据包的反向地址校验,防止IP欺骗,减少伪造IP带来的DDOS问题

net.ipv4.conf.eth0.rp_filter = 1

第二种,从分片角度出发,最主要的是调整MTU(maximum transmission unit)的大小

通常,MTU 的大小应该根据以太网的标准来设置。以太网标准规定,一个网络帧最大为 1518B,那么去掉以太网头部的 18B 后,剩余的 1500 就是以太网 MTU 的大小。

查看:cat /sys/class/net/eth0/mtu 默认是 1500

修改: echo "1450" > /sys/class/net/eth0/mtu

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

比如,你可以禁止 ICMP 协议,即设置 net.ipv4.icmp_echo_ignore_all = 1。这样,外部主机就无法通过 ICMP 来探测主机。

设置为1之后,再来ping会显示超时的。

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

5 链路层

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

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

比如,你可以为网卡硬中断配置 CPU 亲和性(smp_affinity)

CPU affinity 是一种调度属性(scheduler property), 它可以将一个进程"绑定" 到一个或一组CPU上.

,。 调度器会试图保持进程在相同的CPU上运行, 这意味着进程通常不会在处理器之间频繁迁移,进程迁移的频率小就意味着产生的负载小。

因为程序的作者比调度器更了解程序,所以我们可以手动地为其分配CPU核,而不会过多地占用CPU0,或是让我们关键进程和一堆别的进程挤在一起,所有设置CPU亲和性可以使某些程序提高性能

查看 进程的CPU亲和性

taskset -p 1778

 taskset -p[c] mask pid

              举例:打开2个终端,在第一个终端运行top命令,第二个终端中

          首先运行:[~]# ps -eo pid,args,psr | grep top #获取top命令的pid和其所运行的CPU号

          其次运行:[~]# taskset -cp 新的CPU号 pid       #更改top命令运行的CPU号

          最后运行:[~]# ps -eo pid,args,psr | grep top #查看是否更改成功

开启 irqbalance 服务:

默认所有的中断处理都集中在cpu0 上,导致服务器负载过高的时候,cpu0 成了瓶颈,而其他cpu 却还闲着,启用 irqbalance 服务,既可以提升性能,又可以降低能耗。

irqbalance 用于优化中断分配,它会自动收集系统数据以分析使用模式,并依据系统负载状况将工作状态置于 Performance mode 或 Power-save mode。

处于 Performance mode 时,irqbalance 会将中断尽可能均匀地分发给各个 CPU core,以充分利用 CPU 多核,提升性能。

处于 Power-save mode 时,irqbalance 会将中断集中分配给第一个 CPU,以保证其它空闲 CPU 的睡眠时间,降低能耗

简单来说,Irqbalance的主要功能是优化中断分配,收集系统数据并分析,通过修改中断对于cpu的亲和性来尽量让中断合理的分配到各个cpu,以充分利用多核cpu,提升性能

开启命令:service irqbalance start

再如,你可以开启 RPS(Receive Packet Steering)和 RFS(Receive Flow Steering),将应用程序和软中断的处理,调度到相同 CPU 上,这样就可以增加 CPU 缓存命中率,减少网络延迟。


    

    rps,即Receive Package Steering,其原理是单纯地以软件方式实现接收的报文在cpu之间平均分配,即利用报文的hash值找到匹配的cpu,然后将报文送至该cpu对应的backlog队列中进行下一步的处理。上面提到的报文hash值,可以是由网卡计算得到,也可以是由软件计算得到,具体的计算也因报文协议不同而有所差异,以tcp报文为例,tcp报文的hash值是根据四元组信息,即源ip、源端口、目的ip和目的端口进行hash计算得到的

        从rps的原理来看,我们知道rps只是根据报文的hash值从分发处理报文的cpu列表中选取一个目标cpu,这样虽然负载均衡的效果很好,但是当用户态处理报文的cpu和内核处理报文软中断的cpu不同的时候,就会导致cpu的缓存不命中,影响性能。而rfs就是用来处理这种情况的,rfs的目标是通过指派处理报文的应用程序所在的cpu来在内核态处理报文,以此来增加cpu的缓存命中率。所以rfs相比于rps,主要差别就是在选取分发处理报文的目标cpu上,而rfs还需要依靠rps提供的机制进行报文的后续处理。

  rfs实现指派处理报文的应用程序所在的cpu来在内核态处理报文这一目标主要是依靠两个流表来实现的,其中一个是设备流表,记录的是上次在内核态处理该流中报文的cpu;另外一个是全局的socket流表,记录的是流中的报文渴望被处理的目标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 上执行,从而提升网络的吞吐量。

查看网卡多队列:

ethtool -l eth0

Channel parameters for eth0:

Pre-set maximums:

RX: 0

TX: 0

Other: 0

Combined: 4  最多支持设置4个队列

Current hardware settings:

RX: 0

TX: 0

Other: 0

Combined: 1 生效的是一个对列

 开启网卡的多队列功能:

ethtool -L eth1 combined 4  设置eth1当前使用4个队列

再如,你可以增大网络接口的缓冲区大小,以及队列长度等,提升网络传输的吞吐量(注意,这可能导致延迟增大)。

你还可以使用 Traffic Control 工具,为不同网络流量配置 QoS。

甚至使用:

DPDK使用了轮询(polling)而不是中断来处理数据包。在收到数据包时,经DPDK重载的网卡驱动不会通过中断通知CPU,而是直接将数据包存入内存,交付应用层软件通过DPDK提供的接口来直接处理,这样节省了大量的CPU中断时间和内存拷贝时间

XDP(eXpress Data Path)为Linux内核提供了高性能、可编程的网络数据路径。由于网络包在还未进入网络协议栈之前就处理,它给Linux网络带来了巨大的性能提升

总结:

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

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

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

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

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

参考资料:

https://blog.csdn.net/google0802/article/details/52304776 

https://blog.csdn.net/minxihou/article/details/84931456 

http://liushao.net/b/linux-wang-luo-xie-yi-zhan-shou-xiao-xi-guo-cheng-ring-buffer/

https://www.alibabacloud.com/help/zh/doc-detail/52559.htm

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注