0. 为什么有keepalive长连接
考虑到Nginx作为代理,转发请求的场景。如果不使用长连接(keepalive)的话,每次请求需要经过2次的tcp握手和挥手(客户端到nginx,nginx到后端服务),更别提如果使用https,还要有TLS的密钥协商的流程,会导致单个请求的overhead非常高。因此长连接的功能,可以在一个tcp的连接上,实现多个请求复用(有序,得等上一个请求结束)一个tcp连接,提升性能。在QPS高的时候,提升会特别明显。
那么长连接在Nginx这里,有两种keepalive的机制,分别是客户端到Nginx的连接和Nginx到upstream的连接,本文会详细介绍。
1. 客户端到Nginx的keepalive
在Nginx返回响应给客户端的时候,会调用ngx_http_finalize_connection去判断当前连接是否还需要保留,如果需要保留此连接,就调用ngx_http_set_keepalive复用连接;否则就调用ngx_http_close_request关闭连接。
不是HTTP1.0的逻辑,默认开启客户端的keepalive,除非connection强制修改了
主要逻辑ngx_http_finalize_connection
if (!ngx_terminate
&& !ngx_exiting
&& r->keepalive
&& clcf->keepalive_timeout > 0)
{
ngx_http_set_keepalive(r);
return;
}
// 跳过lingering close
ngx_http_close_request(r, 0);
在ngx_http_set_keepalive中,connection的读事件的handler被设置为ngx_http_keepalive_handler:
rev->handler = ngx_http_keepalive_handler;
这样,当客户端主动关闭连接或者发送下一个请求的时候,首先调用的是ngx_http_keepalive_handler,在ngx_http_keepalive_handler中有一个尝试读取可读事件的数据的逻辑,如果读取不到数据(有读事件),就证明是客户端主动关闭了连接,否则就可以认为是下一个请求到来了,就可以按照一个全新请求的逻辑处理。下面是ngx_http_keepalive_handler的一些逻辑:
-
如果读取到数据n为0,认为是客户端主动关闭连接
if (n == 0) { ngx_log_error(NGX_LOG_INFO, c->log, ngx_socket_errno, "client %V closed keepalive connection", &c->addr_text); ngx_http_close_connection(c); return; } -
读取到数据,就指定ngx_http_process_request_line处理读取到的请求
c->log->handler = ngx_http_log_error; c->log->action = "reading client request line"; c->idle = 0; ngx_reusable_connection(c, 0); c->data = ngx_http_create_request(c); if (c->data == NULL) { ngx_http_close_connection(c); return; } c->sent = 0; c->destroyed = 0; ngx_del_timer(rev); rev->handler = ngx_http_process_request_line; ngx_http_process_request_line(rev);
2. Nginx到upstream的keepalive
Nginx默认是使用HTTP1.0连接后端的,因此需要默认开启keepalive,得proxy_http_version 1.1;配置好转发到后端的协议(一般的HTTP框架都支持了HTTP1.1)
使用HTTP1.0的时候,默认是关闭keepalive的,因此当收到upstream得响应后,会调用ngx_close_connection关闭连接,具体的调用链路是
u->peer.connection被设置为NULL是keepalive的模块处理的(ngx_http_upstream_free_keepalive_peer),为了就是保证connection被复用。
其中ngx_http_upstream_free_keepalive_peer中还有u->keepliave判断的逻辑,如果u->keeplive为0,connection不会被设置为NULL,那么连接也是会被关闭的。
2.1 u->keepalive字段设置
如果是使用HTTP1.1访问后端的时候,在ngx_http_upstream_finalize_request中的u->perr.connection字段是NULL(代表不需要调用ngx_close_connection)
如果后端有返回Connection字段,如果明确表示是Close,那么也不使用keepalive机制。
上面的逻辑是u->headers_in.connection_close的字段处理逻辑,u->keepalive字段是nginx从upstream读取完body后,发现body都读取完就修改keepaive的字段。如果响应是没有body的,那么在ngx_http_proxy_input_filter_init会处理这些情况,并且也同步修改u->keeplive的字段。
2.2 强制关闭长连接特殊情况
- 后端返回101协议转换状态码的时候,keepalive失效(因为有可能重新连接),这里可以认为除了HTTP的请求,nginx都不做长连接的维护。比如从http协议转换为websocket协议后,如果websocket连接关闭,那么这个连接就不再维护了。
- upstream响应的body超过了content-length header表示的长度,这种情况也是关闭长连接。这个时候可以认为后端已经发生异常了。
- upstream的响应是chunked的时候,如果最后一个chunked后面还有数据,那么也是认为后端异常,关闭长连接。