大量请求下 常用内核参数的说明

637 阅读1分钟

场景说明

服务器在存在大量短连接的情况下,Linux的TCP栈一般都会生成大量的 TIME_WAIT 状态的socket。

你可以用下面的命令看到

# netstat -an | awk '/^tcp/ {++s[$NF]} END {for (i in s) print i,s[i]}'
TIME_WAIT 4561
FIN_WAIT1 45
ESTABLISHED 85
FIN_WAIT2 266
SYN_RECV 2
LAST_ACK 2
LISTEN 17

要减少TIME_WAIT状态的连接数就要了解这些状态是处于TCP/IP 的那个阶段

TCP/IP 三次握手和4次挥手

三次握手

原理:

  1. 第一次握手:发送方A首先发送一个带有SYN(synchronize) 标志的数据包给接收方B
  2. 第二次握手:接收方B接手后,回传一个带有SYN/ACK 标志的数据包传递确认信息,表示数据已经接收到
  3. 第三次握手:最后发送方A在回传一个带有ACK标志的数据包,代表我已经收到了,表示握手结束

白话版本

  1. Client: 嘿,三儿,是我,听到了吗?
  2. Server:我听到了,你能听到我的吗?
  3. Client:好的,咱俩都能听到对方的话,“交流” 可以开始啦

四次挥手

说明:

当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。但未必被动方所有的数据都完整的发送给了主动方,所以被动方不会马上关闭SOCKET,它可能还需要发送一些数据给主动方后,再发送FIN报文给主动方,告诉主动方同意关闭连接,所以这里的ACK报文和FIN报文多数情况下都是分开发送的。

中断连接端可以是Client端,也可以是Server端

原理:

  1. 第一次挥手: Client 发送一个FIN,用来关闭Client到Server的数据传送,Client进入到FIN_WAIT_1 状态
  2. 第二次挥手:Server收到FIN后,发送一个ACK给Client(Client 接到回复信息后,进入到FIN_WAIT_2状态),确认序列号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server 进入CLOSE_WAIT状态
  3. 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK 状态
  4. 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序列号收到序号+1,Server进入CLOSED状态,完成四次挥手

白话版本:

  1. Client:我所有的东西说完了
  2. Server:我已经全部听到了,但是等等我,我的还没有说完
  3. Server:好了,我的已经说完了
  4. Client:好的,那我们的通信结束

其他

假设Client端发起中断连接请求,也就是发送FIN报文,此时Client端进入到FIN_WAIT_1 状态。

Server端接到FIN报文后,意思是说"我Client端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以你先发送ACK,"告诉Client端,你的请求我收到了,但是我还没准备好,请继续你等我的消息"。

这个时候Client端就进入FIN_WAIT_2状态,继续等待Server端的FIN报文。

当Server端确定数据已发送完成,则向Client端发送FIN报文,"告诉Client端,好了,我这边数据发完了,准备好关闭连接了"。

Client端收到FIN报文后,"就知道可以关闭连接了,但是他还是不相信网络,怕Server端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果Server端没有收到ACK则可以重传。“,Server端收到ACK后,"就知道可以断开连接了"。Client端等待了2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,我Client端也可以关闭连接了。OK,TCP连接就这样关闭了!

MSL解释

MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为tcp报文(segment)是ip数据报(datagram)的数据部分,具体称谓请参见《数据在网络各层中的称呼》一文,而ip头中有一个TTL域,TTL是time to live的缩写,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个ip数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等。

MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间,等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。

注意

在TIME_WAIT状态中,如果TCP client端最后一次发送的ACK丢失了,它将重新发送。TIME_WAIT状态中所需要的时间是依赖于实现方法的。典型的值为30秒、1分钟和2分钟。等待之后连接正式关闭,并且所有的资源(包括端口号)都被释放。

为什么连接的时候是三次握手,关闭的时候却是四次握手?

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。

为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?

虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。

常用内核参数的介绍

常用参数

net.ipv4.tcp_fin_timeout
#表示套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间,默认值是60秒。
#该参数对应系统路径为:/proc/sys/net/ipv4/tcp_fin_timeout 60

net.ipv4.tcp_tw_reuse
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认值为0,表示关闭。
#该参数对应系统路径为:/proc/sys/net/ipv4/tcp_tw_reuse 0

net.ipv4.tcp_tw_recycle
#表示开启TCP连接中TIME-WAIT sockets的快速回收。
#该参数对应系统路径为:/proc/sys/net/ipv4/tcp_tw_recycle,默认为0,表示关闭。
#提示:reuse和recycle这两个参数是为防止生产环境下Web、Squid等业务服务器time_wait网络状态数量过多设置的。

net.ipv4.tcp_syncookies
#表示开启SYN Cookies功能。当出现SYN等待队列溢出时,启用Cookies来处理,可防范少量SYN攻击,这个参数也可以不添加。
#该参数对应系统路径为:/proc/sys/net/ipv4/tcp_syncookies,默认值为1

net.ipv4.tcp_keepalive_time
#表示当keepalive启用时,TCP发送keepalive消息的频度。默认是2小时,建议改为10分钟。
#该参数对应系统路径为:/proc/sys/net/ipv4/tcp_keepalive_time,默认为7200秒。

