1.相关指令
1 | Syntax: limit_conn_zone key zone=name:size; |
此指令设置限流所需共享内存的名称和size,key指定限流的标志,比如$binary_remote_addr。
1 |
|
此指令指定共享内存名以及限制多少连接,比如limit_conn_zone指令key设置为$binary_remote_addr,zone设置为limit_zone,那么来自于$binary_remote_addr的连接超过number,nginx直接给客户端响应503。
2.源码解析
2.1 配置解析函数
- 首先来看下limit_conn_zone指令的解析函数ngx_http_limit_conn_zone,这个函数的功能主要是解析共享内存的name和size,然后使用这两个值作为参数调用ngx_shared_memory_add函数,将共享内存添加到nginx共享内存管理模块中,设置共享内存的data以及初始化函数。
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 static char *
ngx_http_limit_conn_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
u_char *p;
ssize_t size;
ngx_str_t *value, name, s;
ngx_uint_t i;
ngx_shm_zone_t *shm_zone;
ngx_http_limit_conn_ctx_t *ctx;
ngx_http_compile_complex_value_t ccv;
value = cf->args->elts;
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_conn_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
ccv.cf = cf;
ccv.value = &value[1];
ccv.complex_value = &ctx->key;
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
size = 0;
name.len = 0;
for (i = 2; i < cf->args->nelts; i++) {
if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
name.data = value[i].data + 5;
p = (u_char *) ngx_strchr(name.data, ':');
if (p == NULL) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid zone size \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
name.len = p - name.data;
s.data = p + 1;
s.len = value[i].data + value[i].len - s.data;
size = ngx_parse_size(&s);
if (size == NGX_ERROR) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid zone size \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
if (size < (ssize_t) (8 * ngx_pagesize)) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"zone \"%V\" is too small", &value[i]);
return NGX_CONF_ERROR;
}
continue;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
if (name.len == 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%V\" must have \"zone\" parameter",
&cmd->name);
return NGX_CONF_ERROR;
}
shm_zone = ngx_shared_memory_add(cf, &name, size,
&ngx_http_limit_conn_module);
if (shm_zone == NULL) {
return NGX_CONF_ERROR;
}
if (shm_zone->data) {
ctx = shm_zone->data;
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"%V \"%V\" is already bound to key \"%V\"",
&cmd->name, &name, &ctx->key.value);
return NGX_CONF_ERROR;
}
shm_zone->init = ngx_http_limit_conn_init_zone;
shm_zone->data = ctx;
return NGX_CONF_OK;
}
- 接下来看下limit_conn指令解析函数ngx_http_limit_conn,这个函数主要是保存配置的共享内存名以及限制连接的个数。
1 |
|
- 接下来就来看下nginx是如何实现限流的,这个功能由ngx_http_limit_conn_handler实现,主要功能就是统计各个进程与key对应的连接数,超过则返回503,在统计各个进程数据的时候使用了共享内存。
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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_slab_pool_t *shpool;
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_set) {
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++) {
ctx = limits[i].shm_zone->data;
//获取limit_conn_zone指令后key的值
if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (key.len == 0) {
continue;
}
if (key.len > 255) {
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"the value of the \"%V\" key "
"is more than 255 bytes: \"%V\"",
&ctx->key.value, &key);
continue;
}
r->main->limit_conn_set = 1;
//对key进行crc计算,用于在红黑树中查找结点
hash = ngx_crc32_short(key.data, key.len);
shpool = (ngx_slab_pool_t *) limits[i].shm_zone->shm.addr;
ngx_shmtx_lock(&shpool->mutex);
// 查找符合要求的结点
node = ngx_http_limit_conn_lookup(ctx->rbtree, &key, hash);
// 没有找到对应的结点,将此次请求插入到红黑树中
if (node == NULL) {
//计算结点所需内存
n = offsetof(ngx_rbtree_node_t, color)
+ offsetof(ngx_http_limit_conn_node_t, data)
+ key.len;
// 申请结点内存
node = ngx_slab_alloc_locked(shpool, n);
if (node == NULL) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_http_limit_conn_cleanup_all(r->pool);
return lccf->status_code;
}
lc = (ngx_http_limit_conn_node_t *) &node->color;
// 赋值key,用于下次请求到来查询
node->key = hash;
lc->len = (u_char) key.len;
lc->conn = 1;
ngx_memcpy(lc->data, key.data, key.len);
ngx_rbtree_insert(ctx->rbtree, node);
} else {
lc = (ngx_http_limit_conn_node_t *) &node->color;
// 判断key标记的connections是否超过配置,如果超过则返回错误码
if ((ngx_uint_t) lc->conn >= limits[i].conn) {
ngx_shmtx_unlock(&shpool->mutex);
ngx_log_error(lccf->log_level, r->connection->log, 0,
"limiting connections by zone \"%V\"",
&limits[i].shm_zone->shm.name);
ngx_http_limit_conn_cleanup_all(r->pool);
return lccf->status_code;
}
//连接数量+1
lc->conn++;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"limit conn: %08Xi %d", node->key, lc->conn);
ngx_shmtx_unlock(&shpool->mutex);
//注册内存池清理函数
cln = ngx_pool_cleanup_add(r->pool,
sizeof(ngx_http_limit_conn_cleanup_t));
if (cln == NULL) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
// 绑定内存清理函数
cln->handler = ngx_http_limit_conn_cleanup;
lccln = cln->data;
lccln->shm_zone = limits[i].shm_zone;
lccln->node = node;
}
return NGX_DECLINED;
}
- 结点插入函数,限流模块是将每个连接都存进一颗红黑树中,nginx提供一个默认的插入函数,限流模块自己实现了一个插入函数。
3. 总结
以上就是nginx限流模块的基本实现,其他限制请求数的模块类似。