Keep learning, keep living...

0%

关于nginx中的limit_conn指令

多年前写过介绍nginx限制连接模块的文章<<ngx_http_limit_conn_module模块分析>>, 最近业务中用到limit_conn指令限制请求,重新了解了一下它的用法。

根据nginx文档, 可以理解主要逻辑是根据limit_conn_zone所指定的key值计算连接数,当连接数超过limit_conn所指定的值时,则返回错误码。

由于限制值是在limit_conn指令而不是在limit_conn_zone指令中设置的,而limit_conn是可以配置多次的,当配置多次相同zonelimit_conn指令并且限制值不同,那么生效的是哪个限制值呢?

如果在同一个location中配置多个相同zonelimit_conn指令,示例配置为:

1
2
3
4
5
location /a {
limit_conn perserver 2;
limit_conn perserver 5;
...
}

当启动nginx会检测到这种情况直接报错:

1
nginx: [emerg] "limit_conn" directive is duplicate in /usr/local/nginx/conf/nginx.conf:52

查看nginx源码,在处理limit_conn指令配置时,会对相同zone的情况进行检测,当有重复的zone, 则直接报错:

1
2
3
4
5
6
7
8
9
10
static char *
ngx_http_limit_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
......

for (i = 0; i < lccf->limits.nelts; i++) {
if (shm_zone == limits[i].shm_zone) {
return "is duplicate";
}
}

如果在不同location下配置相同zonelimit_conn指令,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
limit_conn_zone $server_name zone=perserver:10m;

server {
...
location /a {
limit_conn perserver 2;
...
}

location /b {
limit_conn perserver 5;
}
}

这种情况下,nginx可以正常启动,限制值如何生效呢?

根据nginx源码, 配置的限制值存储在ngx_http_limit_conn_limit_t结构中,该结构是一个location级别的结构,因而对于不同的location是各自独立的:

1
2
3
4
typedef struct {
ngx_shm_zone_t *shm_zone;
ngx_uint_t conn;
} ngx_http_limit_conn_limit_t;

实际的检测逻辑注册在PREACCESS阶段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static ngx_int_t
ngx_http_limit_conn_init(ngx_conf_t *cf)
{
ngx_http_handler_pt *h;
ngx_http_core_main_conf_t *cmcf;

cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}

*h = ngx_http_limit_conn_handler;

return NGX_OK;
}

检测时,会从对应的location结构中获取ngx_http_limit_conn_limit_t结构,因而使用的是自身location中所配置的限制值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
static ngx_int_t
ngx_http_limit_conn_handler(ngx_http_request_t *r)
{
size_t n;
uint32_t hash;
ngx_str_t key;
ngx_uint_t i;
ngx_rbtree_node_t *node;
ngx_pool_cleanup_t *cln;
ngx_http_limit_conn_ctx_t *ctx;
ngx_http_limit_conn_node_t *lc;
ngx_http_limit_conn_conf_t *lccf;
ngx_http_limit_conn_limit_t *limits;
ngx_http_limit_conn_cleanup_t *lccln;

if (r->main->limit_conn_status) {
return NGX_DECLINED;
}

lccf = ngx_http_get_module_loc_conf(r, ngx_http_limit_conn_module);
limits = lccf->limits.elts;

for (i = 0; i < lccf->limits.nelts; i++) {
......

因而, 对于上述的配置示例,并发连接数是基于zone中的key计算的,因而无论访问/a还是/b都会增加对应zone: perserver的并发连接数,但实际是否超过限制值是每个location单独计算的。

比如,当前已经有了一个/a和一个/b的连接,此时再访问/a, 由于location /a配置的限制值为2, 已达到上限,新连接被限制,返回503或指定的错误码,而这时对于/b的新连接,由于总连接数小于5, 可以正常进行。