TCP的keepalive&HTTP的keep-alive

最近工做中遇到一个问题,想把它记录下来,场景是这样的:html

 

nginx_lvs_client

 

从上图能够看出,用户经过Client访问的是LVS的VIP, VIP后端挂载的RealServer是Nginx服务器。 Client能够是浏览器也能够是一个客户端程序。通常状况下,这种架构不会出现问题,可是若是Client端把请求发送给Nginx,Nginx的后端须要一段时间才能返回结果,超过1分30秒就会有问题,使用LVS做为负载均衡设备看到的现象就是1分30秒以后, Client和Nginx连接被断开,没有数据返回。缘由是LVS默认保持TCP的Session为90s,超过90s没有TCP报文在连接上传输,LVS就会给两端发送RESET报文断开连接。LVS这么作的缘由相信你们都知道一二,我所知道的缘由主要有两点:nginx

1.节省负载均衡设备资源,每个TCP/UDP的连接都会在负载均衡设备上建立一个Session的结构, 连接若是一直不断开,这种Session结构信息最终会消耗掉全部的资源,因此必须释放掉。 2.另外释放掉能保护后端的资源,若是攻击者经过空连接,连接到Nginx上,若是Nginx没有作合适 的保护,Nginx会由于连接数过多而没法提供服务。

这种问题不仅是在LVS上有,以前在商用负载均衡设备F5上遇到过一样的问题,F5的Session断开方式和LVS有点区别,F5不会主动发送RESET给连接的两端,Session消失以后,当连接中一方再次发送报文时会接收到F5的RESET, 以后的现象是再次发送报文的一端TCP连接状态已经断开,而另一端却仍是ESTABLISH状态。git

 

知道是负载均衡设备缘由以后,第一反应就是经过开启KeepAlive来解决。到此这个问题应该是结束了,可是我发现过一段时间总又有人提起KeepAlive的问题,甚至发现因为KeepAlive的理解不正确浪费了不少资源,本来能使用LVS的应用放在了公网下沉区,或者换成了商用F5设备(F5设备的Session断开时间要长一点,默认应该是5分钟)。因此我决定把我知道的KeepAlive知识点写篇博客分享出来。github

 

为何要有KeepAlive?

在谈KeepAlive以前,咱们先来了解下简单TCP知识(知识很简单,高手直接忽略)。首先要明确的是在TCP层是没有“请求”一说的,常常听到在TCP层发送一个请求,这种说法是错误的。TCP是一种通讯的方式,“请求”一词是事务上的概念,HTTP协议是一种事务协议,若是说发送一个HTTP请求,这种说法就没有问题。也常常听到面试官反馈有些面试运维的同窗,基本的TCP三次握手的概念不清楚,面试官问TCP是如何创建连接,面试者上来就说,假如我是客户端我发送一个请求给服务端,服务端发送一个请求给我。。。这种一听就知道对TCP基本概念不清楚。下面是我经过wireshark抓取的一个TCP创建握手的过程。(命令行基本上用TCPdump,后面咱们还会用这张图说明问题):面试

 

tcp_session_create
如今我看只要看前3行,这就是TCP三次握手的完整创建过程,第一个报文SYN从发起方发出,第二个报文SYN,ACK是从被链接方发出,第三个报文ACK确认对方的SYN,ACK已经收到,以下图:后端

tcp_syn_synack_ack

可是数据实际上并无传输,请求是有数据的,第四个报文才是数据传输开始的过程,细心的读者应该可以发现wireshark把第四个报文解析成HTTP协议,HTTP协议的GET方法和URI也解析出来,因此说TCP层是没有请求的概念,HTTP协议是事务性协议才有请求的概念,TCP报文承载HTTP协议的请求(Request)和响应(Response)。浏览器

 

如今才是开始说明为何要有KeepAlive。连接创建以后,若是应用程序或者上层协议一直不发送数据,或者隔很长时间才发送一次数据,当连接好久没有数据报文传输时如何去肯定对方还在线,究竟是掉线了仍是确实没有数据传输,连接还需不须要保持,这种状况在TCP协议设计中是须要考虑到的。TCP协议经过一种巧妙的方式去解决这个问题,当超过一段时间以后,TCP自动发送一个数据为空的报文给对方,若是对方回应了这个报文,说明对方还在线,连接能够继续保持,若是对方没有报文返回,而且重试了屡次以后则认为连接丢失,没有必要保持连接。服务器

 

如何开启KeepAlive

KeepAlive并非默认开启的,在Linux系统上没有一个全局的选项去开启TCP的KeepAlive。须要开启KeepAlive的应用必须在TCP的socket中单独开启。Linux Kernel有三个选项影响到KeepAlive的行为:
1.net.ipv4.tcpkeepaliveintvl = 75
2.net.ipv4.tcpkeepaliveprobes = 9
3.net.ipv4.tcpkeepalivetime = 7200
tcpkeepalivetime的单位是秒,表示TCP连接在多少秒以后没有数据报文传输启动探测报文; tcpkeepaliveintvl单位是也秒,表示前一个探测报文和后一个探测报文之间的时间间隔,tcpkeepaliveprobes表示探测的次数。网络

 

