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