我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

Posted 高效运维

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制相关的知识,希望对你有一定的参考价值。


我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

对于广大IT从业者来说,Keepalive是个耳熟能详的网络概念,Keepalive 即保持连接,不论是操作系统、web服务器、应用服务器、负载均衡设备、防火墙等都会涉及到这个概念。
如何正确保障网络连接状态,什么时候需要断开,什么时候需要保持,是每个管理员都应该考虑的问题。Keepalive有HTTP Keepalive和TCP Keepalive之分,这两个概念对于网络连接保持和断开的时间都非常重要。
本文旨在通过实验的方式对nginx的keepalive相关参数,在不同场景下的使用进行介绍和比较,管中窥豹帮助大家对这个概念有个更全面和深入的了解,以及在典型场景下连接被异常断开时该如何应对。

场景一:Nginx的HTTP Keepalive功能

首先介绍一下“keepalive_timout”参数,这个参数的时间值意味着:一个http连接在传输完最后一个网络报文后,到它被服务端主动关闭之前的最长空闲时间。当httpd守护进程发送完一个响应后,理应马上主动关闭相应的http连接,但设置 keepalive_timeout后,httpd 守护进程会想说:”再等等吧,看看客户端还有没有请求过来”,这一等,便是 keepalive_timeout时间。

如果守护进程在这个等待的时间里,一直没有收到客户端发过来的 http请求,则关闭这个 http 连接。

实验场景:Nginx作为静态资源服务器,使用默认配置,此时keepalive_timeout的值为65秒,基本配置参数如下:
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
server {
listen 8000;
server_name localhost;
location / {
root html;
index index.html index.htm;
}
}

使用IE浏览器访问url,正常得到结果后隔一段时间刷新页面,之后再隔一段时间后第二次刷新页面(两次等待的时间间隔小于上面配置的65秒)。整个过程使用tcpdump抓取nginx服务监听端口8000的网络包,结果如下:

我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

从上图可以清晰的看出在TCP三次握手建立连接后,发送第一次http请求,之后分别在11:13:02 秒和 11:13:17 秒两次刷新又发送了两次 http 请求,再之后过了65秒在11:14:22 秒nginx主动发送FIN包经过4次握手断开了浏览器的连接。该实验说明keepalive_timeout是从最后一个客户端http请求被服务端响应后开始计时的,并且是由服务端nginx主动关闭连接的。

如果将IE浏览器换为火狐浏览器,同样的操作抓包结果会有什么不同呢?

我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

火狐浏览器默认会发送TCP KeepAlive探测包到服务端,当到达keepalive_timeout时间后,nginx依旧会关闭tcp连接。该实验说明nginx作为静态资源服务器时并不会处理TCP Keep-Alive探测报文,这种四层网络探测报文到达后是由操作系统直接做出应答的。

场景二:连接异常关闭、客户端不能接收到响应的情况

