Nginx学习:HTTP核心模块(五)长连接与连接处理
HTTP 基础知识大家掌握的怎么样呀?对于长连接这一块的内容应该也不是什么新鲜东西了吧。毕竟 HTTP1.1 都已经发布这么久了。今天主要来看的就是长连接相关的配置,另外还会介绍几个和连接有关的内容。同时,今天的内容除了 HTTP 外,还需要一点 TCP 的知识。没办法,毕竟 Ngxin 本身就是一个网络代理服务器软件,离不开的就是各种网络相关的知识。相信大家肯定没问题的,毕竟很早前在短视频中就说过,基础能比我差的程序员估计在全国范围内都找不到几个了。
但是不要脸和硬着头皮啃的水平咱还是有的,因此,如果有遗漏或错误的地方,也恳请各位大佬在评论区指出。
好了,不多废话了,进入主题吧。
长连接
关于长连接的知识,属于 HTTP 的基础知识了,咱们不多说,不了解的小伙伴可以去查阅下相关资料。简单来说,一次 HTTP 连接,就要经历 TCP 的三次握手四次挥手,毕竟它是处于网络的第七层,同时也是基于第四层的 TCP 来实现的。如果请求很多,需要不停地建立 TCP 连接,效率明显下降。而长连接则是一次连接后保持这个连接一段时间,如果有其它的请求就可以复用这条连接,从而减少网络连接开销,提升效率。比如很多门户或者电商站,一打开就是一大堆的 JS、图片之类的请求。如果每一个都要单独建立连接,势必会影响页面的整体打开速度。
同时,在进行反向代理的时候,也可以启用长连接功能,减少后端代理的连接次数。反向代理相关的配置我们在后面学习反向代理相关的内容时再说,现在学习的主要是针对 http、server、location 模块的长连接配置。
在 Nginx 中,有完整的长连接配置。
keepalive_disable
指定哪些浏览器不使用长连接功能,或者说是针对行为异常的浏览器关闭长连接功能。
keepalive_disable none | browser ...;
默认 msie6 , 值为 msie6 表示在遇到POST请求时,关闭与老版本 MSIE 浏览器建立长连接。 值为 safari 表示在遇到 Mac OS X 和类 Mac OS X 操作系统下的 Safari 浏览器和类 Safari 浏览器时,不与浏览器建立长连接。 值为none表示为所有浏览器开启长连接功能。
在 nginx 1.1.18 版本及以前,safari 将匹配所有操作系统上的 Safari 和类 Safari 浏览器,并默认不与这些浏览器建立长连接。
我本地是 Mac 电脑,因此,直接设置一个 safari 的配置。
http {
……
keepalive_disable safari;
……
}
配置完成后,使用 Safari 和 Chrome 分别测试,可以看到 Safari 的 Response Header 中的 Connection 的值变成了 close ,而 Chrome 还是正常的 keep-alive 。
keepalive_requests
设置通过一个长连接可以处理的最大请求数。 请求数超过此值,长连接将关闭。
keepalive_requests number;
在版本1.19.10之前,默认值为100,现在默认值是 1000 。定期关闭连接对于释放每个连接的内存分配是必要的。因此,使用过高的最大请求数可能会导致内存使用过多,因此不建议使用。
我们直接配置一个,将它的 number 设置为 2 ,然后建立一个 html 页面以及一堆空的 js 文件。
// nginx.conf
http {
……
keepalive_requests 2;
……
}
// testkeepalive1.html
~
~
"testkeepalive1.html" 23L, 461C 20,19-26 All
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
This is testkeepalive1.html
<script src="js1/1.js"></script>
<script src="js1/2.js"></script>
<script src="js1/3.js"></script>
<script src="js1/4.js"></script>
<script src="js1/5.js"></script>
</body>
</html>
好了,现在访问 http://192.168.56.88/testkeepalive1.html 页面,查看所有的请求,目前我们算上这个 html 页面,一共会有 6 个请求。你会发现有的请求的 Connection 会变成 Close ,也就是说一条长连接在请求数量达到设置的值之后就断了,然后又是一个新的请求连接。
一般情况下不需要刻意设置这个值,上面文档的说明中也说过了使用过高的最大请求数可能会导致内存使用过多,正常来说 1000 已经相当够用了,即使是淘宝这样的网站,首页上的请求数也没多少,毕竟大家还会合并请求及图片来进行连接的优化。
keepalive_time
限制通过一个保持活动连接处理请求的最长时间。
keepalive_time time;
达到此时间后,连接在后续请求处理后关闭,默认值是 1h 。它是 Nginx 1.19.10 之后新出的配置,咱们就不做详细的测试了,如果想要测试的同学,可以根据下面的 keepalive_timeout 测试,然后访问一个动态能够 sleep 的请求路径,让 sleep 时间超过这个配置的时间,接着使用一个静态页面来定时发送 ajax 请求查看连接复用情况。比较麻烦,日常估计使用的小伙伴也不会很多,它默认的 1h 估计很多耗时连接也不可能去做这么长时间的操作。
keepalive_timeout
设置客户端的长连接在服务器端保持的最长时间
keepalive_timeout timeout [header_timeout];
在 timeout 设置的时间内,客户端未发起新请求,则长连接关闭。第二个参数为可选项,设置 “Keep-Alive: timeout=time” 响应头的值,可以为这两个参数设置不同的值。“Keep-Alive: timeout=time” 响应头可以被 Mozilla 和 Konqueror 浏览器识别和处理,MSIE 浏览器在大约 60 秒后会关闭长连接。
默认安装完成之后的 Nignx 配置中,这个选项被设置为 65 。如果发现 Nginx 占用服务器的 CPU 特别高,可以尝试调低这个时间,或者直接设置成 0 ,设置成 0 后将整个关闭 keepalive 长连接的功能。
现在我们一起来进行测试,首先添加如下配置。
http {
……
keepalive_timeout 0;
……
}
重载配置后使用一个 Linux 命令查看连接情况。
[root@localhost html]# netstat -nat|grep -i "80"
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
看来目前还没有连接过来,我们可以访问之前的 /testkeepalive1.html ,马上就能看到有新的连接建立了。
[root@localhost html]# netstat -nat|grep -i "80"
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 192.168.56.88:80 192.168.56.1:49816 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:49811 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:49810 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:49814 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:49805 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:49812 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:49813 TIME_WAIT
因为我们将 keepalive_timeout 设置成 0 了,所以现在有 7 条连接建立成功,注意,我使用的是 Chrome ,所以还会自带一个 /favicon.ico 请求,加上前面的 html 以及 5 个 js ,正好 7 个连接。如果速度快点过来查看的话,可以看到连接是 ESTABLISHED 状态,表示连接正在使用,TIME_WAIT 表示连接在等待中。
接下来,我们修改 keepalive_timeout 为 5 ,意思就是 5 秒,如果没有使用,就会用新的连接。现在再次访问,会看到依然建立了 5 个连接,毕竟我们相当于同时请求本地的虚拟机,速度还是够快的,只有两个连接被复用了,其它的还是建立了连接。另外,默认 Chrome 可以同时建立 6 个连接,你可以测试再多加几个 js ,然后试试,最多它就只能建立 6 个连接,前面的 js 没有加载完,后面的就会等待加载,这里和长连接无关,即使不使用长连接,也会在 6 个连接中的某一个连接释放之后才会建立新的连接。
[root@localhost html]# netstat -nat|grep -i "80"
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 192.168.56.88:80 192.168.56.1:52469 ESTABLISHED
tcp 0 0 192.168.56.88:80 192.168.56.1:52471 ESTABLISHED
tcp 0 0 192.168.56.88:80 192.168.56.1:52470 ESTABLISHED
tcp 0 0 192.168.56.88:80 192.168.56.1:52472 ESTABLISHED
tcp 0 0 192.168.56.88:80 192.168.56.1:52465 ESTABLISHED
在 5 秒内,刷新页面,不会有新的连接出现,但是等待 5 秒后,再次刷新,就会发现又有新的连接建立了。
[root@localhost html]# netstat -nat|grep -i "80"
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
tcp 0 0 192.168.56.88:80 192.168.56.1:52469 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:52471 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:52692 ESTABLISHED
tcp 0 0 192.168.56.88:80 192.168.56.1:52691 ESTABLISHED
tcp 0 0 192.168.56.88:80 192.168.56.1:52690 ESTABLISHED
tcp 0 0 192.168.56.88:80 192.168.56.1:52686 ESTABLISHED
tcp 0 0 192.168.56.88:80 192.168.56.1:52693 ESTABLISHED
tcp 0 0 192.168.56.88:80 192.168.56.1:52470 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:52472 TIME_WAIT
tcp 0 0 192.168.56.88:80 192.168.56.1:52465 TIME_WAIT
再等待一段时间后,所有连接全部关闭。这里关闭连接的时间是操作系统决定的,与操作系统被动断开 TCP 连接的配置有关,主要是 net.ipv4.tcp_tw_reuse 这个配置,大家可以自己查阅相关的资料。正常情况下是在 TIME_WAIT 状态后等待大约 2 分钟,最低可以修改为 1 分钟,通过下面的命令。这个是操作系统的固定值,最低只能是 60 秒,也就是这个 1 分钟。
[root@localhost html]# sysctl net.ipv4.tcp_tw_reuse=1
[root@localhost html]# sysctl -p
这样一分钟以后这些连接信息就会消失了。设置成 0 不管用的,一样会等待,客户端关掉浏览器也没用。这个命令真实的作用其实是如果连接是安全可控的,可以复用 TIME_WAIT 的连接为新的连接所用。它可以快速复用处于 TIME_WAIT 状态的 TCP 连接,也就相当于缩短 TIME_WAIT 状态的持续时间。只有客户端主动关闭连接才会让服务器的正常关闭,使用 curl 命令测试就可以看到效果,你不会看到任何等待的连接。
关于 TIME_WAIT ,其实是为了能够正确、自然地进行 TCP 四次挥手而预留的等待时间,更具体的内容,大家可以再自行查阅 WSL(最大报文生产周期) 相关的知识点。同时,net.ipv4.tcp_tw_reuse 也要慎用,当客户端与服务端主机时间不同步时,客户端的发送的消息有可能会被直接拒绝掉。正式环境使用时,建议:别动!保持默认就好。
长连接总结
关于长连接的内容写了这么多,但其实也仅仅只是四个配置指令而已。更重要的其实是对于 HTTP 基础知识的学习,长连接现在在 Nginx 中是默认打开的,这几个选项也都是有默认值配置好的。所以平常其实我们不太需要去关心他们的配置。就像上面说的,如果发现 CPU 莫名升高,而且是因为 Nginx 的话,那么可以适当调节部分参数。但 CPU 的问题也不一定仅仅是连接的问题,gzip 同样也会带来 CPU 的压力。因此,调优是一个综合的活,要找到问题所在才好应对,比如我们还可以查看连接中 TIME_WAIT 的情况来看是不是连接非常多,这时更好的方案其实是要做负载均衡分散压力了。
连接处理
连接处理主要是针对 Nginx 如何来关闭客户端连接的一些配置操作。Nginx 在接收客户端的请求时可能由于客户端或服务端出错了,要立即响应错误信息给客户端,而 Nginx 在响应错误信息后大分部情况下是需要关闭当前连接的。Nginx 执行完 write() 系统调用(操作系统函数,参考网络编程或操作系统相关资料)把错误信息发送给客户端,write() 系统调用返回成功并不表示数据已经发送到客户端,有可能还在 tcp 连接的 write buffer 里。所以当在某些场景下出现 tcp write buffer 里的数据在 write() 系统调用之后到 close() 系统调用执行之前没有发送完毕,且 tcp read buffer 里面还有数据没有读,close() 系统调用会导致客户端收到 RST 报文且不会拿到服务端发送过来的错误信息数据。
所以,解决问题的重点是,让服务端别发 RST 包。或者说延迟发送,这就是下面要讲的 lingering_close 所要解决的问题。
上面的概念看着就很晕吧,如何测试我也没找到相关的资料,自己也尝试了半天没有什么效果。所以对这一块有了解的同学可以评论区留言哦。在实际应用中,是否应该打开 lingering_close 呢?这个就没有固定的推荐值了,lingering_close 的主要作用是保持更好的客户端兼容性,但是却需要消耗更多的额外资源(比如连接会一直占着)。因此,秉承对于不懂的东西,默认的就是最好的原则,咱们保持默认状态就好了。将来如果学习或者接触到这一块的内容了,再写文章和录视频进行详细的学习吧。
lingering_close
控制 Nginx 如何关闭客户端连接。
lingering_close off | on | always;
它的默认值是 on ,指示 Nginx 在完成关闭连接前等待和处理客户端发来的额外数据。但只有在预测客户端可能发送更多数据的情况才会做此处理。为了控制关闭HTTP/2连接,必须在 server 下(1.19.1)指定该指令。
- always 指示 Nginx 无条件等待和处理客户端的额外数据。
- off 指示nginx立即关闭连接,而绝不等待客户端传送额外数据。 这样做破坏了协议,所以正常条件下不应使用。
lingering_time
lingering_close 生效时(非off),这条指令定义 Nginx 处理(读取但忽略)客户端额外数据的最长时间。
lingering_time time;
默认值是 30s,超过这段时间后,Nginx 将关闭连接,不论是否还有更多数据待处理。
lingering_timeout
lingering_close 生效时(非off),这条指令定义 Nginx 等待客户端更多数据到来的最长时间。
lingering_timeout time;
默认值是 5s ,如果在这段时间内,Nginx 没有接收到数据,Nginx 将关闭连接。否则,Nginx 将接收数据,忽略它,然后再等待更多数据。 这个“等待——接收——忽略”的循环一直重复,但总时间不会超过 lingering_time 指令定义的时间。
总结
怎么说呢,学这些真的想回去好好再补补网络知识了。就这么点内容,也已经是边查资料边测试边写了,后面还有那么多配置以及牵涉到的相关知识,想想都头大。不过没关系,谁让自己喜欢这行呢,各位同学是不是也感觉到每次看完文章或者视频也会跟我一样多少会有一点点的进步呢?
长连接一般我们都会简单配置一下,通常也是以 keepalive_time 和 keepalive_time_out 的配置为主,其它两个说实话,没学之前我都不知道有这俩货。另外一个连接处理相关的配置更是从来没用过。但是,现在起码我们了个印象,将来或许哪天它们就能为我们解决大问题呢。
参考文档: