2

TIME_WAIT与Http的Keep-Alive

 2 years ago
source link: https://wakzz.cn/2020/05/04/linux/TIME_WAIT%E4%B8%8EHttp%E7%9A%84Keep-Alive/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

TIME_WAIT与Http的Keep-Alive

祈雨的博客
2020-05-04

虽然上一次服务器TIME_WAIT连接过多导致报警后,解决方案初步拟定,但是还有一个疑问:线上的服务器架构是前端 -> nginx -> server的模式,但是nginx服务器并没有触发报警,仅仅后端server服务器触发了报警。

况且是一台nginx服务器负载均衡了多台server服务器,当时每台server服务器均触发了6000的连接数阈值,但nginx上仅仅有三千多的连接数。查看连接数命令如下:

[nginx@hd2-cil-rs-nginx-01 ~]$ netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'
LAST_ACK 1
SYN_RECV 3
CLOSE_WAIT 9
ESTABLISHED 573
FIN_WAIT1 16
FIN_WAIT2 388
CLOSING 1
TIME_WAIT 3141
[nginx@hd2-cil-rs-nginx-01 ~]$ ss -s
Total: 722 (kernel 0)
TCP: 3803 (estab 560, closed 3205, orphaned 20, synrecv 0, timewait 3198/0), ports 0

Transport Total IP IPv6
* 0 - -
RAW 0 0 0
UDP 4 3 1
TCP 598 597 1
INET 602 600 2
FRAG 0 0 0

重新审视TCP的四次挥手流程,TIME_WAIT状态是TCP连接的主动关闭方会有的状态,在发出最后一个ACK包之后,主动关闭方进入TIME_WAIT状态,为了防止在端口被复用时收到了迷途包导致数据错误的情况。

image

也就是说虽然请求是前端 -> nginx -> server的流程,但是大多数请求是被server服务器主动关闭,导致server服务器的TIME_WAIT状态连接远远多于nginx服务器。具体原因,则需要通过抓包来观测http请求的关闭流程了。

TIME_WAIT的危害

TIME_WAIT状态是TCP链接中正常产生的一个状态,TIME_WAIT状态过多会存在以下的问题:

  1. TCP连接的TIME_WAIT状态结束之前,该端口号将一直无法释放。如果请求并发量很大,TIME_WAIT状态的TCP连接会把系统所有的可用端口占用,最终服务器端口耗尽无法建立新TCP连接
  2. 大量的TIME_WAIT状态也会系统一定内存和cpu资源

对于问题1,只有作为client时的TCP连接才会占用可用端口的情况,如果作为server端的TCP连接处于TIME_WAIT状态,只会占用监听端口,只需要注意Too many open files的问题,修改文件句柄限制即可;而问题2问题不大,提高服务器配置即可。

前端请求nginx

从前端请求到nginx服务器的http请求,查看nginx日志几乎都是HTTP1.1,而线上nginx默认的HTTP长长连接配置是keepalive_timeout 65即HTTP长连接超时时间为65秒,HTTP长连接超时后,则会由nginx主动关闭连接,而该TCP连接也会进入TIME_WAIT状态。

如下图,使用Chrome浏览器发起长连接后,中途浏览器发起过一次心跳包(不同的客户端实现有所出入,例如Postman发起长连接后,会每秒发起一次心跳包),最终在65秒时由nginx端主动发起了TCP关闭。

image

当然在实际使用场景下,很多用户从前端发起请求后,并不会在页面保持65秒之久,关闭页面或者浏览器后,则会由客户端主动发起TCP关闭,如下图。这时候该TCP连接在nginx服务器就会直接关闭回收,不会进入TIME_WAIT状态。

image

nginx中keepalive_timeout参数默认为65,修改为0则代表禁用keep-alive客户端连接,所有的HTTP连接均为短连接。前端客户端发起HTTP请求到nginx后,nginx返回的Response会添加Connection: close的HTTP Header通知客户端该HTTP连接已关闭,并且由nginx端主动关闭TCP连接,如下图。

location / {
...
keepalive_timeout 0;
}

image

因此强烈建议启用keepalive_timeout保持前端到nginx的长连接。如果使用HTTP短连接,当一个页面发起多个请求时,该每一个HTTP请求将会创建一个独立TCP连接,不仅耗时,而且还会使nginx服务器的TIME_WAIT连接暴增。