TCP socket也有三个选项和内核对应,经过setsockopt系统调用针对单独的socket进行设置:
TCPKEEPCNT: 覆盖 tcpkeepaliveprobes
TCP
KEEPIDLE: 覆盖 tcpkeepalivetime
TCPKEEPINTVL: 覆盖 tcpkeepalive_intvlsession

 

举个例子,以个人系统默认设置为例,kernel默认设置的tcpkeepalivetime是7200s, 若是我在应用程序中针对socket开启了KeepAlive,而后设置的TCP_KEEPIDLE为60,那么TCP协议栈在发现TCP连接空闲了60s没有数据传输的时候就会发送第一个探测报文。

 

TCP KeepAlive和HTTP的Keep-Alive是同样的吗?

估计不少人乍看下这个问题才发现其实常常说的KeepAlive不是这么回事,实际上在没有特指是TCP仍是HTTP层的KeepAlive,不能混为一谈。TCP的KeepAlive和HTTP的Keep-Alive是彻底不一样的概念。TCP层的KeepAlive上面已经解释过了。 HTTP层的Keep-Alive是什么概念呢? 在讲述TCP连接创建的时候,我画了一张三次握手的示意图,TCP在创建连接以后, HTTP协议使用TCP传输HTTP协议的请求(Request)和响应(Response)数据,一次完整的HTTP事务以下图:

http_session

各位看官请注意,这张图我简化了HTTP(Req)和HTTP(Resp),实际上的请求和响应须要多个TCP报文。从图中能够发现一个完整的HTTP事务,有连接的创建,请求的发送,响应接收,断开连接这四个过程,早期经过HTTP协议传输的数据以文本为主,一个请求可能就把全部要返回的数据取到,可是,如今要展示一张完整的页面须要不少个请求才能完成,如图片,JS,CSS等,若是每个HTTP请求都须要新建并断开一个TCP,这个开销是彻底没有必要的,开启HTTP Keep-Alive以后,能复用已有的TCP连接,当前一个请求已经响应完毕,服务器端没有当即关闭TCP连接,而是等待一段时间接收浏览器端可能发送过来的第二个请求,一般浏览器在第一个请求返回以后会当即发送第二个请求,若是某一时刻只能有一个连接,同一个TCP连接处理的请求越多,开启KeepAlive能节省的TCP创建和关闭的消耗就越多。固然一般会启用多个连接去从服务器器上请求资源,可是开启了Keep-Alive以后,仍然能加快资源的加载速度。HTTP/1.1以后默认开启Keep-Alive, 在HTTP的头域中增长Connection选项。当设置为Connection:keep-alive表示开启,设置为Connection:close表示关闭。实际上HTTP的KeepAlive写法是Keep-Alive,跟TCP的KeepAlive写法上也有不一样。因此TCP KeepAlive和HTTP的Keep-Alive不是同一回事情。

 

Nginx的TCP KeepAlive如何设置

开篇提到我最近遇到的问题,Client发送一个请求到Nginx服务端,服务端须要通过一段时间的计算才会返回, 时间超过了LVS Session保持的90s,在服务端使用Tcpdump抓包,本地经过wireshark分析显示的结果如第二副图所示,第5条报文和最后一条报文之间的时间戳大概差了90s。在肯定是LVS的Session保持时间到期的问题以后,我开始在寻找Nginx的TCP KeepAlive如何设置,最早找到的选项是keepalivetimeout,从同事那里得知keepalivetimeout的用法是当keepalivetimeout的值为0时表示关闭keepalive,当keepalivetimeout的值为一个正整数值时表示连接保持多少秒,因而把keepalivetimeout设置成75s,可是实际的测试结果代表并不生效。显然keepalivetimeout不能解决TCP层面的KeepAlive问题,实际上Nginx涉及到keepalive的选项还很多,Nginx一般的使用方式以下:

nginx

从TCP层面Nginx不只要和Client关心KeepAlive,并且还要和Upstream关心KeepAlive, 同时从HTTP协议层面,Nginx须要和Client关心Keep-Alive,若是Upstream使用的HTTP协议,还要关心和Upstream的Keep-Alive,总而言之,还比较复杂。因此搞清楚TCP层的KeepAlive和HTTP的Keep-Alive以后,就不会对于Nginx的KeepAlive设置错。我当时解决这个问题时候不肯定Nginx有配置TCP keepAlive的选项,因而我打开Ngnix的源代码,在源代码里面搜索TCP_KEEPIDLE,相关的代码以下:

又见KeepAlive


从代码的上下文我发现TCP KeepAlive能够配置,因此我接着查找经过哪一个选项配置,最后发现listen指令的so_keepalive选项能对TCP socket进行KeepAlive的配置。

又见KeepAlive

以上三个参数只能使用一个,不能同时使用, 好比sokeepalive=on, sokeepalive=off或者sokeepalive=30s::(表示等待30s没有数据报文发送探测报文)。经过设置listen 80,sokeepalive=60s::以后成功解决Nginx在LVS保持长连接的问题,避免了使用其余高成本的方案。在商用负载设备上若是遇到相似的问题一样也能够经过这种方式解决。

 

参考资料

《TCP/IP协议详解VOL1》--强烈建议对于网络基本知识不清楚同窗有空去看下。

http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/#overview

http://nginx.org/en/docs/http/ngx_http_core_module.html

Nginx Source code: https://github.com/alibaba/tengine

相关文章
相关标签/搜索