Nginx长连接keepavlie实现源码分析

560 阅读4分钟

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强制修改了

image-20240430174359265.png

主要逻辑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的一些逻辑:

  1. 如果读取到数据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;
    }
    
  2. 读取到数据,就指定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关闭连接,具体的调用链路是

image-20240506160520948.png

u->peer.connection被设置为NULL是keepalive的模块处理的(ngx_http_upstream_free_keepalive_peer),为了就是保证connection被复用。

image-20240506170605801.png

其中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)

image-20240506162749577.png

如果后端有返回Connection字段,如果明确表示是Close,那么也不使用keepalive机制。

image-20240506163134917.png

上面的逻辑是u->headers_in.connection_close的字段处理逻辑,u->keepalive字段是nginx从upstream读取完body后,发现body都读取完就修改keepaive的字段。如果响应是没有body的,那么在ngx_http_proxy_input_filter_init会处理这些情况,并且也同步修改u->keeplive的字段。

image-20240506172756043.png

image-20240506174546742.png

2.2 强制关闭长连接特殊情况

  • 后端返回101协议转换状态码的时候,keepalive失效(因为有可能重新连接),这里可以认为除了HTTP的请求,nginx都不做长连接的维护。比如从http协议转换为websocket协议后,如果websocket连接关闭,那么这个连接就不再维护了。
  • upstream响应的body超过了content-length header表示的长度,这种情况也是关闭长连接。这个时候可以认为后端已经发生异常了。
  • upstream的响应是chunked的时候,如果最后一个chunked后面还有数据,那么也是认为后端异常,关闭长连接。