tcp和http都有keepalive,但是它们的作用是不一样的,tcp 的keepalive是为了确认长连接的状态,而http的keepalive是为了让连接保持得久一些。nginx提供了对keepalive和pipeline的支持。
当客户端与服务器建立了tcp连接后,如果客户端一直不发送数据, 或者隔很长时间才发送一次数据。当连接很久没有数据报文传输时,服务器如何去确定对方还在线。到底是掉线了还是确实没有数据传输,连接还需不需要保持,这种情况在TCP协议设计中是需要考虑的。TCP协议通过一种巧妙的方式去解决这个问题,当超过一段时间(tcpkeepalivetime)之后,TCP自动发送一个数据为 空的报文给对方, 如果对方回应了这个报文,说明对方还在线,连接可以继续保持,如果对方没有报文返回并且重试了多次之后则认为连接丢失,没有必要保持连接。这个过程相当于服务器向客户端发送心跳包, 确认客户端是否还在线。对应的内核参数:
echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time2 echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl3 echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes
在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的推文。
voidngx_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 voidngx_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的处理。
在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
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缓存连接的。