nginx之keepalive与pipeline
Posted 开发架构二三事
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了nginx之keepalive与pipeline相关的知识,希望对你有一定的参考价值。
tcp和http都有keepalive,但是它们的作用是不一样的,tcp 的keepalive是为了确认长连接的状态,而http的keepalive是为了让连接保持得久一些。nginx提供了对keepalive和pipeline的支持。
1. tcp的keepalive机制:
当客户端与服务器建立了tcp连接后,如果客户端一直不发送数据, 或者隔很长时间才发送一次数据。当连接很久没有数据报文传输时,服务器如何去确定对方还在线。到底是掉线了还是确实没有数据传输,连接还需不需要保持,这种情况在TCP协议设计中是需要考虑的。TCP协议通过一种巧妙的方式去解决这个问题,当超过一段时间(tcpkeepalivetime)之后,TCP自动发送一个数据为 空的报文给对方, 如果对方回应了这个报文,说明对方还在线,连接可以继续保持,如果对方没有报文返回并且重试了多次之后则认为连接丢失,没有必要保持连接。这个过程相当于服务器向客户端发送心跳包, 确认客户端是否还在线。对应的内核参数:
echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
2 echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl
3 echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes
2. http的keepalive机制为:
通常客户端浏览器要展现一张完整的页面需要很多个请求才能完成,如图片,js,CSS等。如果每一个HTTP请求都需要新建并断开一个TCP,这个开销是完全没有必要的。开启HTTP Keep-Alive之后,能复用已有的TCP链接, 当一个请求已经响应完毕,服务器端没有立即关闭TCP连接,而是等待一段时间继续接收浏览器可能发送过来的第二个请求,通常浏览器在第一个请求返回之后会立即发送第二个请求。因此在一个tcp连接上可以存在多个http请求,当然这个如果客户端一直不发送新的http请求,超过一段时间后,nginx服务器还是会关闭这个TCP长连接。
对于http1.0 协议来说,如果响应头中有content-length头,则以content-length的长度就可以知道body的长度,客户端在接收body时,可以依照这个长度接收数据,接收完后,就表示该请求完成。如果没有content-length,客户端会一直接收数据,直到服务端主动端口连接,才表示body接收完。
对于http1.1 协议,如果响应头中transfer-encoding为chunked传输,表示body是流式输出,body被分成多个块,每块的开始会标示出当前块的长度,此时,body不需要指定长度。如果是非chunked传输,而且有Content-length,则按照content-length来接收数据。否则,非chunked且没有content-length,则客户端接收数据,知道服务器主动断开。
如果客户端请求头中connection为close,表示客户端要主动关闭连接。如果connection为keepalive,则表示客户端要保持长连接。请求头中connection的默认值在http1.0中是close,在http 1.1中是默认keepalive。如果要keep-alive,nginx在输出完响应体后,会设置当前连接的keepalive属性,然后等待客户端下一次请求,nginx设置了keepalive的等待最大时间。一般来说,当客户端需要多次访问同一个server时,打开keepalive的效果非常好。
http2中已经是全双工模式,不需要使用keepalive来维持长连接,所以在rfc7540规范中移除了这个头
参考:https://httpwg.org/specs/rfc7540.html
keepalive并不是免费的午餐,长时间的tcp连接容易导致系统资源无效占用。配置不当的keepalive有时比重复利用连接带来的损失还更大。所以,正确地设置keepalive timeout时间非常重要。
3. 关于pipeline:
在RFC 2616(https://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.2.2)中规定了pipelining:
A client that supports persistent connections MAY "pipeline" its requests (i.e., send multiple requests without waiting for each response). A server MUST send its responses to those requests in the same order that the requests were received.
Clients which assume persistent connections and pipeline immediately after connection establishment SHOULD be prepared to retry their connection if the first pipelined attempt fails. If a client does such a retry, it MUST NOT pipeline before it knows the connection is persistent. Clients MUST also be prepared to resend their requests if the server closes the connection before sending all of the corresponding responses.
Clients SHOULD NOT pipeline requests using non-idempotent methods or non-idempotent sequences of methods (see section 9.1.2). Otherwise, a premature termination of the transport connection could lead to indeterminate results. A client wishing to send a non-idempotent request SHOULD wait to send that request until it has received the response status for the previous request.
讲到这里,有必要把keepalive和pipeline做一个区分: pipeline其实就是流水线作业,它可以看作为keepalive的一种升华,因为pipeline也是基于长连接的,目的就是利用一个连接做多次请求。连接请求头使用keepalive之后,在处理多个请求时,第二个请求要等到第一个请求响应完成才能发起,两个响应时间至少为2RTT。而对于pipeline,客户端请求是打包进行的,第二个请求不必等第一个请求处理完,两个响应的时间可能达到1RTT。http1.1以后是支持pipeline的,关于pipeline的更多细节请自行查阅之前关于Request-Response的推文。
3. nginx对keepalive和pipeline的处理
3.1 对keepalive的处理:
void
ngx_http_handler(ngx_http_request_t *r)
{
.........................................
switch (r->headers_in.connection_type) {
case 0:
//如果版本大于1.0则默认是keepalive
r->keepalive = (r->http_version > NGX_HTTP_VERSION_10);
break;
case NGX_HTTP_CONNECTION_CLOSE:
//如果指定connection头为close则不需要keepalive
r->keepalive = 0;
break;
case NGX_HTTP_CONNECTION_KEEP_ALIVE:
r->keepalive = 1;
break;
}
..................................
}
static void
ngx_http_finalize_connection(ngx_http_request_t *r)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
.....................................................................
//可以看到如果设置了keepalive,并且timeout大于0,就进入keepalive的处理。
if (!ngx_terminate
&& !ngx_exiting
&& r->keepalive
&& clcf->keepalive_timeout > 0)
{
ngx_http_set_keepalive(r);
return;
} else if (r->lingering_close && clcf->lingering_timeout > 0) {
ngx_http_set_lingering_close(r);
return;
}
ngx_http_close_request(r, 0);
}
解析http头部的connection字段后,将会设置TCP的连接类型,是打开长连接还是关闭长连接。然后会设置到请求对象的keepalive中。如果设置了keepalive,并且timeout大于0,就进入keepalive的处理。
3.2 对pipeline的处理
在ngxhttpset_keepalive方法中会相应地进行pipeline的处理:
hc = r->http_connection;
b = r->header_in;
//一般情况下,当解析完header_in之后,pos会设置为last。也就是读取到的数据刚好是一个完整的http请求.当pos小于last,则说明可能是一个pipeline请求。
if (b->pos < b->last) {
/* the pipelined request */
if (b != c->buffer) {
...
其实nginx的做法很简单,在读取数据时会将读取的数据放到一个buffer里面。当nginx在处理完前一个请求后,如果发现buffer里面还有数据,就认为剩下的数据是下一个请求的开始,然后接下来处理下一个请求(这样也会产生large header)。这就是pipeline请求。
另上nginx对pipeline中的多个请求的处理不是并行的,而是一个接一个的处理,只是在处理第一个请求的时候,客户端就可以发起第二个请求。nginx这样做就可以利用pipeline可以减少从处理完一个请求后到等待第二个请求的请求头数据的时间。参考:https://blog.csdn.net/wenwuge_topsec/article/details/43268343
3.3 nginx的keepalive指令
nginx cookBook page 158-159:
The keepalive directive in the upstream context activates a cache of connections that stay open for each NGINX worker。注意这里的keepalive指令和上面讲的keepalive是有所区别的,keepalive指令是用于为nginx worker缓存连接的。
以上是关于nginx之keepalive与pipeline的主要内容,如果未能解决你的问题,请参考以下文章
keepalived入门与掌握之keepalive+lvs实例部署