Nginx 作为代理转发服务器时,如果上游服务器响应时间比较长,有时客户端不能正常收到响应。在这种情况下,服务端返回请求前,nginx 的 proxy_read_timeout 参数超时生效,会主动发起关闭到服务端的连接的请求,之后即使服务端将请求处理完毕,客户端也不能接收到响应了。
实验场景:在浏览器->nginx->服务端的场景下,其中服务端程序 sleep60 秒后返回结果,将 proxy_read_timeout 默认的 60 秒改为30 秒,keepalive_timeout 使用默认值65 秒,基本配置参数如下:
upstream WLS {
server 10.10.10.106:17101;
}
server {
server_name testWLS;
listen 9000;
error_log logs/testWLS_debug.log;
location /testWLS {
proxy_pass http://WLS;
proxy_http_version 1.1;
proxy_method POST;
proxy_read_timeout 30s;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

浏览器正常访问url后等待响应,在整个过程中网络抓包内容如下:

nginx->服务端网络包

我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

从上图可看出nginx将请求转发给后台服务器后没有得到响应,过了30秒后 proxy_read_timeout 参数生效,nginx 主动发送FIN包关闭连接,但此时连接并未立刻关闭,之后又过了 30 秒即服务端 sleep60 时间达到后服务端返回响应,但此时的连接状态已经不能正常处理响应报文。

浏览器->nginx网络包(和上个网络包不是同一个实验下的抓取的)

我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

从上图可以看出浏览器发送请求没有得到响应,在达到 proxy_read_timeout 30 秒后,nginx 向浏览器发送 504 Gateway Time-out 包,但这时网络连接并未关闭,在这之后空闲了 keepalive_timeout 65 秒后 nginx 才主动断开连接(这期间浏览器一直在发送TCP Keep-Alive 探测报文)。
因此,场景一中介绍的keepalive_timeout超时时间,是从处理请求并传送完响应报文后才开始计时的,如果服务端由于种种原因迟迟没有返回请求,这个timeout始终不会开始计时,一般在这种情况下客户端会先超时并断开连接。

场景三:Nginx的TCP Keepalive功能

首先介绍一下”so_keepalive”参数,Nginx使用该参数来实现TCP keepalive,该参数位于listen指令当中,实现了对操作系统TCP keepalive三个参数:
tcp_keealive_time、tcp_keepalive_intvl、tcp_keepalive_probes的覆盖(即该参数优先于系统层的tcp keepalive),该参数具体信息如下:

我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

on表示开启;off表示关闭;keepidle表示等待时间,keepintvl表示探测报文发送的时间间隔,keepcnt表示探测报文发送的次数。以上三个参数只能使用一个,不能同时使用,比如so_keepalive=on, sokeepalive=off或者sokeepalive=30s::(表示等待30s没有数据报文发送探测报文,省略则表示使用系统默认的参数)。
实验场景:浏览器->Nginx->Nginx,上游服务器nginx设置so_keepalive,在nginx1.15.3版本后,到上游服务器建立连接也支持keepalive_timeout参数,在代理转发服务器nginx的keepalive_timeout超时之前,上游nginx发送tcp keepalive探测包,基本配置如下:

代理转发服务器配置

upstream rrups {
server 1.1.1.106:8011;
keepalive_timeout 60;
}
server {
server_name rrups.test;
listen 8000;
#error_log logs/rrups.log debug;
location / {
proxy_pass http://rrups;
proxy_http_version 1.1;
proxy_method POST;
proxy_set_header Connection "";
}
}

上游服务器配置

server {
listen 8011 so_keepalive=15s:20s:;
default_type text/plain;
return 200 "106 server port 8011 response!";
}
代理转发服务器keepalive_timeout超时时间设置为60秒,上游服务器Tcp keepalive设置为15秒没有数据报文则发送探测报文,之后每隔20秒发送一次,发送次数使用系统默认的值。
浏览器访问代理转发nginx的监听端口8000,nginx将请求转发到106主机的8011端口(为了方便测试只配置了一台上游服务器),浏览器首次访问正常得到结果后,空闲一段时间刷新页面再次得到结果(间隔小于15秒),在代理转发服务器上使用tcpdump全程抓取到上游服务器的网络包,结果如下:

我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

代理转发服务器keepalive_timeout超时时间设置为60秒,上游服务器Tcp keepalive设置为15秒没有数据报文则发送探测报文,之后每隔20秒发送一次,发送次数使用系统默认的值。

浏览器访问代理转发nginx的监听端口8000,nginx将请求转发到106主机的8011端口(为了方便测试只配置了一台上游服务器),浏览器首次访问正常得到结果后,空闲一段时间刷新页面再次得到结果(间隔小于15秒),在代理转发服务器上使用tcpdump全程抓取到上游服务器的网络包,结果如下:

我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

从上图可以看出 9:57:19 秒代理转发服务器 nginx(ip67) 主动创建连接到上游服务器nginx(ip106),并转发第一次HTTP报文,之后在 9:57:31 秒发送第二个HTTP请求,代理转发服务器 nginx 继续将请求转发到上游服务器nginx,之后空闲了15秒在 9:57:46 秒上游服务器 nginx 发送第一个 TCP keepalive 探测报文,之后每隔20秒又发送了两次探测报文,最后在9:58:31秒代理转发服务器nginx主动断开了连接,此时距离第二次转发HTTP 请求整整过了60秒。
TCP keepalive 探测报文并没有重置 keepalive_timeout 的超时时间,这是什么原因呢?仔细分析tcpdump可以看出:nginx作为代理转发服务器时,在处理完HTTP请求并把结果返回给客户端后,这期间上游服务器虽然发送了长度为0的TCP的探测报文(tcpdump截图可见),但nginx并没有解析处理,而是由操作系统直接返回ack回复报文的。
该实验也佐证了实验一中得出的结论:TCP Keep-Alive探测报文并不能重置Nginx的keepalive_timeout超时时间。

思考

类似Nginx的keepalive_timeout设置的是七层HTTP协议的超时时间,并不会处理TCP/IP四层网络模型的探测报文。那么类似Nginx的TCP KeepAlive是不是没有意义呢?答案当然是否定的。

在现实的生产环境中客户端和服务端中间,往往会多一层或几层的网络设备如LVS或防火墙等,如果这些设备的空闲连接超时时间设置不当,再加上服务端响应时间过长,很容易会出现连接断开导致客户端不能正常接收响应结果的问题。

但是,这些设备是可以捕获并处理TCP探测报文的,此时便可以通过设置类似Nginx的so_keepalive参数,或者直接修改操作系统默认的TCP Keepalive参数,在这些设备空闲连接超时前发送TCP探测报文,告诉这些设备连接是活动的暂时不要断开。

引申

并不是所有软件的keepalive发送的都是TCP探测报文,比如大家熟悉的中间件产品WebLogic,它的WTC组件(WebLogic Tuxedo Connector)有两个参数:KeepAlive、KeepAliveWait。
功能上和上面介绍的sokeepalive参数类似,表示空闲多长时间发送探测包以及多长时间没收到响应就关闭连接。但这个探测包并不是空的,是WebLogic和Tuxedo相互可以解析并处理的报文,tcpdump分析如下(KeepAlive参数设置为60秒):

我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制

其实上面介绍的WTC探测属于应用层心跳探测,应用层心跳实现在第七层,so_keepalive是实现在TCP协议栈(四层即传输层),本质没有任何区别,但应用层需要自己来定义心跳包格式。既然有了TCP心跳,为什么还要应用层心跳呢?对于TCP心跳,只要当前连接是可用的,对方就会ACK我们的心跳,而对于当前对端应用是否能正常提供服务,TCP层的心跳机制是无法获知的。

我们在应用层实现的心跳是依赖于对端应用的,如果对端当前无法正常提供服务,通过应用层心跳马上就可以获知,以便后续进行相应的处理。此外,TCP心跳超时时间一般较长,无法给应用提供快速的反馈。所以类似BGP协议就独立实现了自己的keepalive,最小可以设置一秒钟,三次没有应答即可以Reset连接,最快三秒就可以检测到失效。

总结

Keepalive相关参数使用的上下文和使用场景不尽相同。需要注意的是,这些参数的设置只能保证参数设置方在设置的条件范围内不会主动断开连接,但不能保证其上下游会先断开连接,在设置的时候需要综合考虑相关上下游服务器或网络设备的keepalive设置。


当出现连接被无故断开的时候,首先应查看相关软件和系统设备日志,一般会得到“连接超时/网络断开”类似的错误信息,然后需要排查请求链路上的相关软件和系统设备的超时时间设置,之后我们需要在请求链路上的相关服务器设备上收集tcpdump,找到主动断开连接的一方,并且计算出连接是在保持多长时间后被断开的,进而判断出是哪个参数生效的,最后我们需要根据实际情况调整相关参数或优化系统架构。


想与这么优质的文章作者面对面嗨聊?GOPS 2020 全球运维大会 · 深圳站,光大证券网络安全总监分享精彩议题。


相关好文推荐:

投稿邮箱:jiachen@greatops.net,或添加联系人微信:135 2116 9787.
来源:本文转自公众号 匠心独运维妙维效,。
你点的每个赞,我都认真当成了喜欢

以上是关于我就是认真:正确保障网络连接状态的通断,一文读懂 Keepalive 工作机制的主要内容,如果未能解决你的问题,请参考以下文章

云原生周刊:一文读懂 Pod 网络 | 2023.4.10

网络编程之一文读懂HTTP协议的历史演变和设计思路

一文读懂连接池技术原理设计与实现

一文读懂 DevOps与SRE 的来龙去脉

一文读懂神经网络

一文读懂I/O多路复用技术