net.ipv4.ip_local_port_range
#该选项用来设定允许系统打开的端口范围,即用于向外连接的端口范围。
#该参数对应系统路径为:/proc/sys/net/ipv4/ip_local_port_range 32768 61000

net.ipv4.tcp_max_syn_backlog
#表示SYN队列的长度,默认为1024,建议加大队列的长度为8192或更多,这样可以容纳更多等待连接的网络连接数。
#该参数为服务器端用于记录那些尚未收到客户端确认信息的连接请求最大值。
#该参数对象系统路径为:/proc/sys/net/ipv4/tcp_max_syn_backlog

net.ipv4.tcp_max_tw_buckets
#表示系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数值,TIME_WAIT套接字将立刻被清除并打印警告信息。
#默认为180000,对于Apache、Nginx等服务器来说可以将其调低一点,如改为5000~30000,不通业务的服务器也可以给大一点,比如LVS、Squid。
#此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。
#该参数对应系统路径为:/proc/sys/net/ipv4/tcp_max_tw_buckets

net.ipv4.tcp_synack_retries
#参数的值决定了内核放弃连接之前发送SYN+ACK包的数量。
#该参数对应系统路径为:/proc/sys/net/ipv4/tcp_synack_retries,默认值为5

net.ipv4.tcp_syn_retries
#表示在内核放弃建立连接之前发送SYN包的数量。
#该参数对应系统路径为:/proc/sys/net/ipv4/tcp_syn_retries 5

net.ipv4.tcp_max_orphans
#用于设定系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。
#如果超过这个数值,孤立连接将被立即被复位并打印出警告信息。
#这个限制只有为了防止简单的DoS攻击。不能过分依靠这个限制甚至认为减少这个值,更多的情况是增加这个值。
#该参数对应系统路径为:/proc/sys/net/ipv4/tcp_max_orphans 65536

net.core.somaxconn
#该选项默认值是128,这个参数用于调节系统同时发起的TCP连接数,在高并发的请求中,默认的值可能会导致链接超时或重传,因此,需要结合并发请求数来调节此值。
#该参数对应系统路径为:/proc/sys/net/core/somaxconn 128

net.core.netdev_max_backlog
#表示当每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许发送到队列的数据包最大数。
#该参数对应系统路径为:/proc/sys/net/core/netdev_max_backlog,默认值为1000

net.ipv4.tcp_max_syn_backlog = 262144
#记录的那些尚未收到客户端确认信息的连接请求的最大值。对于有128M内存的系统而言,缺省值是1024,小内存的系统则是128。262144 针对32G内存

net.ipv4.tcp_keepalive_time = 30
#当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时。

下面几个参数需要打开防火墙才会存在

net.netfilter.nf_conntrack_max=65536   

#系统支持的最大ipv4连接数,默认65536(事实上这也是理论最大值),同时这个值和你的内存大小有关,如果内存128M,这个值最大8192,1G以上内存这个值都是默认65536,这个值受/proc/sys/net/ipv4/ip_conntrack_max限制

net.netfilter.nf_conntrack_tcp_timeout_established=432000   

#已建立的tcp连接的超时时间,默认432000,也就是5天。影响:这个值过大将导致一些可能已经不用的连接常驻于内存中,占用大量链接资源,从而可能导致NAT ip_conntrack: table full的问题。建议:对于NAT负载相对本机的 NAT表大小很紧张的时候,可能需要考虑缩小这个值,以尽早清除连接,保证有可用的连接资源;如果不紧张,不必修改

net.netfilter.nf_conntrack_tcp_timeout_time_wait=120  

#time_wait状态超时时间,超过该时间就清除该连接,保持不变

net.netfilter.nf_conntrack_tcp_timeout_close_wait=60  

#close_wait状态超时时间,超过该时间就清除该连接,保持不变

net.netfilter.nf_conntrack_tcp_timeout_fin_wait=120  

#fin_wait状态超时时间,超过该时间就清除该连接,同上

Open File 相关

查看open file 相关的操作

# 系统级别的文件数限制,则通过sysctl -a

# 查看系统的最大打开文件数
# sysctl -a |grep -w "fs.file-max"
fs.file-max = 1555706


# 临时修改系统的最大文件数,只对当前进程有效
ulimit -n xxxxx 

# 永久生效的需要修改/etc/security/limits.conf,重启系统后生效
<username>  soft  nofile <open-file number>
<username>  hard  nofile <open-file number>


# 查看所有进程的文件打开数
# lsof | wc -l
28317

# 查看某个进程的打开的文件数
# lsof -p pid | wc -l

# 查看系统中各个进程分别打开了多少句柄数,第一列是句柄数,第二列是应用程序名,第三列是PID
# lsof -n |awk '{print $1,$2}'| sort -n -k2  |uniq -c| sort -nr |more
   1989 https-jss 22964
   1369 titanagen 7679
   1224 GC 22964
    765 G1 22964
    612 dsm_om_co 22964
    495 dsm_sa_da 22902
    364 tuned 17255