tcp 基础知识
CLOSED
: 这个没什么好说的了,表示初始状态。LISTEN
: 这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处于监听状态,可以接受连接了。SYN_RCVD
: 这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三- 次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。SYN_SENT
: 这个状态与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。ESTABLISHED
:这个容易理解了,表示连接已经建立了。FIN_WAIT_1
: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。FIN_WAIT_2
:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。TIME_WAIT
: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。CLOSING
: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。CLOSE_WAIT
: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。LAST_ACK
: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。
三次握手
四次挥手
数据接收
数据包的接收,从下往上经过了三层:网卡驱动、系统内核空间,最后到用户态空间的应用。Linux内核使用sk_buff(socketkernel buffers)数据结构描述一个数据包。当一个新的数据包到达,NIC(networkinterface controller)调用DMAengine,通过RingBuffer将数据包放置到内核内存区。RingBuffer的大小固定,它不包含实际的数据包,而是包含了指向sk_buff的描述符。当RingBuffer满的时候,新来的数据包将给丢弃。一旦数据包被成功接收,NIC发起中断,由内核的中断处理程序将数据包传递给IP层。经过IP层的处理,数据包被放入队列等待TCP层处理。每个数据包经过TCP层一系列复杂的步骤,更新TCP状态机,最终到达recvBuffer,等待被应用接收处理。有一点需要注意,数据包到达recvBuffer,TCP就会回ACK确认,既TCP的ACK表示数据包已经被操作系统内核收到,但并不确保应用层一定收到数据(例如这个时候系统crash),因此一般建议应用协议层也要设计自己的确认机制。
数据发送
和接收数据的路径相反,数据包的发送从上往下也经过了三层:用户态空间的应用、系统内核空间、最后到网卡驱动。应用先将数据写入TCP sendbuffer,TCP层将sendbuffer中的数据构建成数据包转交给IP层。IP层会将待发送的数据包放入队列QDisc(queueingdiscipline)。数据包成功放入QDisc后,指向数据包的描述符sk_buff被放入RingBuffer输出队列,随后网卡驱动调用DMAengine将数据发送到网络链路上。
tcp queue
当 client 通过 connect 向 server 发出 SYN 包时,client 会维护一个 socket 等待队列,而 server 会维护一个 SYN 队列
此时进入半链接的状态,如果 socket 等待队列满了,server 则会丢弃,而 client 也会由此返回 connection time out;只要是 client 没有收到 SYN+ACK,3s 之后,client 会再次发送,如果依然没有收到,9s 之后会继续发送
半连接 syn 队列的长度为 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog) 决定
当 server 收到 client 的 SYN 包后,会返回 SYN, ACK 的包加以确认,client 的 TCP 协议栈会唤醒 socket 等待队列,发出 connect 调用
client 返回 ACK 的包后,server 会进入一个新的叫 accept 的队列,该队列的长度为 min(backlog, somaxconn),默认情况下,somaxconn 的值为 128,表示最多有 129 的 ESTAB 的连接等待 accept(),而 backlog 的值则由 int listen(int sockfd, int backlog) 中的第二个参数指定,listen 里面的 backlog 的含义请看这里。需要注意的是,一些 Linux 的发型版本会直接取 backlog 的值(本人目前接触到的都是正常的)。
当 accept 队列满了之后,即使 client 继续向 server 发送 ACK 的包,也会不被相应,此时,server 通过 /proc/sys/net/ipv4/tcp_abort_on_overflow 来决定如何返回,0 表示直接丢丢弃该 ACK,1 表示发送 RST 通知 client;相应的,client 则会分别返回 read timeout 或者 connection reset by peer。上面说的只是些理论,如果服务器不及时的调用 accept(),当 queue 满了之后,服务器并不会按照理论所述,不再对 SYN 进行应答,返回 ETIMEDOUT。
syn 半连接 accept 全连接
syn 半链接
三次握手协议中,服务器维护一个半连接队列,该队列为每个客户端的SYN包开设一个条目(服务端在接收到SYN包的时候,就已经创建了request_sock结构,存储在半连接队列中),该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包
(会进行第二次握手发送SYN+ACK的包加以确认)。这些条目所标识的连接在服务器处于SYN_RECV
状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。
该队列为SYN 队列,长度为 max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog)
,在机器的tcp_max_syn_backlog值在/proc/sys/net/ipv4/tcp_max_syn_backlog
下配置。
注
: 新版的 linux 已经没有了 syn 队列,SYN_RECV 直接存入 ehash table(e代表establish,ehash即已连接hash队列) 里面 ,详情见https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
查看SYN queue 溢出
netstat -s | grep LISTEN
102324 SYNs to LISTEN sockets dropped
accept 全链接
当第三次握手时,当server接收到ACK之后,会进入一个新的accept的队列,该队列的长度为min(backlog, somaxconn),默认情况下,somaxconn ()的值为 128
,表示最多有 129 的 ESTAB 的连接等待 accept();backlog
的值则应该创建socket 的时候由 int listen(int sockfd, int backlog) 中的第二个参数指定,listen 里面的 backlog 可以有我们的应用程序去定义的。
查看Accept queue 溢出
netstat -s | grep TCPBacklogDrop
TCPBacklogDrop: 2334
netstat ss 相关参数
ss -nltp
查看 listen 状态
Recv-Q 表示的当前等待服务端调用 accept 完成三次握手的 listen backlog 数值,也就是说,当客户端通过 connect() 去连接正在 listen() 的服务端时,这些连接会一直处于这个 queue 里面直到被服务端 accept()
;
Send-Q 表示的则是最大的 listen backlog 数值,这就就是上面提到的 min(backlog, somaxconn) 的值。
ss -ant |grep ESTAB
查看链接状态
recv-Q 表示网络接收队列
表示收到的数据已经在本地接收缓冲,但是还有多少没有被进程取走,recv()
如果接收队列Recv-Q一直处于阻塞状态,可能是遭受了拒绝服务 denial-of-service 攻击
。
send-Q 表示网路发送队列
对方没有收到的数据或者说没有Ack的
,还是本地缓冲区.
如果发送队列Send-Q不能很快的清零,可能是有应用向外发送数据包过快,或者是对方接收数据包不够快
。
这两个值通常应该为0,如果不为0可能是有问题的
。packets在两个队列里都不应该有堆积状态。可接受短暂的非0情况。
ss -s
统计信息
Total: 828 (kernel 0)
TCP: 20792 (estab 293, closed 20484, orphaned 0, synrecv 0, timewait 20480/0), ports 0
Transport Total IP IPv6
* 0 - -
RAW 0 0 0
UDP 15 15 0
TCP 308 306 2
INET 323 321 2
FRAG 0 0 0
- orphaned 不属于任何进程的TCP sockets 数量
synrecv 标识 syn 队列
ss -i
tcp ESTAB 0 0 10.132.*.*:ssh 10.132.*.*:18535 cubic wscale:9,9 rto:204 rtt:0.101/0.015 ato:40 mss:1460 cwnd:10 ssthresh:16 send 1156.4Mbps rcv_rtt:452153 rcv_space:45036 tcp ESTAB 0 0 10.132.*.*:43630 10.135.*.*:6030 cubic wscale:9,9 rto:204 rtt:0.38/0.206 ato:40 mss:1460 cwnd:10 send 307.4Mbps rcv_rtt:398286 rcv_space:29862
- rto 重传超时时间
- rtt 往返时间
- ato ACK超时时长
- mss 网络传输数据最大值
- mtu 网络传输最大报文包 mss+包头长度
- cwnd 拥塞窗口
- ssthresh 慢开始门限
- rcv_space 可用于接收数据的缓冲区空间
netstat -s
统计信息
202270382 invalid SYN cookies received --- 三次握手ack包,syncookies校验没通过;
13700572 resets received for embryonic SYN_RECV sockets ---syn_recv状态下,收到非重传的syn包,则返回reset
1123035 passive connections rejected because of time stamp ---开启sysctl_tw_recycle,syn包相应连接的时间戳 小于 路由中保存的时间戳;
14886782 failed connection attempts --- syn_recv状态下,socket被关闭; 或者 收到syn包(非重传)
438798 times the listen queue of a socket overflowed ---收到三次握手ack包,accept队列满
438798 SYNs to LISTEN sockets ignored ---收到三次握手ack包,因各种原因(包括accept队列满) 创建socket失败
netstat -s | egrep "listen|LISTEN"
ss netstat
性能
time ss *
, time netstat *
对比发现很明显 ss性能更高
,一般建议使用 ss 命令
Net IO配置
系统配置
参数(路径+文件) | 描述 | 默认值 | 优化值 |
---|---|---|---|
/proc/sys/net/core/rmem_default | 默认的TCP数据接收窗口大小(字节) | 229376 | 256960 |
/proc/sys/net/core/rmem_max | 最大的TCP数据接收窗口(字节) | 131071 | 513920 |
/proc/sys/net/core/wmem_default | 默认的TCP数据发送窗口大小(字节) | 229376 | 256960 |
/proc/sys/net/core/wmem_max | 最大的TCP数据发送窗口(字节) | 131071 | 513920 |
/proc/sys/net/core/netdev_max_backlog | 在每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。 | 1000 | 2000 |
/proc/sys/net/core/somaxconn | 定义了系统中每一个端口最大的accept队列的长度,这是个全局的参数。 | 128 | 2048 |
/proc/sys/net/core/optmem_max | 表示每个套接字所允许的最大缓冲区的大小。 | 20480 | 81920 |
/proc/sys/net/ipv4/tcp_mem | 确定TCP栈应该如何反映内存使用,每个值的单位都是内存页(通常是4KB)。第一个值是内存使用的下限;第二个值是内存压力模式开始对缓冲区使用应用压力的上限;第三个值是内存使用的上限。超过将报文丢弃,从而减少对内存的使用。对于较大的BDP可以增大这些值(注意,其单位是内存页而不是字节)。该值包括当前正在使用的所有TCP套接字 | 94011 125351 188022 | 131072 262144 524288 |
/proc/sys/net/ipv4/tcp_rmem | 第一个值告诉内核每个TCP连接的最小接收缓冲区,并且该缓冲区总是分配给TCP套接字,即使系统处于高压状态。 指定的第二个值告诉内核为每个TCP套接字分配的默认接收缓冲区。该值将覆盖其他协议使用的rmem_default值。 此变量中指定的第三个和最后一个值指定可以为TCP套接字分配的最大接收缓冲区 | 4096 87380 4011232 | 8760 256960 4088000 |
/proc/sys/net/ipv4/tcp_wmem | 为自动调优定义socket使用的内存。第一个值是为socket发送缓冲区分配的最少字节数;第二个值是默认值(该值会被wmem_default覆盖),缓冲区在系统负载不重的情况下可以增长到这个值;第三个值是发送缓冲区空间的最大字节数(该值会被wmem_max覆盖)。 | 4096 16384 4011232 | 8760 256960 4088000 |
/proc/sys/net/ipv4/tcp_keepalive_time | TCP发送keepalive探测消息的间隔时间(秒),用于确认TCP连接是否有效。 | 7200 | 1800 |
/proc/sys/net/ipv4/tcp_keepalive_intvl | 探测消息未获得响应时,重发该消息的间隔时间(秒)。 | 75 | 30 |
/proc/sys/net/ipv4/tcp_keepalive_probes | 在认定TCP连接失效之前,最多发送多少个keepalive探测消息。 | 9 | 3 |
/proc/sys/net/ipv4/tcp_sack | 启用有选择的应答(1表示启用),通过有选择地应答乱序接收到的报文来提高性能,让发送者只发送丢失的报文段,(对于广域网通信来说)这个选项应该启用,但是会增加对CPU的占用。 | 1 | 1 |
/proc/sys/net/ipv4/tcp_fack | 启用转发应答,可以进行有选择应答(SACK)从而减少拥塞情况的发生,这个选项也应该启用。 | 1 | 1 |
/proc/sys/net/ipv4/tcp_timestamps | CP时间戳(会在TCP包头增加12个字节),以一种比重发超时更精确的方法(参考RFC 1323)来启用对RTT 的计算,为实现更好的性能应该启用这个选项。 | 1 | 1 |
/proc/sys/net/ipv4/tcp_window_scaling | 启用RFC 1323定义的window scaling,要支持超过64KB的TCP窗口,必须启用该值(1表示启用),TCP窗口最大至1GB,TCP连接双方都启用时才生效。 | 1 | 1 |
tcp_abort_on_overflow | 当守护进程太忙而不能接受新的连接,就象对方发送reset消息,默认值是false。这意味着当溢出的原因是因为一个偶然的猝发,那么连接将恢复状态。只有在你确信守护进程真的不能完成连接请求时才打开该选项,该选项会影响客户的使用。(对待已经满载的sendmail,apache这类服务的时候,这个可以很快让客户端终止连接,可以给予服务程序处理已有连接的缓冲机会,所以很多防火墙上推荐打开它) | 0 | 0 |
/proc/sys/net/ipv4/tcp_syncookies | 表示是否打开TCP同步标签(syncookie),内核必须打开了CONFIG_SYN_COOKIES项进行编译,同步标签可以防止一个套接字在有过多试图连接到达时引起过载。目的是为了防止syn flood攻击 | 1 | 1 |
/proc/sys/net/ipv4/tcp_tw_reuse | 表示是否允许将收到最后一个包超过1秒的处于TIME-WAIT状态的socket(TIME-WAIT的端口)用于新的TCP连接 。 | 0 | 1 |
/proc/sys/net/ipv4/tcp_tw_recycle | 能够更快地回收TIME-WAIT套接字。2msl(RFC 793中规定MSL为2分钟) 变成 rto | 0 | 1 |
/proc/sys/net/ipv4/tcp_fin_timeout | 对于本端断开的socket连接,TCP保持在FIN-WAIT-2状态的时间(秒)。对方可能会断开连接或一直不结束连接或不可预料的进程死亡。 | 60 | 30 |
/proc/sys/net/ipv4/ip_local_port_range | 表示TCP/UDP协议允许使用的本地端口号 | 32768 61000 | 1024 65000 |
/proc/sys/net/ipv4/tcp_max_syn_backlog | 对于还未获得对方确认的连接请求,可保存在队列中的最大数目。如果服务器经常出现过载,可以尝试增加这个数字。 | 2048 | 16384 |
/proc/sys/net/ipv4/tcp_low_latency | 允许TCP/IP栈适应在高吞吐量情况下低延时的情况,这个选项应该禁用。 | 0 | |
/proc/sys/net/ipv4/tcp_westwood | 启用发送者端的拥塞控制算法,它可以维护对吞吐量的评估,并试图对带宽的整体利用情况进行优化,对于WAN 通信来说应该启用这个选项。 | 0 | |
/proc/sys/net/ipv4/tcp_bic | 为快速长距离网络启用Binary Increase Congestion,这样可以更好地利用以GB速度进行操作的链接,对于WAN通信应该启用这个选项。 | 1 | |
tcp_syn_retries | 对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃。不应该大于255,默认值是5,对应于180秒左右时间。。(对于大负载而物理通信良好的网络而言,这个值偏高,可修改为2.这个值仅仅是针对对外的连接,对进来的连接,是由tcp_retries1决定的) | 5 | 1 |
tcp_synack_retries | 对于远端的连接请求SYN,内核会发送SYN + ACK数据报,以确认收到上一个 SYN连接请求包。这是所谓的三次握手( threeway handshake)机制的第二个步骤。这里决定内核在放弃连接之前所送出的 SYN+ACK 数目。不应该大于255,默认值是5,对应于180秒左右时间。 | 5 | 1 |
/proc/sys/net/ipv4/netfilter/ip_conntrack_max | 系统支持的最大ipv4连接数,默认65536(事实上这也是理论最大值),同时这个值和你的内存大小有关,如果内存128M,这个值最大8192,1G以上内存这个值都是默认65536 | 65535 | 65535 |
/proc/sys/net/ipv4/netfilter/ip_conntrack_tcp_timeout_established | 已建立的tcp连接的超时时间,默认432000,也就是5天。影响:这个值过大将导致一些可能已经不用的连接常驻于内存中,占用大量链接资源,从而可能导致NAT ip_conntrack: table full的问题。建议:对于NAT负载相对本机的 NAT表大小很紧张的时候,可能需要考虑缩小这个值,以尽早清除连接,保证有可用的连接资源;如果不紧张,不必修改 | 432000 | 180 |
ip_conntrack_tcp_timeout_time_wait | time_wait状态超时时间,超过该时间就清除该连接 | 120 | 120 |
ip_conntrack_tcp_timeout_close_wait | close_wait状态超时时间,超过该时间就清除该连接 | 60 | 60 |
ip_conntrack_tcp_timeout_fin_wait | fin_wait状态超时时间,超过该时间就清除该连接 | 120 | 120 |
/etc/sysctl.conf文件
/etc/sysctl.conf是一个允许你改变正在运行中的Linux系统的接口。它包含一些TCP/IP堆栈和虚拟内存系统的高级选项,可用来控制Linux网络配置,由于/proc/sys/net目录内容的临时性,建议把TCPIP参数的修改添加到/etc/sysctl.conf文件, 然后保存文件,使用命令“/sbin/sysctl –p”使之立即生效。具体修改方案参照上文:
fs.nr_open = 6553500
fs.file-max = 6553500
vm.swappiness = 1
net.ipv4.tcp_tw_recycle = 0
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
net.ipv4.ip_local_port_range = 11000 65535
net.core.netdev_max_backlog = 65535
net.core.somaxconn = 2048
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_max_syn_backlog = 262144
net.ipv4.tcp_max_tw_buckets = 20480
net.ipv4.tcp_no_metrics_save = 1
net.ipv4.tcp_syn_retries = 2
net.ipv4.tcp_synack_retries = 2
net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 15
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_intvl = 15
#disable ipv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
业务配置
- jdk serversocket 默认 50
- netty 的 backlog 默认为 50
- jetty 默认 50
- tomcat backlog 是 AcceptCount参数,默认为100,Ali-Tomcat 为200.
- nginx backlog 默认 511
具体根据系统的并发量,吞吐量,以及长连接,短链接情况 进行设置.
长连接 还要注意修改 fd 的上限(ulimit -n
)
压测调优
慢慢调整io线程数,观察服务响应时间,第三方接口响应时间,关注io处理线程的状态:jstack 统计runnable的数量, 观察 cpu load (dstat,uptime,top),观察线程使用比例,观察listen等待队列是否有堆积, 观察 netstat -s |egrep listen 是否有增长. 结合各个参数的数据,和服务的具体功能,具体分析,慢慢优化参数.JAVA代码注意观察gc 的情况.
简单介绍几个场景的分析
不依赖第三方,响应速度快
- client 60 线程压测
- 服务端 backlog 设置为 1024
- 请求 是 http 短连接
- 压力测试下,观察 io处理线程大多是wait 状态, listen 等待队列有积压, netstat -s accept 队列的overflowed次数没有增加,服务响应时间没有太大变化.cpu 使用率 不到40%,机器io,磁盘都没有到达瓶颈,gc正常
对于响应很快的短连接服务 backlog 是很大的瓶颈
,注意线程池不能盲目的给的很大,可能导致大量的 fetux 系统调用,和大量的cs , 中断 ,导致sys cpu 增加,反而影响性能.服务依赖第三方,响应快(小于2ms)
TODO- 服务依赖第三方,响应慢 (大于50 ms)
TODO
- 不依赖第三方,响应慢(大于50ms)
TODO
常见问题
syn rto, rto
- 建立链接阶段,重传时间一般是1秒
- 数据传输阶段,重传时间最小是200ms,windows是300ms,根据rtt动态变化,内网环境一般都是默认最小值
- 可以通过ip route 修改rto,
ip route change dev eth0 rto_min 20ms
syn flood 攻击
TIME_WAIT 导致连接数不够用
因为本地端口有限sysctl -a | grep net.ipv4.ip_local_port_range
,
TIME_WAIT也有限sysctl -a | grep tcp_max_tw_buckets
常见都是打开tcp_tw_recycle
,tcp_tw_reuse
- tcp_tw_reuse开启重用,重用生效条件
1)tcp_tw_reuse选项和tcp_timestamps选项也必须同时打开;
2)重用TIME_WAIT的条件是收到最后一个包后超过1s。
-tcp_tw_recycle 快速回收
一、net.ipv4.tcp_tw_recycle
功能:系统会假设对端开启了 tcp_timestamps,然后会去比较时间戳,如果时间戳变大了,就可以重用fd。开启后在 3.5*RTO 内回收,RTO 200ms~ 120s 具体时间视网络状况。内网状况比tw_reuse 稍快,公网尤其移动网络大多要比tw_reuse 慢,优点就是能够回收服务端的TIME_WAIT数量
打开后的副作用:如果对端是一个NAT网络的话(如:一个公司只用一个IP出公 网)或是对端的IP被另一台重用了,或者一个机器上多个docker Node,这个事就复杂了。建链接的SYN可能就被直接丢掉了(你可能会看到connection time out的错误) -- 这就是为什么一个公司连接在测试服务器时,会很慢,但是服务器本身负载很低的问题的原因。
建议:对外服务器不能打开该开关!!!对内服务器如果没有docker(Nat转换)的可以打开。
二、net.ipv4.tcp_timestamps
功能:记录数据包的发送时间,其为双向的选项,当一方不开启时,两方都将停用timestamps,默认打开
打开后的副作用:1.10字节的TCP header开销 2. allow you to guess the uptime of a target system
建议:保持打开状态
三、net.ipv4.tcp_tw_reuse
功能:表示开启重用。当从协议认为安全,则允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭。 协议什么情况下认为是安全的?从代码来看是收到最后一个包后超过1s :(sysctl_tcp_tw_reuse && get_seconds() - tcptw->tw_ts_recent_stamp > 1)
打开后的副作用:主动关闭方有可能在下次使用时收到上一次连接的数据包,包括关闭连接响应包或者正常通信的数据包(因为数据包需要经过MSL时间才会消失),主动关闭方会出现奇怪的表现。
建议:服务器应该打开该配置,因为内网网络状态一般比较好。
tcp_tw_recycle 导致syn抛弃
启用TIME-WAIT状态sockets的快速回收,这个选项不推荐启用。在NAT(Network Address Translation)网络下,会导致大量的TCP连接建立错误。当开启了tcp_tw_recycle选项后,当连接进入TIME_WAIT状态后,会记录对应远端主机最后到达分节的时间戳。如果同样的主机有新的分节到达,且时间戳小于之前记录的时间戳,即视为无效,相应的数据包会被丢弃.
半链接队列导致 syn丢包
在Linux内核2.2之后,分离为两个backlog来分别限制半连接(SYN_RCVD状态)队列大小和全连接(ESTABLISHED状态)队列大小。
SYN queue 队列长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,默认为2048。
Accept queue 队列长度由 /proc/sys/net/core/somaxconn 和使用listen函数时传入的参数,二者取最小值。默认为128。在Linux内核2.4.25之前,是写死在代码常量 SOMAXCONN ,在Linux内核2.4.25之后,在配置文件 /proc/sys/net/core/somaxconn 中直接修改,或者在 /etc/sysctl.conf 中配置 net.core.somaxconn = 128 。溢出时,根据tcp_abort_on_overflow
处理,tcp_abort_on_overflow指定为1(将返回RST),否则底层将不会做任何事情……这是一种委婉的退让策略,在服务端处理不过来时,让客户端误以为ACK丢失,继续重新发送ACK。这样,当服务端的处理能力恢复时,这条连接又可以重新被移动到ESTABLISHED队列中去。
CLOSE_WAIT 过多
CLOSE_WAIT 看状态图是被动关闭方的状态,在客户端关闭链接时,服务端没有做响应导致大量CLOSE_WAIT.
- 因为closewait不会主动回收,一般只能通过重启,
- keepalive 参数解决closewait问题,会定时心跳client,client关闭会返回rst关闭链接
- 应用程序问题:如果在应用程序中忘记了 close 相应的TCP连接,那么Y也就不会发出 FIN 包,进而导致 CLOSE_WAIT;
- 应用程序响应太慢:应用程序不能及时响应client的关闭请求也会导致CLOSE_WAIT状态的堆积。
- Accept backlog太大:Accept 的 backlog太大,设想突然遭遇大访问量的话,即便响应速度不慢,也可能出现来不及消费的情况,导致多余的请求还在队列里就被对方关闭了。