[tcp] WEB服务,Linux下的内核参数调优

潘小安 2019-07-25

前言:
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 buffer
net.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:以上有些地方可能会解释得不对,还望指正,一起学习

相关推荐