nginx代理后端

线上nginx负责将请求负载均衡给各个后端server服务器,使用的配置如下。该配置下,nginx请求到server的HTTP请求均为HTTP/1.0,并且为HTTP短连接。

upstream http_backend_8080 {
server 192.168.103.1:8080;
}
server {
listen 8000;
server_name localhost;

location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://http_backend_8080;
}
}

通过查看HTTP报文请求,nginx请求到server的HTTP请求的Header中有Connection: close,即通知后端server在完成本次请求的响应后,断开连接。通过抓包发现,大多数情况下,是server端主动关闭连接,如下图。

因此前端发起的HTTP请求,通过nginx负载均衡到server后,都会对应一个server端的TIME_WAIT状态的TCP连接。

image

而在少数情况下,则由nginx端主动关闭连接,如下图:

image

综上,前端到nginx端默认使用的HTTP长连接,发起多次HTTP请求使用的同一个TCP连接;nginx负载到server时,则对应了多个HTTP短连接。大多数情况下由server端主动关闭连接,就导致server端的TIME_WAIT状态连接暴增。

HTTP1.1配置

修改nginx参数,代理连接使用HTTP/1.1时,实际效果与HTTP/1.0相同。

upstream http_backend_8080 {
server 192.168.103.1:8080;
}
server {
listen 8000;
server_name localhost;

location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://http_backend_8080;
proxy_http_version 1.1;
}
}

大多数情况下,是server端主动关闭连接,如下图:

image

少数情况下,由nginx端主动关闭连接,如下图:

image

HTTP连接池配置

启用nginx的HTTP连接池后,nginx到server端的HTTP连接不再是朝生夕死的短链接,而是HTTP长连接,keepalive的时长由server端决定,例如springboot默认60秒。

upstream http_backend_8080 {
server 192.168.103.1:8080;
keepalive 256;
}
server {
listen 8000;
server_name localhost;

location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://http_backend_8080;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}

启用连接池后,HTTP连接创建后经过60秒才被server端主动关闭,可以减少大量的TCP连接创建和销毁。虽然依然是server端发起主动关闭,造成server端的TCP连接进入TIME_WAIT状态,但是TCP连接总数量大大减少,从而大幅降低了server端TIME_WAIT状态的TCP连接数量。

image

后端http调用

server端TIME_WAIT状态的TCP除了nginx负载均衡到server的HTTP请求,还有server端系统请求其他server端系统的HTTP请求。

例如java端的HTTPClient库,在默认参数下不会使用HTTP连接池,发起一个HTTP请求后,通过抓包如下图,每个HTTP请求均为短连接,且会由请求端主动发起关闭连接,因此请求端的TCP连接会进入TIME_WAIT状态。

image

这种情况相当糟糕,因为后端系统之间的调用相当频繁,甚至前端的一个请求到后端时,后端需要发起多个HTTP请求调用其他系统才能完成,而每一次请求其他系统的HTTP请求都会导致一个TIME_WAIT状态的TCP连接。这时候如果前端请求QPS上升,则会导致该server服务器TIME_WAIT状态的TCP连接数量暴增。

因此后端系统直接的调用也需要使用HTTP连接池,java端的HTTPClient库使用了HTTP连接池后,效果如下图,每个HTTP连接为长连接,并且会由被调用端主动关闭连接。不仅TCP连接数量大幅减少,而且TIME_WAIT状态也转嫁到被调用端,而非全部集中到调用端了。

image

优化前,除了前端到nginx使用HTTP长连接,其他部分例如nginx到server端,后端应用之间调用都使用的HTTP短连接,如下图,最终当前端QPS上升时大量的HTTP短连接由server0服务器发起关闭,造成该服务器TIME_WAIT状态的TCP请求数量暴增触发阈值报警。

显而易见,server0服务器成为了TCP连接数量的瓶颈。

image

优化后,所有端到端的HTTP连接均改为HTTP长连接,TCP连接的创建和销毁数量陡然下降,并且HTTP连接也不再全由server0服务器关闭,server0服务器只负责nginx代理到server端的HTTP连接关闭,从而进一步减少了该机器的TIME_WAIT状态的TCP请求数量。

image


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK