nginx upstream中长连接池的维护

nginx中的长连接分为:

  • 下游客户端和nginx的长连接
  • nginx反向代理中和上游服务器之间的长连接

 

upstream中的长连接池

当nginx反向代理请求上游服务器时,第一次时会建立TCP连接,等待请求响应完毕之后,如果upstream中配置了keepalive指令,对应的upstream就会把连接暂存;等到下次继续请求upstream中的server时,首先从上次暂存的连接池中复用连接,省去TCP的握手、挥手时间。上面这些功能是在ngx_http_upstream_keepalive_module模块中进行实现的。

 

ngx_http_upstream_keepalive_module模块相关实现

ngx_http_upstream_keepalive_module模块的对应的指令是 upstream配置块中的 keepalive,下面是相关配置文件:

upstream test_backend {
    server 127.0.0.1:12345;
    server 127.0.0.2:12345;

    keepalive 32;
}

 

1、初始化流程

ngx_http_upstream_keepalive_module相关数据结构:

typedef struct {
    ngx_uint_t                         max_cached;      // 最大keepalive连接个数 配置的参数

    // 用于缓存keepalive连接  数据结构为ngx_http_upstream_keepalive_cache_t
    // 使用时 首先从free链表中获取位置 然后保存连接放到cache中,获取连接时直接从cache中获取
    ngx_queue_t                        cache;   // 保存的可以直接复用的链接
    ngx_queue_t                        free;    // 当前upstream还可以复用的连接数(数量最开始等于max_cached 后续cache+free=max_cached)
    
    // 原始的init_upstream函数(默认为RR) 
    // 将init_upstream替换为本模块函数ngx_http_upstream_init_keepalive,在函数中调用原来的init_upstream函数 相当于hook
    ngx_http_upstream_init_pt          original_init_upstream;  
    // 原始的init_peer函数 同上
    ngx_http_upstream_init_peer_pt     original_init_peer;      

} ngx_http_upstream_keepalive_srv_conf_t;


typedef struct {
    ngx_http_upstream_keepalive_srv_conf_t  *conf;

    ngx_queue_t                        queue;
    ngx_connection_t                  *connection; // 可复用的连接

    // TCP连接相关信息  在复用连接时 用于匹配upstream中的server信息
    socklen_t                          socklen;
    ngx_sockaddr_t                     sockaddr;

} ngx_http_upstream_keepalive_cache_t;

// 用于保存原始的get/free等函数 将对应函数替换为ngx_http_upstream_get_keepalive_peer/ngx_http_upstream_free_keepalive_peer
typedef struct {
    ngx_http_upstream_keepalive_srv_conf_t  *conf;

    ngx_http_upstream_t               *upstream;

    void                              *data;

    ngx_event_get_peer_pt              original_get_peer;
    ngx_event_free_peer_pt             original_free_peer;
} ngx_http_upstream_keepalive_peer_data_t;

配置中keepalive指令对应的解析函数,主要工作就是将原来upstream的peer.init函数保存起来,然后将upstream的peer.init函数替换为当前模块的ngx_http_upstream_init_keepalive函数,和HOOK比较类似。

static ngx_command_t  ngx_http_upstream_keepalive_commands[] = {
    { ngx_string("keepalive"),
      NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
      ngx_http_upstream_keepalive,
      NGX_HTTP_SRV_CONF_OFFSET,
      0,
      NULL },

      ngx_null_command
};

static char *ngx_http_upstream_keepalive(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
    ngx_http_upstream_srv_conf_t            *uscf;
    ngx_http_upstream_keepalive_srv_conf_t  *kcf = conf;
    
    ngx_str_t   *value = cf->args->elts;
    ngx_int_t n = ngx_atoi(value[1].data, value[1].len);

    // 保存配置的keepalive最大连接数
    kcf->max_cached = n;
    uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);

    // 保存原来的init_upstream  默认为ngx_http_upstream_init_round_robin
    kcf->original_init_upstream = uscf->peer.init_upstream ? uscf->peer.init_upstream
                                  : ngx_http_upstream_init_round_robin;

    // 将init_upstream替换为ngx_http_upstream_init_keepalive
    uscf->peer.init_upstream = ngx_http_upstream_init_keepalive;
    return NGX_CONF_OK;
}

