前言:
web 类应用一般会部署像 nginx、tomcat、php 等应用程序,使用默认的内核参数设置满足大部分场景,如果优化内核参数,也可以释放不少服务器性能,尤其是在高并发下
一.SYN 状态的内核参数调优
大量 SYN_SENT
这种是主动连接服务端,而未得到响应,也就是 SYN 超时,一般是服务端根本不存在或者无法访问
如,我随便 telnet 一个位置的 IP 和端口
telnet 172.18.11.110:90
[root@test bbs]# ss -an|grep SYN
SYN-SENT 0 1 172.16.196.145:55052 172.18.11.110:90
除了以上,还有种就是你的服务出现异常,比如 mysql 服务器宕机了,web 服务去访问 mysql 数据库的时候就连不上,也会出现 SYN_SENT 状态,但无论哪种,都是主动发起连接导致的,因此业务上解决更好
net.ipv4.tcp_syn_retries = 2
新建连接如果无响应,内核要发送多少次 SYN 连接才放弃,默认值为 5
在 Linux 下,默认重试次数为 5 次,该值不能大于 255,重试的间隔时间从 1s 开始每次都翻倍(因为隔一秒重试后还会等待响应,因此实际上是从 3 秒开始),5 次的重试时间间隔为 3s, 7s, 15s, 31s, 63s,总共 63s,TCP 才会把断开这个连接。统计成公式 2^(n+1) – 1,因此设置越大,翻倍越多,对应内网环境,这个值修改为 2 比较合适
大量 SYN_RECV
大量的 SYN 出现有两种情况,可能是攻击,也可能是正常的业务请求,无论哪种,都大量的占用了服务器资源
net.ipv4.tcp_synack_retries = 2
跟参数 net.ipv4.tcp_syn_retries 一样,只是这个内核参数是控制回应 SYN 失败的重试次数,默认值也是 5,和上面一样修改为 2
其他内核参数调整 net.ipv4.tcp_syncookies = 1
开启 SYN cookies,当出现 SYN 等待队列溢出时,启动 cookies 来处理
什么是 SYN cookies? 我们知道 SYN 攻击是一系列伪造 IP 源地址的 SYN 包,IP 地址是随意选择且不提供攻击者任何的线索,SYN 攻击持续直到服务的 SYN 队列被用满。如果启用该参数,此时 SYN cookies 会将 TCP 请求的 SYN 缓存起来,当服务器正常的时候,再处理,但是如果攻击并发很高很大,其实用处不大,因此只能少量防范
SYN cookies 可参考:https://blog.csdn.net/chenmo1…
net.ipv4.tcp_max_syn_backlog = 65535
指定所能接受 SYN 同步包的最大客户端数量,即半连接上限,默认值为 128,对于 web 服务,频繁大量的 SYN 同步包,应该放大这个值
注: 这个值应该 >=net.core.somaxconn,net.core.somaxconn 后面会提到
二.FIN_WAIT_2 状态的内核参数调优
FIN_WAIT_2 是主动关闭端等待对端关闭连接的状态,如果被动关闭不发送 FIN 关闭连接,那么这个状态就会一直存在,当然 Linux 有针对该状态的超时时间,默认为 60 秒
net.ipv4.tcp_fin_timeout = 10
三.TIME_WAIT 状态的内核参数调优
TIME_WAIT 是主动关闭端的状态,也称为 2MSL 等待状态,也就是 2 倍的 MSL 时间。在 RFC 793[Postel 1981c]指出 MSL 为 2 分钟,然而现实中的常用值是 30 秒,1 分钟或者 2 分钟(Linux 设置为 30 秒),Linux 也没有提供能够修改 TIME_WAIT 状态时间的接口,除非重新编译系统内核
MSL 的理解
MSL 是英文 Maximum Segment Lifetime 的缩写,翻译为 ” 最长报文段寿命 ”,每个具体 TCP 实现必须选择一个报文段最大生存时间(Maximum Segment Lifetime),而这个最大生存时间是任何报文段被丢弃前在网络内的最长时间
MSL 的时间是有限的,因为 TCP 报文段以 IP 数据报在网络内传输,而 IP 数据报则有限制其生存时间的 TTL(time to live)字段,TTL 可译为生存时间,IP 数据报每经过一个路由器,它的值就减 1,当这个值为 0 时,数据报则被丢弃
为什么等待 2MSL
1. 确保有足够的时间让服务端收到 ACK,如没有收到,则会响应对方新的 FIN+ACK 封包。比如主动关闭端 (客户端) 发送了最后一个 ACK 报文段给被动关闭端(服务端),但这个 ACK 报文段有可能丢失,如果服务端没有收到这个 ACK,那么处于 LAST_ACK 的服务端在超时后回重发 FIN+ACK 报文段,这样客户端就能在 2MSL 时间内收到这个重发的 FIN+ACK 报文段。如果客户端发送了最后的 ACK 报文不进入 TIME_WAIT 而是立即释放连接,那么就无法收到客户端重发的 FIN+ACK 报文段。因此等待 2MSL 是为了更安全的断开连接
2. 有足够的时间让处于 TIME_WAIT 状态的连接不会跟后面的连接混在一起。比如一些延迟的包发过来,但是如果没有 TIME_WAIT,那么就发到了新连接上,这样就混为一团,而如果是 TIME_WAIT,则会丢弃这些延迟的包
等待 2MSL 的缺点
TCP 连接在 2MSL 等待期间,这个处于 TIME_WAIT 状态的连接(客户端的 IP 地址和端口编号,服务器的 IP 地址和端口号) 不能再被使用,它只能在 2MSL 结束后才能再被使用,而这些 TIME_WAIT 状态占用大量服务资源,对于 web 服务来说是不合理的
修改内核参数防止因为 2MSL 导致 TIME_WAIT 过多
对于 web 服务器,由于我们需要经常去连接 mysql、redis 或者一些 RPC 调用等,会有大量的主动关闭状态(TIME_WAIT),因此可以修改内核参数限制 TIME_WAIT 的数量
net.ipv4.tcp_max_tw_buckets = 20000
限制 timewait 的数量,防止大量 timewait 导致系统负载升高,一旦达到限定值,则强制清理 TIME_WAIT 状态的连接并在打印系统日志(time wait bucket table overflow),该参数官方文档说明主要用来对抗 DDos 攻击
net.ipv4.tcp_tw_recycle= 1
启用 timewait 快速回收
net.ipv4.tcp_timestamps = 0
时间戳,0 关闭,1 开启。不能和 net.ipv4.tcp_tw_recycle 参数同时开启,因为一旦开启 net.ipv4.tcp_tw_recycle,服务器就会检查包的时间戳,如果对方发来的包的时间戳是乱跳或者说时间戳是滞后的,这样服务器就不会回复,服务器会把带了 ” 倒退 ” 的时间戳包当作是 ”recycle” 的 tw 连接的重传数据,不是新的请求,于是丢掉不回包,就容易出现 syn 不响应
net.ipv4.tcp_tw_reuse = 1
开启重用,允许将 TIME-WAIT sockets 重新用于新的 TCP 连接
TIME_WAIT 总结
其实 TIME_WAIT 是主动断开连接,所以如果让对方主动断开连接的话,那么这个 TIME_WAIT 问题就对方的了。所以如果这个问题出现过多,多从业务着手,比如 HTTP 服务,NGINX 设置 keepalive 参数(浏览器会重用一个 TCP 连接来处理多个 HTTP 请求),然后让客户端断开连接,当然这个要设置好 keepalive_timeout 的超时时间,因为有些浏览器可能不会主动断开连接
而如果是主动连接 mysql、redis 等后端调用,可以考虑使用长连接来避免 TIME_WAIT 过多的问题
四. 长连接 (keepalive) 的内核参数调整
Linux 下,keepalive 不是默认开启,也无内核参数控制,它需要在 TCP 的 socket 中单独开启,Linux 内核影响 keepalive 的参数目的仅仅是探测 TCP 连接是否存活,然后处理异常连接
net.ipv4.tcp_keepalive_time = 120
单位秒,表示 TCP 连接在多少秒没有数据报文传输时启动探测报文,探测连接是否正常net.ipv4.tcp_keepalive_intvl = 5
单位秒,前后探测报文之间的时间间隔net.ipv4.tcp_keepalive_probes = 3
探测次数,超过设置后丢弃
五.TCP/UDP 内存参数调整
(1)TCP 内存使用设置
针对 TCP socket buffernet.ipv4.tcp_mem = 94500000 915000000 927000000
指定 TCP 内存的整体使用状况,单位为页。这 3 个值为 TCP 整体内存【低、压力、高】,在 web 服务中,放大这个值即可
第一个值 tcp_mem[0]:当 TCP 全局分配的页数低于此数时,TCP 不调整其内存分配
第二个值 tcp_mem[1]:当 TCP 分配的内存量超过这个页数,进入内存压力模式,TCP 调节内存消耗
第三个值 tcp_mem[2]:TCP 全局使用的最大页数分配,这个会值覆盖任何其他限制,如超过,所有的新的 TCP 的 buffer(缓冲区)内存分配都会失败
其实我们可以设置这个值较大,只要不限制系统分配内存,然后以监控来应对内存问题,一般来说,根据业务所选配置,很难将内存耗尽,否则优化的就不仅仅是这个参数了
net.ipv4.tcp_rmem = 4096 87380 6291456
net.ipv4.tcp_wmem = 4096 16384 4194304
上面两组参数表示单个 TCP 连接上的读写 buffer(缓冲)内存上限,单位字节,这三个值分别为最小值、默认值(会覆盖 rmem_default、wmem_default 配置)、最大值
最小值:TCP socket 的发送缓冲区 (tcp_rmem)/ 接收缓冲区(tcp_wmem) 的内存,默认 1 页(4K)
默认值:TCP socket 使用的发送缓冲区 (tcp_rmem)/ 接收缓冲区(tcp_wmem) 初始大小,这个值会覆盖 (net.core.wmem_default/net.core.rmem_default),一般设置要低于(net.core.wmem_default/net.core.rmem_default) 这个值,默认值为 16K
最大值:TCP socket 使用的发送缓冲区 (tcp_rmem)/ 接收缓冲区(tcp_wmem) 的最大大小,这个值不会覆盖(net.core.wmem_max/net.core.rmem_max),默认为 4M
这两个内核参数的设置主要是针对每一个 TCP 连接来说的,使用默认设置就差不多了,如果设置太大,单个 TCP 连接占用过多内存也是有问题的
什么是 TCP 读写 buffer(缓冲)?
实际上,TCP 连接所用内存的多少是由读写 buffer 大小决定,对读 buffer 来讲,当收到对端连接的 TCP 报文时,会导致读 buffer 内存增加,如果这个报文加上当前读 buffer 内存超过 tcp_rmem[3]上限,那么该报文将被丢弃。只有当调用 read、recv 这样的方法读取 TCP 流时,读 buffer 内存就会减少,因此读 buffer 内存是一个动态变化的,用多少就分配多少 buffer,如果这个连接空闲时,而用户进程已经把连接上收到的数据都消费了,那么读 buffer 使用的内存就为 0 了
对于写 buffer 也是一样的,在 socket 编程中,当调用 send 或者 write 时,就会造成写 buffer 增大,那么什么时候减少?就是当接收到对端 TCP 连接发来的 ACK 确认了报文成功发送时,写 buffer 就会减少,类似于我给你发一个文件,我先拷贝出来发给你,我确认你收到了,我就把这个源文件删除,以免占用空间,如果确认没收到,那么我会重发
所以读写 buffer 是一直不停变化的,那么怎样的场景会导致读写 buffer 达到上限呢?就读 buffer 而言,比如接收 TCP 对端报文,对端发了很多很多报文,我读取后无法及时读取(read 和 recv),导致读 buffer 堆积越来越多,最终达到上限,最后丢弃报文,写 buffer 也一样,send 或者 write 大量的报文时,如果 TCP 对端不能及时 read 和 recv 就会导致写 buffer 堆积。
针对系统的读写 buffer 参数调整 net.core.rmem_default = 4194304
默认读 buffer 大小,单位字节net.core.wmem_default = 4194304
默认写 buffer 大小,单位字节net.core.rmem_max = 4194304
最大读 buffer 大小,单位字节net.core.wmem_max = 4194304
最大写 buffer 大小,单位字节
看到其定义,是不是觉得跟 net.ipv4.tcp_mem、net.ipv4.tcp_rmem、net.ipv4.tcp_wmem 含义很重合呢?
其实 (net.ipv4.tcp_mem、net.ipv4.tcp_rmem、net.ipv4.tcp_wmem) 这几个参数只控制 TCP socket 的内存大小,而且如果遇到 TCP socket 申请内存,(net.core.rmem_default、net.core.wmem_default)会被 (net.ipv4.tcp_rmem、net.ipv4.tcp_wmem) 覆盖
所以 (net.core.rmem_default、net.core.wmem_default、net.core.rmem_max、net.core.wmem_max) 控制系统所有协议的读写 buffer 大小
(2)UDP 协议内存使用设置
net.ipv4.udp_mem = 752832 1003776 1505664
net.ipv4.udp_rmem_min = 4096
net.ipv4.udp_wmem_min = 4096
这几个参数针对 UDP 协议,则跟上面 TCP 的含义一致
六. 其他内核参数
net.ipv4.ip_local_port_range = 1024 65000
表示用于向外连接的临时端口范围。缺省情况下很小:32768 到 61000,因为主动连接需要用到很多临时端口(如连接 mysql、redis),而临时端口最大值为(2^16-1)65535,1000 之前一般为系统保留端口,所以建议设置为 1024 到 65000 的较大范围
net.core.somaxconn = 65535
net.core.somaxconn 表示 socket 监听 (listen) 的 backlog 上限,backlog 是 socket 的监听队列,也就是服务端所能 accept(socket 编程中 accpet()函数为建立 TCP 连接接受连接状态)即处理数据的最大客户端数量队列,默认值为 128,如果队列满了的时候新来一条建立连接,该连接会被拒绝
该值应当小于等于 net.ipv4.tcp_max_syn_backlog,因为 net.ipv4.tcp_max_syn_backlog 参数控制的 SYN 队列客户端的数量,还在建立连接之前,因此设置为 65535 一样比较合适
fs.file-max = 6553600
设置系统所有进程一共可以打开多少个文件句柄,这是一个系统级的设置,管控的是所有进程总共可以同时打开多少文件句柄,如果多个进程打开了较多文件就会导致文件句柄不足,因此设置较大值,不过要注意程序打开的文件越多,就占用更多的内存,因此要根据业务和服务器配置起来设置
如果想单独对某个进程设置可以打开多少文件句柄,那么可以使用 ulimit - n 命令设置,但该命令只对当前 session 生效,默认值为 1024
ulimit -n 655350
也可以写入文件永久生效,对每个进程的打开文件数量限制
vim /etc/security/limits.conf
* soft nofile 655350
* hard nofile 655350
总结
现在多数线上业务,服务器很少暴露在外网了,前端一般有负载均衡、防火墙等代理。甚至服务器已经变成 VPC(虚拟内网)环境,将这些服务器隔离在外网环境之外,这样就减少了像 DDOS 等攻击,这些攻击一般都让外部代理承受了。
对于服务器的一些内核性能参数范围,如果网络环境及架构设计好,一些范围参数可以设置的偏大,性能偏极限一些,这样能最大释放服务器的性能,其他的就用系统默认的参数配置即可。对于 WEB 服务的优化,是多方面的,内核参数仅仅是释放了服务器本该有的性能,而更高的承载能力,需要从服务器配置、网络、架构、数据库及缓存和实际业务应用等多方面着手,不同的调整满足不同的需求
RT: 以上有些地方可能会解释得不对,还望指正,一起学习