服务器性能优化,是我以前一提起来就发憷的东西,觉的很泛。不知道该怎么搞,搞哪些 看着群里的大神们都侃侃而谈。我就在某客学习了一些套路,总结下来。用以备忘。
1 确定优化目标
实际上,虽然网络性能优化的整体目标,是降低网络延迟(如 RTT)和提高吞吐量(如 BPS 和 PPS),但具体到不同应用中,每个指标的优化标准可能会不同,优先级顺序也大相径庭。
数据库,缓存系统,快速完成网络收发,低延迟,是主要性能目标。
web服务,需要同时兼顾吞吐量和延迟
明白了这一点,在进行基准测试时,我们就可以按照协议栈的每一层来测试。由于底层是其上方各层的基础,底层性能也就决定了高层性能。所以我们要清楚,底层性能指标,其实就是对应高层的极限性能。我们从下到上来理解这一点。
首先是网络接口层和网络层,它们主要负责网络包的封装、寻址、路由,以及发送和接收。每秒可处理的网络包数 PPS,就是它们最重要的性能指标(特别是在小包的情况下)。你可以用内核自带的发包工具 pktgen ,来测试 PPS 的性能。
再向上到传输层的 TCP 和 UDP,它们主要负责网络传输。对它们而言,吞吐量(BPS)、连接数以及延迟,就是最重要的性能指标。你可以用 iperf 或 netperf ,来测试传输层的性能。
不过要注意,网络包的大小,会直接影响这些指标的值。所以,通常,你需要测试一系列不同大小网络包的性能。
最后,再往上到了应用层,最需要关注的是吞吐量(BPS)、每秒请求数以及延迟等指标。你可以用 wrk、ab 等工具,来测试应用程序的性能。
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。
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 传输层
第一类,在请求数比较大的场景下,你可能会看到大量处于 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秒)不成功,内核才彻底放弃,认为该连接已失效.对服务器而言,显然上述值太大
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