ngx_http_upstream_keepalive_module模块初始化函数,主要工作是初始化了max_cached个连接空间位置,方便后续保存可复用的连接。

static ngx_int_t ngx_http_upstream_init_keepalive(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us) {
    ngx_http_upstream_keepalive_srv_conf_t  *kcf;
    ngx_http_upstream_keepalive_cache_t     *cached;

    kcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_keepalive_module);
    // 调用原始的init_upstream函数
    if (kcf->original_init_upstream(cf, us) != NGX_OK) {
        return NGX_ERROR;
    }

    // 保存原始的init_peer函数
    kcf->original_init_peer = us->peer.init;
    // 使用ngx_http_upstream_init_keepalive_peer替换原始的init_peer函数
    us->peer.init = ngx_http_upstream_init_keepalive_peer;

    /* allocate cache items and add to free queue */
    cached = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_keepalive_cache_t) * kcf->max_cached);
    ngx_queue_init(&kcf->cache);
    ngx_queue_init(&kcf->free);

    // 初始化max_cached个可以复用连接的位置 
    for (i = 0; i < kcf->max_cached; i++) {
        ngx_queue_insert_head(&kcf->free, &cached[i].queue);
        cached[i].conf = kcf;
    }
    return NGX_OK;
}

注意点:

在ngx_http_upstream_init_keepalive函数中,可以看到是将原始的peer.init函数保存了起来,然后替换为ngx_http_upstream_init_keepalive_peer函数。如果当前upstream配置使用了非默认的负载均衡函数(即非RR),比如ip_hash等,则需要将keepalive指令放到对应负载均衡指令后面,否则其他负载均衡模块还会继续替换peer.init函数,就把ngx_http_upstream_keepalive_module刚设置的给覆盖了。官网解释如下:

When using load balancer methods other than the default round-robin method, it is necessary to activate them before the keepalive directive.

 

 

2、运行流程

在向上游服务器(upstream)建立连接时,即ngx_http_upstream_init_request函数中调用之前设置的 peer.init函数,这个函数已经被替换为ngx_http_upstream_init_keepalive_peer函数,函数中首先调用原始的 peer.init函数,后续继续讲 peer.get/peer.free函数替换为ngx_http_upstream_keepalive_module模块对应的函数。

static void ngx_http_upstream_init_request(ngx_http_request_t *r) {
    // ...
    
    // 初始化负载均衡算法(不同负载均衡模块有不同的实现 默认为ngx_http_upstream_init_round_robin_peer)
    // ngx_http_upstream_keepalive_module会替换此函数为ngx_http_upstream_init_keepalive_peer
    if (uscf->peer.init(r, uscf) != NGX_OK) {
        ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
    }

    // ...
    // 调用ngx_http_upstream_connect向上游服务器发起连接
    ngx_http_upstream_connect(r, u);
}

static ngx_int_t ngx_http_upstream_init_keepalive_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us) {
    ngx_http_upstream_keepalive_peer_data_t  *kp;
    ngx_http_upstream_keepalive_srv_conf_t   *kcf;

    kcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_keepalive_module);
    kp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_keepalive_peer_data_t));
    // 首先调用原始的init_peer函数 默认为ngx_http_upstream_init_round_robin_peer,在函数中会分配peer.get/peer.free等函数指针
    if (kcf->original_init_peer(r, us) != NGX_OK) {
        return NGX_ERROR;
    }

    // 将原始的peer.get/peer.free等提前保存
    kp->conf = kcf;
    kp->upstream = r->upstream;
    kp->data = r->upstream->peer.data;
    kp->original_get_peer = r->upstream->peer.get;
    kp->original_free_peer = r->upstream->peer.free;

    // 将peer.get/peer.free等替换为当前模块对应函数
    r->upstream->peer.data = kp;
    r->upstream->peer.get = ngx_http_upstream_get_keepalive_peer;
    r->upstream->peer.free = ngx_http_upstream_free_keepalive_peer;
    return NGX_OK;
}

上面初始化完成后,即调用ngx_http_upstream_connect函数,在ngx_event_connect_peer函数中会调用peer.get函数获取一个upstream中的server块信息。

