TCP和HTTP中的KeepAlive机制总结

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"什么是KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"KeepAlive可以简单理解为一种状态保持或重用机制,比如当一条连接建立后,我们不想它立刻被关闭,如果实现了KeepAlive机制,就可以通过它来实现连接的保持"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP的KeepAlive在HTTP 1.0版本默认是关闭的,但在HTTP1.1是默认开启的;操作系统里TCP的KeepAlive默认也是关闭,但一般应用都会修改设置来开启。因此网上TCP流量中基于KeepAlive的是主流"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP的KeepAlive和TCP的KeepAlive有一定的依赖关系,名称又一样,因此经常被混淆,但其实是不同的东西,下面具体分析一下"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"TCP为什么要做KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们都知道TCP的三次握手和四次挥手。当两端通过三次握手建立TCP连接后,就可以传输数据了,数据传输完毕,连接并不会自动关闭,而是一直保持。只有两端分别通过发送各自的 "},{"type":"codeinline","content":[{"type":"text","text":"FIN"}]},{"type":"text","text":" 报文时,才会关闭自己侧的连接。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个关闭机制看起来简单明了,但实际网络环境千变万化,衍生出了各种问题。假设因为实现缺陷、突然崩溃、恶意攻击或网络丢包等原因,一方一直没有发送 "},{"type":"codeinline","content":[{"type":"text","text":"FIN"}]},{"type":"text","text":" 报文,则连接会一直保持并消耗着资源,为了防止这种情况,一般接收方都会主动中断一段时间没有数据传输的TCP连接,比如LVS会默认中断90秒内没有数据传输的TCP连接,F5会中断5分钟内没有数据传输的TCP连接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但有的时候我们的确不希望中断空闲的TCP连接,因为建立一次TCP连接需要经过一到两次的网络交互,且由于TCP的 "},{"type":"codeinline","content":[{"type":"text","text":"slow start"}]},{"type":"text","text":" 机制,新的TCP连接开始数据传输速度是比较慢的,我们希望通过连接池模式,保持一部分空闲连接,当需要传输数据时,可以从连接池中直接拿一个空闲的TCP连接来全速使用,这样对性能有很大提升"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了支持这种情况,TCP实现了KeepAlive机制。KeepAlive机制并不是TCP规范的一部分,但无论Linux和Windows都实现实现了该机制。TCP实现里KeepAlive默认都是关闭的,且是每个连接单独设置的,而不是全局设置"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Implementors MAY include \"keep-alives\" in their TCP implementations, although this practice is not universally accepted. If keep-alives are included, the application MUST be able to turn them on or off for each TCP connection, and they "},{"type":"text","marks":[{"type":"strong"}],"text":"MUST default to off"},{"type":"text","text":"."}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外有一个特殊情况就是,当某应用进程关闭后,如果还有该进程相关的TCP连接,一般来说操作系统会自动关闭这些连接"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"如何开启TCP的KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP的KeepAlive默认不是开启的,如果想使用,需要在自己的应用中为每个TCP连接设置"},{"type":"codeinline","content":[{"type":"text","text":"SO_KEEPALIVE"}]},{"type":"text","text":" 才会生效"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Java中,应用程序一般通过设置 "},{"type":"codeinline","content":[{"type":"text","text":"java.net.SocketOptions"}]},{"type":"text","text":" 来开启TCP连接的KeepAlive"}]}]}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"/**\n * When the keepalive option is set for a TCP socket and no data\n * has been exchanged across the socket in either direction for\n * 2 hours (NOTE: the actual value is implementation dependent),\n * TCP automatically sends a keepalive probe to the peer. This probe is a\n * TCP segment to which the peer must respond.\n * One of three responses is expected:\n * 1. The peer responds with the expected ACK. The application is not\n * notified (since everything is OK). TCP will send another probe\n * following another 2 hours of inactivity.\n * 2. The peer responds with an RST, which tells the local TCP that\n * the peer host has crashed and rebooted. The socket is closed.\n * 3. There is no response from the peer. The socket is closed.\n *\n * The purpose of this option is to detect if the peer host crashes.\n *\n * Valid only for TCP socket: SocketImpl\n *\n * @see Socket#setKeepAlive\n * @see Socket#getKeepAlive\n */\n@Native public final static int SO_KEEPALIVE = 0x0008;"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Java Docs里对 "},{"type":"codeinline","content":[{"type":"text","text":"SO_KEEPALIVE"}]},{"type":"text","text":" 的工作机制做了比较详细的说明,具体来说就是,如果某连接开启了TCP KeepAlive,当连接空闲了两个小时(依赖操作系统的 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp_keepalive_time"}]},{"type":"text","text":" 设置),TCP会自动发送一个KeepAlive探测报文给对端。对端必须回复这个探测报文,假设对端正常,就可以回复ACK报文,收到ACK后该连接就会继续维持,直到再次出现两个小时空闲然后探测;假设对端不正常,比如重启了,应该回复一个RST报文来关闭该连接。假设对端没有任何响应,TCP会每隔75秒(依赖操作系统的 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp_keepalive_intvl"}]},{"type":"text","text":" 设置)再次重试,重试9次(依赖OS的 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp"},{"type":"text","marks":[{"type":"italic"}],"text":"keepalive"},{"type":"text","text":"probes"}]},{"type":"text","text":" 设置)后如果依然没有回复则关闭连接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Linux中KeepAlive相关的配置可以通过如下方式查看"}]}]}]},{"type":"codeblock","attrs":{"lang":"bash"},"content":[{"type":"text","text":"chendw@chendw-PC:~$ sysctl -a | grep keepalive\nnet.ipv4.tcp_keepalive_time = 7200\nnet.ipv4.tcp_keepalive_intvl = 75\nnet.ipv4.tcp_keepalive_probes = 9"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"HTTP为什么要做KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP虽然是基于有连接状态的TCP,但本身却是一个无连接状态的协议,客户端建立连接,发出请求,获取响应,关闭连接,然后整个流程就结束了;当有新的HTTP请求,则使用新建立的TCP连接。老的连接一般会被客户端浏览器或服务器关闭,此时由于是两端主动发的 "},{"type":"codeinline","content":[{"type":"text","text":"FIN"}]},{"type":"text","text":" 报文,因此即使TCP已经设置了KeepAlive,TCP连接也会被正常关闭"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这种模式下每个HTTP请求都会经过三次握手创建新的TCP,再加上TCP慢启动的影响,以及单个网页里包含越来越多的资源请求,因此效果并不理想。为了提升性能,HTTP规范也提出了KeepAlive机制,HTTP请求携带头部 "},{"type":"codeinline","content":[{"type":"text","text":"Connection: Keep-Alive"}]},{"type":"text","text":" 信息,告知服务器不要关闭该TCP连接,当服务器收到该请求,完成响应后,不会主动主动关闭该TCP连接。而浏览器当然也不会主动关闭,而是在后续请求里复用该TCP连接来发送下一个HTTP请求"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP1.0默认不开启KeepAlive,因此要使用的话需要浏览器支持,在发送HTTP请求时主动携带 "}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Connection: Keep-Alive"}]},{"type":"codeinline","content":[{"type":"text","text":"头部,应用服务器同样也要支持;而HTTP1.1规范明确规定了要默认开启KeepAlive,所以支持HTTP1.1的浏览器不需要显式指定,发送请求时会自动携带该头部,只有在想关闭时可以通过设置 "}]},{"type":"codeinline","content":[{"type":"text","text":"Connection: Close"}]},{"type":"text","text":" 头部告知对端"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,HTTP的KeepAlive机制还提供了头部 "},{"type":"codeinline","content":[{"type":"text","text":"Keep-Alive: max=5, timeout=120"}]},{"type":"text","text":" 来控制连接关闭时间,比如如上头部就表示该TCP连接还会保持120秒,max表示可以发送的请求数,不过在非管道连接下会被忽略,我们基本都是非管道连接,因此可以忽略"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP/2为每个域名使用单个TCP连接,本身就是连接复用,因此请求不再需要携带头部来开启KeepAlive"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"HTTP的KeepAlive和TCP的KeepAlive的关系"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从上面可以看出,虽然都叫KeepAlive且有依赖关系,但HTTP的KeepAlive和TCP的KeepAlive是两个完全不同的概念"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TCP的KeepAlive是由操作系统内核来控制,通过 "},{"type":"codeinline","content":[{"type":"text","text":"keep-alive"}]},{"type":"text","text":" 报文来防止TCP连接被对端、防火墙或其他中间设备意外中断,和上层应用没有任何关系,只负责维护单个TCP连接的状态,其上层应用可以复用该TCP长连接,也可以关闭该TCP长连接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"HTTP的KeepAlive机制则是和自己的业务密切相关的,浏览器通过头部告知服务器要复用这个TCP连接,请不要随意关闭。只有到了 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive"}]},{"type":"text","text":" 头部规定的 "},{"type":"codeinline","content":[{"type":"text","text":"timeout"}]},{"type":"text","text":" 才会关闭该TCP连接,不过这具体依赖应用服务器,应用服务器也可以根据自己的设置在响应后主动关闭这个TCP连接,只要在响应的时候携带 "},{"type":"codeinline","content":[{"type":"text","text":"Connection: Close"}]},{"type":"text","text":" 告知对方"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以很多时候我们可以把HTTP连接理解为TCP连接,但HTTP KeepAlive则不能当成TCP的KeepAlive看待"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假设我们不开启TCP长连接而只开启HTTP长连接,是不是HTTP的KeepAlive就不起作用了?并不是的,此时HTTP的KeepAlive还会正常起作用,TCP连接还会被复用,但被复用的TCP连接出现故障的概率就高很多。由于没有开启TCP的KeepAlive,防火墙或负载转发服务等中间设备可能因为该TCP空闲太长而悄悄关闭该连接,当HTTP从自己的连接池拿出该TCP连接时,可能并不知道该连接被关闭,继续使用就会出现错误"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了减少错误,一般来说开启HTTP的KeepAlive的应用都会开启TCP的KeepAlive"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默认的 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp_keepalive_time"}]},{"type":"text","text":" 为2个小时,是不是太长了?感觉太长了,2小时监测一次感觉黄花菜都凉了。我们公司F5后面的Nginx服务器配置了30分钟,但应该也是太长了吧,F5维持空闲连接5分钟,那超时监测不应该低于这个值吗 "},{"type":"text","marks":[{"type":"strong"}],"text":"???"},{"type":"text","text":",比如"},{"type":"link","attrs":{"href":"https://cloud.google.com/compute/docs/troubleshooting/general-tips#communicatewithinternet","title":""},"content":[{"type":"text","text":"Google Cloud"}]},{"type":"text","text":"说其防火墙允许10分钟空闲连接,因此建议 "},{"type":"codeinline","content":[{"type":"text","text":"net.ipv4.tcp_keepalive_time"}]},{"type":"text","text":" 设置为6分钟"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"如何使用HTTP的KeepAlive"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很明显,开启HTTP KeepAlive不需要用户做任何操作,只要浏览器和应用服务器支持即可,不过需要注意的是,HTTP KeepAlive的相关头部都是 "},{"type":"codeinline","content":[{"type":"text","text":"hop-by-hop"}]},{"type":"text","text":" 类型的"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"和TCP连接不同,一个完整的HTTP事务,可能会横跨多个TCP连接,比如浏览器请求某个网页,请求可能先通过浏览器与负载均衡之间的TCP连接传输,再经过负载均衡到Nginx的TCP连接,最后在经过Nginx与业务Tomcat服务器的TCP连接,Tomcat处理完请求并返回响应后,响应沿着同样的TCP连接路线返回"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因此HTTP的头部被分为了两部分:"},{"type":"codeinline","content":[{"type":"text","text":"End-to-end"}]},{"type":"text","text":" 头部和 "},{"type":"codeinline","content":[{"type":"text","text":"Hop-by-hop"}]},{"type":"text","text":" 头部,"},{"type":"codeinline","content":[{"type":"text","text":"End-to-end"}]},{"type":"text","text":" 头部会被中间的代理原样转发,比如浏览器请求报文中的 "},{"type":"codeinline","content":[{"type":"text","text":"host"}]},{"type":"text","text":" 头部,会被负载均衡、反向代理原样转发到Tomcat里,除非特意修改。而 "},{"type":"codeinline","content":[{"type":"text","text":"Hop-by-hop"}]},{"type":"text","text":" 头部则只在当前TCP连接里有效,大部分头部都是 "},{"type":"codeinline","content":[{"type":"text","text":"End-to-end"}]},{"type":"text","text":" ,但KeepAlive相关头部很明显和TCP连接有密切关系,因此是 "},{"type":"codeinline","content":[{"type":"text","text":"Hop-by-hop"}]},{"type":"text","text":" 的"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" * End-to-end headers which are transmitted to the ultimate recipient of a request or response. End-to-end headers in responses MUST be stored as part of a cache entry and MUST be transmitted in any response formed from a cache entry."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" * Hop-by-hop headers which are meaningful only for a single transport-level connection and are not stored by caches or forwarded by proxies."}]}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"也就是说,即使浏览器请求时携带了 "},{"type":"codeinline","content":[{"type":"text","text":"Connection: Keep-Alive"}]},{"type":"text","text":" ,也只表示浏览器到负载均衡之间是长连接,但负载均衡到nginx、nginx到tomcat是否是长连接则需要具体分析。比如Nginx虽然支持HTTP的Keep-Alive,但由Nginx发起的HTTP请求默认不是长连接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于这种 "},{"type":"codeinline","content":[{"type":"text","text":"Hop-by-hop"}]},{"type":"text","text":" 的特性,HTTP长连接中的 "},{"type":"codeinline","content":[{"type":"text","text":"timeout"}]},{"type":"text","text":" 设置就十分可疑了,不过一般来说应用服务器都是根据自己的设置来管理TCP连接的,因此HTTP长连接中 "},{"type":"codeinline","content":[{"type":"text","text":"Connection"}]},{"type":"text","text":" 头部每个请求都携带, "},{"type":"codeinline","content":[{"type":"text","text":"keepalive"}]},{"type":"text","text":" 头部用的就比较少"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Nginx的KeepAlive配置"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx与客户端的长连接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx是支持HTTP KeepAlive的,因此只要client发送的http请求携带了KeepAlive头部,客户端和Nginx的长连接就能正常保持"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以使用keepalive"},{"type":"text","marks":[{"type":"italic"}],"text":"requests和keepalive"},{"type":"text","text":"timeout调整对client的长连接的单个连接承受的最大请求数,以及长连接最大空闲时长"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从上面可知,服务端可以根据客户端的 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive"}]},{"type":"text","text":" 头部来管理TCP连接,也可以根据自己的设置来管理,Nginx一般根据自己的设置来管理"}]}]}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Syntax:\tkeepalive_requests number;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Default:\tkeepalive_requests 100;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Context:\thttp, server, location"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" This directive appeared in version 0.8.0."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Sets the maximum number of requests that can be served through one keep-alive connection. After the maximum number of requests are made, the connection is closed."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Closing connections periodically is necessary to free per-connection memory allocations. Therefore, using too high maximum number of requests could result in excessive memory usage and not recommended."}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Syntax:\tkeepalive"},{"type":"text","marks":[{"type":"italic"}],"text":"timeout timeout [header"},{"type":"text","text":"timeout];"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Default:\tkeepalive_timeout 75s;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Context:\thttp, server, location"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" The first parameter sets a timeout during which a keep-alive client connection will stay open on the server side. The zero value disables keep-alive client connections. The optional second parameter sets a value in the “Keep-Alive: timeout=time” response header field. Two parameters may differ."}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"客户端修改默认值具体配置如下"}]}]}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"http {\n keepalive_requests 100;\n keepalive_timeout 75s;\n upstream backend {\n server 192.167.61.1:8080;\n }\n}"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx与Upstream Server的长连接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx作为发起方的时候,默认还是不开启HTTP的KeepAlive的,因此需要主动设置"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 "},{"type":"codeinline","content":[{"type":"text","text":"upstream"}]},{"type":"text","text":" 区块使用 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive"}]},{"type":"text","text":" 开启,数字表示每个work开启的最大长连接数"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Nginx和上游交互时,默认 "},{"type":"codeinline","content":[{"type":"text","text":"proxy_http_version"}]},{"type":"text","text":" 为1.0,因此需要配置 "},{"type":"codeinline","content":[{"type":"text","text":"proxy_http_version"}]},{"type":"text","text":" ,并清空 "},{"type":"codeinline","content":[{"type":"text","text":"connection"}]},{"type":"text","text":",这样即使前一跳是短连接,Nginx与上游也可以是长连接"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外 "},{"type":"codeinline","content":[{"type":"text","text":"upstream"}]},{"type":"text","text":" 里的 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive_requests"}]},{"type":"text","text":" 和 "},{"type":"codeinline","content":[{"type":"text","text":"http"}]},{"type":"text","text":" 区块里的一样是100,但 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive_timeout"}]},{"type":"text","text":" 默认为60秒,比 "},{"type":"codeinline","content":[{"type":"text","text":"http"}]},{"type":"text","text":" 区块里的少15秒,不过也正常,毕竟是里层,这个设置是比较合理的,使用默认的就可以"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"upstream backend {\n server 192.167.61.1:8080;\n server 192.167.61.1:8082 back;\n keepalive 100;\n # keepalive_requests 100;\n # keepalive_timeout 60s;\n}\n\nlocal /test {\n proxy_http_version 1.1;\n proxy_set_header Connection \"\"; // 传递给上游服务器的头信息\n\n proxy_pass http://backend;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外,Nginx还在 "},{"type":"codeinline","content":[{"type":"text","text":"listner"}]},{"type":"text","text":" 指令上提供了一个 "},{"type":"codeinline","content":[{"type":"text","text":"so_keepalive"}]},{"type":"text","text":" 选项,来开启Nginx对TCP长连接的支持,应该开启的是客户端与Nginx之间的TCP长连接,但一般没有人使用,那负载均衡和Nginx、Nginx和Tomcat之间是"},{"type":"text","marks":[{"type":"strong"}],"text":"不需要TCP长连接吗"},{"type":"text","text":"?因为中间没有网络设备?否则TCP长连接是由谁来做检测?"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"长连接的资源占用问题"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 长连接带来的一个很明显的问题就是资源的占用,浏览器对同一个域名一般能并发建立6个连接,一般这些都是长连接,而这些连接会维护75秒,但客户端获得响应以后一般就结束了,下一次的客户是不同的源地址,因此无法复用前一个浏览器与服务器之间维护的长连接,这会造成服务端维护了大量不再被使用的连接,所以长连接的意义在于有大量资源持续请求的场景"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 假设你就一个静态页面,里面包含几个资源,使用短连接对服务器并发更好"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 另外,注意Nginx中 "},{"type":"codeinline","content":[{"type":"text","text":"keepalive_requests"}]},{"type":"text","text":" 默认的100表示的是单个长连接能处理的最大请求数,而并不是Nginx能维护的长连接数。Nginx能维护的TCP连接数,为工作进程个数 "},{"type":"codeinline","content":[{"type":"text","text":"worker_processes"}]},{"type":"text","text":" 乘以每个工作进程允许维护的最大连接数 "},{"type":"codeinline","content":[{"type":"text","text":"worker_connections"}]},{"type":"text","text":"(默认512);如果想计算Nginx能服务的最大请求数,还需要在最大TCP连接数外,加上操作系统允许的排队等待数 "},{"type":"codeinline","content":[{"type":"text","text":"net.core.somaxconn"}]},{"type":"text","text":",默认128"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" Nginx通过事件驱动来实现大量长连接的维护,具体可以查看Nginx文档"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"端口号与文件数"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由于端口在传输层使用16位来传输,因此取值范围只能是0到65535,再加上TCP连接关闭后端口并不能立刻被重用,而是要经过2MSL的TIME_WAIT闲置,所以经常有人以为一个服务器同时最大能维持的TCP数是 "},{"type":"codeinline","content":[{"type":"text","text":"65000/2*60"}]},{"type":"text","text":" ,大约500左右"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 这个理解是有偏颇的。端口的限制只是对发起方来说的,即源端口。比如Nginx作为反向代理,和上游Tomcat建立连接时,源IP和目的IP肯定是固定的,目的端口也是固定的,比如Tomcat的8080端口,只有源端口可变,所以Nginx和上游Tomcat最多只能建立500左右的TCP连接,不过两端IP都是固定的,所以TCP连接重用效果非常好,并不会造成性能问题"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 当Nginx作为接收方和客户端浏览器建立连接时,Nginx服务器提供固定的IP和端口,而客户端浏览器IP和端口都会正常变动,因此Nginx服务器上维护的与客户端的长连接是不受端口限制的,不过此时服务器又会遇到著名的C10K问题"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" 此时限制服务器维持TCP连接数的是操作系统允许打开的最大文件数,要修改的主要有以下几处"}]}]}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":2,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"/proc/sys/fs/file-max:操作系统所有进程一共可以打开的文件数"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":2,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"/proc/sys/fs/nr_open:单个进程能分配的最大文件数"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":2,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"ulimit的open files:当前shell以及由它启动的进程可以打开的最大文件数,如果超过了nr"},{"type":"text","marks":[{"type":"italic"}],"text":"open,要先调整nr"},{"type":"text","text":"open的值"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"Tomcat的KeepAlive配置"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tomcat7以上都默认开启了keepalive支持。两个主要参数maxKeepAliveRequest和KeepAliveTimeout"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" maxKeepAliveRequest:一个长连接能接受的最大请求数,默认100"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":1,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":" KeepAliveTimeout:一个长连接最长空闲时间,否则被关闭,默认为connectionTimeout的值,默认60s"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Tomcat里的应用作为发起方的时候,是否支持KeepAlive是由应用自行决定的,和Tomcat无关"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"参考资料"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://tools.ietf.org/html/rfc1122#page-101","title":""},"content":[{"type":"text","text":"RFC1122 - TCP Keep-Alives"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://tools.ietf.org/html/rfc2616#section-13.5.1","title":""},"content":[{"type":"text","text":"HTTP Header Type"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://nginx.org/en/docs/http/ngxhttpcore_module.html","title":""},"content":[{"type":"text","text":"Nginx ngx"},{"type":"text","marks":[{"type":"italic"}],"text":"http"},{"type":"text","text":"core_module"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.nginx.com/blog/http-keepalives-and-web-performance/","title":""},"content":[{"type":"text","text":"HTTP Keepalive Connections and Web Performance"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://www.nginx.com/blog/tuning-nginx/","title":""},"content":[{"type":"text","text":"Tuning NGINX for Performance"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"http://www.kegel.com/c10k.html","title":""},"content":[{"type":"text","text":"The C10K problem"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://juejin.im/post/6844903916887343118","title":""},"content":[{"type":"text","text":"ulimit的探讨"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章