Linux在系统运行时可以修改内核参数(/proc/sys
或/etc/sysctl.conf
),而无需重新引导系统,这个功能是通过/proc
虚拟文件系统实现的。
在/proc/sys
目录下存放着大多数的内核参数,并且设计成可以在系统运行的同时进行更改。修改后保存即可生效。但这种修改只是临时的,重新启动机器后就会失效。如果想要永久生效,可以修改/etc/sysctl.conf
文件,并通过sysctl -p
使参数立即生效。
通过sysctl -a
可以查看所有内核参数。
由于可以修改的内核参数都在/proc/sys
目录下,所以sysctl.conf
中的变量名省略了目录名/proc/sys
,换句话说,内核参数/proc/sys/net/ipv4/tcp_tw_reuse
在sysctl.conf
中写成net.ipv4.tcp_tw_reuse
即可。
以下是一些常见的需要优化的内核参数。
net.ipv4.tcp_max_tw_buckets和net.ipv4.tcp_tw_reuse
对于tcp连接,四次挥手过程如下图所示:
经过四次挥手后,主动方处于TIME_WAIT
状态,经过2MSL时间后才会真正关闭。原因有两点:
正常情况下,TIME_WAIT会定时回收资源,不会产生什么问题。但对于一些存在很多高并发短链接的服务器,大量的socket处于TIME_WAIT状态,再加上如果客户端的并发量持续保持高位,socket端口号可能会出现不够用的情况(处于TIME_WAIT状态的套接字其实是已经关闭了文件描述符,也就是说这个状态并不占用文件描述符)。
通过命令 netstat -ant | grep -i time_wait | wc -l
可以统计处于TIME_WAIT状态的socket。
单进程打开文件受最大文件句柄数限制(linux中一切皆文件),系统级文件句柄数通过
/proc/sys/fs/file-max
查看,用户级句柄数通过ulimit -n
查看(默认1024)。用户级不能超过系统级限制。
客户端主动发起的连接数也受可用端口号限制(net.ipv4.ip_local_port_range
),一般使用端口号为1025~65500
。
在保证足够的文件句柄数和可用端口的情况下,可以对处于TIME_WAIT状态的socket数量进行优化,适当减小或者开启重用。前者调整net.ipv4.tcp_max_tw_buckets
的大小,后者将net.ipv4.tcp_tw_reuse
设置为1。
net.ipv4.tcp_syncookies
在tcp三次握手过程中,如果客户端在发起第一次握手完成后直接断开连接,此时服务端在一段时间内就要维护这个半连接并重复发送ack确认包,如果这样的无意义半连接大量存在,就会导致服务器消耗大量资源从而瘫痪。Syn flood攻击就是利用的这一特点。
为了避免Syn flood攻击,可以将net.ipv4.tcp_syncookies
设置为1,开启该参数后,服务端在向客户端发送ack+syn之前会要求client在短时间内回应一个序号,如果客户端不能提供正确的序号则认为客户端不合法,就不会维持该半连接了。
net.ipv4.tcp_fin_timeout
在tcp四次挥手过程中,主动发起方在转变成TIME_WAIT
状态前处于FIN-WAIT-2
状态。该状态默认值为60,建议优化为30。
net.ipv4.tcp_keepalive_time, net.ipv4.tcp_keepalive_intvl和net.ipv4.tcp_keepalive_probes
在经过三次握手建立起tcp连接之后,客户端和服务端就可以进行通信了。正常通信完成后,可以由任意一方主动关闭连接。
如果出现特殊情况,例如本应该由客户端关闭连接,但客户端断网了或者直接退出了,此时该连接就变成一个死连接了。在这种情况下,就需要服务器来探测了。net.ipv4.tcp_keepalive_time
表示在连接建立之后经过多少时间开始探测客户端是否正常,net.ipv4.tcp_keepalive_intvl
表示探测间隔时间,net.ipv4.tcp_keepalive_probes
表示如果探测到客户端不正常,经过多少次探测才能决定客户端实际死亡。
下面是一个参考的优化示例:
net.ipv4.ip_local_port_range="1025 65000" // 默认起始值>3000
net.ipv4.tcp_max_tw_buckets=8000 // 默认值>10000
net.ipv4.tcp_tw_reuse=1 // 默认0或2,表示禁用或对本地流量启用
net.ipv4.tcp_fin_timeout=30 // 默认60s
net.ipv4.tcp_keepalive_time=120 // 默认7200s
net.ipv4.tcp_keepalive_intvl=20 // 默认75s
net.ipv4.tcp_keepalive_probes=3 // 默认9
net.ipv4.tcp_max_syn_backlog= 1024 // 半连接队列大小,默认值与内存相关,可能比较小
net.core.somaxconn = 4096 // 全连接队列大小,默认值较小
除此之外,一般还有缓冲区相关参数的优化。
参数 | 说明 | 示例 |
---|---|---|
net.ipv4.tcp_mem | 总的tcp占用内存,三个值低、中、高分别代表tcp内存使用水平,一般由操作系统自动分配,单位是内存页 | [262144 524288 786432] 262144表示1G内存:262144x4/1024/1024 |
net.core.rmem_default | 接收缓冲区默认值 | 131072(128k) |
net.core.rmem_max | 接收缓冲区最大值 | 16777216(16m) |
net.ipv4.tcp_rmem | tcp接收缓冲区大小 | [4096 131072 16777216],tcp_rmem一般不单独配置,默认值和最大值会被rmem_default和rmem_max覆盖 |
对应的,net.core.wmem_default
, net.core.wmem_max
和net.ipv4.tcp_wmem
表示发送缓冲区大小。
下面是一个示例:
net.ipv4.tcp_mem = 262144 524288 786432
net.core.wmem_max = 16777216
net.core.wmem_default = 131072
net.core.rmem_max = 16777216
net.core.rmem_default = 131072
net.ipv4.tcp_wmem = 4096 131072 16777216
net.ipv4.tcp_rmem = 4096 131072 16777216
稍微提高tcp读写缓冲区的容量,可以增加tcp传输效率,比如上面默认值131072=128k,现有一个1M的文件传输,只需8次传输即可,比较适合图片类传输。但也不是越大越好,比如一个文字页面只有15k,使用128k的内存显然有些浪费。上文tcp压力状态(中值)下的容量为2G,对应tcp读写缓冲区128k,可应对的连接数为16384 (2048x1024/128),可满足10k要求[3]。
可通过ss -ntmp
查看当前缓冲区情况:
$ ss -ntmp
ESTAB 0 0 [::ffff:109.244.190.163]:9988 [::ffff:10.10.4.26]:54440 users:(("squid",pid=2299,fd=12605)) skmem:(r0,rb12582912,t0,tb12582912,f0,w0,o0,bl0,d0)
rb12582912 表示 TCP 接收缓冲区大小是 12582912 字节,tb12582912 表示 UDP 发送缓存区大小是 12582912 字节。Recv-Q 和 Send-Q 分别表示当前接收和发送缓冲区中的数据包字节数。
一些其他参数的说明[4]。
[1].https://www.yisu.com/zixun/111497.html
[2].http://www.uml.org.cn/embeded/202101112.asp
[3]. http://events.jianshu.io/p/c9b95525b53b
[4]. https://cloud.tencent.com/document/product/213/46400