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