惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
IT之家
IT之家
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Security Archives - TechRepublic
Security Archives - TechRepublic
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
P
Palo Alto Networks Blog
Know Your Adversary
Know Your Adversary
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cisco Talos Blog
Cisco Talos Blog
L
Lohrmann on Cybersecurity
AWS News Blog
AWS News Blog
J
Java Code Geeks
博客园_首页
Scott Helme
Scott Helme
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
T
The Exploit Database - CXSecurity.com
Security Latest
Security Latest
V
Visual Studio Blog
Cloudbric
Cloudbric
Jina AI
Jina AI
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 叶小钗
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 聂微东
人人都是产品经理
人人都是产品经理
A
Arctic Wolf
C
Cybersecurity and Infrastructure Security Agency CISA
S
SegmentFault 最新的问题
The Last Watchdog
The Last Watchdog
SecWiki News
SecWiki News
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
W
WeLiveSecurity
K
Kaspersky official blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hacker News: Ask HN
Hacker News: Ask HN
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
宝玉的分享
宝玉的分享
Hugging Face - Blog
Hugging Face - Blog
量子位
Google Online Security Blog
Google Online Security Blog
博客园 - Franky
Simon Willison's Weblog
Simon Willison's Weblog
博客园 - 三生石上(FineUI控件)
Recent Commits to openclaw:main
Recent Commits to openclaw:main

祈雨的笔记

安全多方计算MPC spark原理解析 kueue执行源码分析 spark on k8s执行源码分析 spark-operator源码解析 系统压测遇到的缓存击穿问题 我的世界PC与安卓联机 蚂蚁金服流量投放平台的AIG改造 G1大对象致Old区占用率高 日志打印导致接口响应率下跌分析 Groovy加载类导致OOM分析 ERROR日志打印导致CPU满载 记OceanBase死锁超时 应用发版期间服务响应超时 Ark Serverless初探 系统优化复盘一二三 The user specified as a definer does not exist Kong网关初探 API网关选型调研 CPU火焰图常用工具 配置中心选型调研 root操作Nginx导致用户组错误 基于Proxifier使用代理 FastJSON字段智能匹配踩坑 Nacos初探 记一次Nginx服务器CPU满荷载故障 基于券系统分库分表的思考 limit不参与SQL成本计算致索引失效 Linux常用性能监控命令 golang低版本http2偶现400 hostname in certificate didn't match 常见对称加密原理以及应用 tcp_tw_recycle引起的TCP握手失败 记一次mysql执行DDL导致锁表 mysql磁盘占用查看 mysql对text字段update致磁盘增长 elasticsearch报错index read-only 记一次TIME_WAIT导致连接数报警 记一次生产事故OOM问题排查 redis分布式锁RedissonLock的实现细节 webservice复杂加密签名(2)java调用 webservice复杂加密签名(1)SoapUI mysql延时关联 利用中间人拦截实现APP内H5窜改 MySQL表字符集不同导致关联查询索引失效 通过SSH隧道远程办公 数据落盘方案 BeanDefinitionRegistryPostProcessor扩展 mysql空间索引 HTTPS攻击 spring循环依赖过程解析 elasticsearch性能优化 mysql IS NULL 使用索引 mysql字符集utf8mb4失效踩坑 常用加密算法 xml与javaBean转换 初探InnoDB MVCC源码实现 mysql索引原理 redis之list源码分析 redis之key过期源码分析 redis之string源码分析 redis之hash源码分析 线程池之ThreadPoolExecutor mysql数据页结构 Using temporary与Using filesort mysql回表致索引失效 springboot(28)HTTP连接池 定时任务之ScheduledThreadPoolExecutor elasticsearch常用script聚合 elasticsearch实现like查询 elasticsearch实现乐观锁 elasticsearch准实时原理 springboot(27)自定义缓存读写机制CachingConfigurerSupport optimizer tracing arthas常用命令 HTTP和HTTPS详解 redis集群选举机制 kafka消息重试 一点压力测试的经验 kafka架构概念 explain分析sql语句字段的解释 JVM问题分析处理手册 logstash过滤器(2)date logstash过滤器(3)dissect logstash编码器(1)json logstash编码器(2)multiline logstash表达式 logstash输入(1)通用选项 logstash输入(3)file logstash过滤器(1)通用选项 logstash输入(2)stdin logstash安装 记一次前端vConsole导致JSON序列化错误排查 解决多个异步操作嵌套问题 fastjson反序列化失败autoType is not support RTMP串流服务 POI自动调整列宽错误 Nginx+Lua实现动态黑名单 使用curl命令模拟POST和GET请求 ResponseEntity下载文件
TIME_WAIT与Http的Keep-Alive
祈雨的笔记 · 2020-05-03 · via 祈雨的笔记

背景

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

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

1
2
3
4
5
6
7
8
9
[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
1
2
3
4
5
6
7
8
9
10
11
[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连接,如下图。

1
2
3
4
location / {
...
keepalive_timeout 0;
}

image

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

nginx代理后端

默认配置

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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