如果upstream中开启了keepalive指令,这里会替换为ngx_http_upstream_get_keepalive_peer函数,在函数中会优先费用之前的TCP连接,如果可以复用,则返回NGX_DONE,ngx_event_connect_peer函数直接返回,否则将创建一个TCP连接向上游服务器连接。

static void ngx_http_upstream_connect(ngx_http_request_t *r, ngx_http_upstream_t *u) {
    // ...
    
    // 获取ngx_connection_t结构体(TCP套接字) 调用connect连接上游服务器  获取服务器算法也由内部get函数实现
    rc = ngx_event_connect_peer(&u->peer);
    // ...
}

ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc) {
    int                rc, type;
    ngx_socket_t       s;
    ngx_connection_t  *c;

    // 调用get函数指针获取服务器地址 如果使用长连接 已经替换为了ngx_http_upstream_get_keepalive_peer
    rc = pc->get(pc, pc->data);
    if (rc != NGX_OK) {
        return rc;
    }

    // 真正建立TCP连接
    type = (pc->type ? pc->type : SOCK_STREAM);
    s = ngx_socket(pc->sockaddr->sa_family, type, 0);
    c = ngx_get_connection(s, pc->log);
    // ...    
}

在ngx_http_upstream_keepalive_module设置的ngx_http_upstream_get_keepalive_peer函数中:

1、调用原始的peer.get函数获取一个upstream配置中的server块,确定本次请求那个server;

2、遍历当前upstream的连接缓存队列,查找地址信息确实是否有可复用的连接。

static ngx_int_t ngx_http_upstream_get_keepalive_peer(ngx_peer_connection_t *pc, void *data) {
    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
    ngx_http_upstream_keepalive_cache_t      *item;
    ngx_int_t          rc;
    ngx_queue_t       *q, *cache;

    // 调用原始get_peer获取一个后端连接属性 在kp->data中(可以是rr、hash等各种算法)
    rc = kp->original_get_peer(pc, kp->data);
    // 从之前的连接缓存池中遍历查找上面的连接信息
    cache = &kp->conf->cache;

    for (q = ngx_queue_head(cache); q != ngx_queue_sentinel(cache); q = ngx_queue_next(q)) {
        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
        c = item->connection;
        if (ngx_memn2cmp((u_char *) &item->sockaddr, (u_char *) pc->sockaddr, item->socklen, pc->socklen) == 0) {
            // 找到可以复用的链接  从cache队列中删除  将q返还给free队列(表示当前keepalive可保存的长连接数量+1)
            ngx_queue_remove(q);
            ngx_queue_insert_head(&kp->conf->free, q);
            goto found;
        }
    }
    return NGX_OK;

found:
    // 找到一个可以复用的TCP连接  直接返回NGX_DONE
    pc->connection = c;
    return NGX_DONE;
}

在与上游服务器请求响应完毕后,需要释放连接,会调用ngx_http_upstream_free_keepalive_peer函数缓存没有出现异常的TCP连接:

static void ngx_http_upstream_free_keepalive_peer(ngx_peer_connection_t *pc, void *data, ngx_uint_t state) {
    ngx_http_upstream_keepalive_peer_data_t  *kp = data;
    ngx_http_upstream_keepalive_cache_t      *item;
    ngx_queue_t          *q;
    ngx_connection_t     *c;
    ngx_http_upstream_t  *u;

    u = kp->upstream;
    c = pc->connection;
    // ... 状态校验,确定连接是否可以缓存

    if (ngx_queue_empty(&kp->conf->free)) {
        // free队列为空 当前upstream保存的长连接数量到达了最大限制  
        // 释放cache队列尾部连接(即最早加进去的长连接) 然后把当前的长连接保存到刚释放的连接的位置
        q = ngx_queue_last(&kp->conf->cache);
        ngx_queue_remove(q);

        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
        ngx_http_upstream_keepalive_close(item->connection);

    } else {
        // free队列不为空 当前upstream保存的长连接没到最大限制  仍可以继续保存当前长连接
        q = ngx_queue_head(&kp->conf->free);
        ngx_queue_remove(q);
        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
    }

    // 将q(即当前长连接)保存到cache队列中
    ngx_queue_insert_head(&kp->conf->cache, q);
    item->connection = c;
    pc->connection = NULL;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章