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;
}

 

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