nginx 제한 요청 수 (ngx http limit req module) 상세 설명 실현
1.ngx_http_limit_req_모듈 레 드 블랙 트 리 노드
typedef struct {
u_char color; //
u_char dummy; //
u_short len; // key key
ngx_queue_t queue; //LRU ( )
ngx_msec_t last; //
/* integer value, 1 corresponds to 0.001 r/s */
ngx_uint_t excess; //
ngx_uint_t count; //
u_char data[1]; // key ( len )
} ngx_http_limit_req_node_t
2.ngx_http_limit_req_모듈 레 드 블랙 트 리
typedef struct {
ngx_rbtree_t rbtree; //
ngx_rbtree_node_t sentinel; //
ngx_queue_t queue; //LRU ( 1 )
} ngx_http_limit_req_shctx_t;
3.ngx_http_limit_req_module 요청 한 컨 텍스트 구조 제한
typedef struct {
ngx_http_limit_req_shctx_t *sh; // 2
ngx_slab_pool_t *shpool; //slab
/* integer value, 1 corresponds to 0.001 r/s */
ngx_uint_t rate; //
ngx_http_complex_value_t key; // ( IP)
ngx_http_limit_req_node_t *node; // ( )
} ngx_http_limit_req_ctx_t
4. 요청 한 설정 설명 구 조 를 제한 합 니 다 (여러 개 설정 가능)
typedef struct {
ngx_shm_zone_t *shm_zone; //
/* integer value, 1 corresponds to 0.001 r/s */
ngx_uint_t burst; //
ngx_uint_t nodelay; /* unsigned nodelay:1 */ //
} ngx_http_limit_req_limit_t
5. location 단계 의 제한 요청 설정
typedef struct {
ngx_array_t limits; // location 4
ngx_uint_t limit_log_level; //
ngx_uint_t delay_log_level; //
ngx_uint_t status_code; //
} ngx_http_limit_req_conf_t
nginx 요청 제한 모듈 은 전역 (main 단계) 의 요청 제한 안에서 공유 메모리 의 용량, 요청 한 주파수 단위 의 시간 내 요청 횟수 (초급 또는 등급) 를 설정 할 수 있 습 니 다.main 등급 의 설정 은 각 location 등급 의 설정 에 영향 을 미 칠 수 있 습 니 다. 먼저 main 등급 설정 의 제한 조건 을 판단 하고 그 다음 에 location 등급 설정 의 제한 조건 을 판단 합 니 다.
* 8195 ° main 등급 의 설정 처리 절 차 는 다음 과 같 습 니 다.
static char *
ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
...
value = cf->args->elts; //
/* 3 */
ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));
if (ctx == NULL) {
return NGX_CONF_ERROR;
}
ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
...
/*
main key key
key key
*/
if (ngx_http_compile_complex_value(&ccv) != NGX_OK) {
return NGX_CONF_ERROR;
}
...
for (i = 2; i < cf->args->nelts; i++) {
if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
/* zone */
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 */
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)) {
/* 8 */
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"zone \"%V\" is too small", &value[i]);
return NGX_CONF_ERROR;
}
continue;
}
if (ngx_strncmp(value[i].data, "rate=", 5) == 0) {
/* */
len = value[i].len;
p = value[i].data + len - 3;
if (ngx_strncmp(p, "r/s", 3) == 0) {
/* */
scale = 1;
len -= 3;
} else if (ngx_strncmp(p, "r/m", 3) == 0) {
/* */
scale = 60;
len -= 3;
}
rate = ngx_atoi(value[i].data + 5, len - 5);
if (rate <= 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid rate \"%V\"", &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;
}
/* 1000 */
ctx->rate = rate * 1000 / scale;
/* */
shm_zone = ngx_shared_memory_add(cf, &name, size,
&ngx_http_limit_req_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_req_init_zone;
shm_zone->data = ctx;
return NGX_CONF_OK;
}
* 81955 ° location 등급 의 설정 처리 절 차 는 여러 server 에 설정 할 수 있 습 니 다.
static char *
ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_limit_req_conf_t *lrcf = conf;
...
value = cf->args->elts;
shm_zone = NULL;
burst = 0;
nodelay = 0;
for (i = 1; i < cf->args->nelts; i++) {
if (ngx_strncmp(value[i].data, "zone=", 5) == 0) {
/* main */
s.len = value[i].len - 5;
s.data = value[i].data + 5;
shm_zone = ngx_shared_memory_add(cf, &s, 0,
&ngx_http_limit_req_module);
if (shm_zone == NULL) {
return NGX_CONF_ERROR;
}
continue;
}
if (ngx_strncmp(value[i].data, "burst=", 6) == 0) {
/* */
burst = ngx_atoi(value[i].data + 6, value[i].len - 6);
if (burst <= 0) {
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid burst rate \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
continue;
}
/* nodelay nodelay 403 */
if (ngx_strcmp(value[i].data, "nodelay") == 0) {
nodelay = 1;
continue;
}
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"invalid parameter \"%V\"", &value[i]);
return NGX_CONF_ERROR;
}
if (shm_zone == NULL) {
/* */
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
"\"%V\" must have \"zone\" parameter",
&cmd->name);
return NGX_CONF_ERROR;
}
limits = lrcf->limits.elts;
if (limits == NULL) {
/* location */
if (ngx_array_init(&lrcf->limits, cf->pool, 1,
sizeof(ngx_http_limit_req_limit_t))
!= NGX_OK)
{
return NGX_CONF_ERROR;
}
}
for (i = 0; i < lrcf->limits.nelts; i++) {
/* location */
if (shm_zone == limits[i].shm_zone) {
return "is duplicate";
}
}
/* location location */
limit = ngx_array_push(&lrcf->limits);
if (limit == NULL) {
return NGX_CONF_ERROR;
}
limit->shm_zone = shm_zone;
/* 1000 */
limit->burst = burst * 1000;
limit->nodelay = nodelay;
return NGX_CONF_OK;
}
main 단계 의 공유 메모리 초기 화
static ngx_int_t
ngx_http_limit_req_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
ngx_http_limit_req_ctx_t *octx = data;
...
ctx = shm_zone->data;
if (octx) {
/*main key */
if (ctx->key.value.len != octx->key.value.len
|| ngx_strncmp(ctx->key.value.data, octx->key.value.data,
ctx->key.value.len)
!= 0)
{
ngx_log_error(NGX_LOG_EMERG, shm_zone->shm.log, 0,
"limit_req \"%V\" uses the \"%V\" key "
"while previously it used the \"%V\" key",
&shm_zone->shm.name, &ctx->key.value,
&octx->key.value);
return NGX_ERROR;
}
ctx->sh = octx->sh;
ctx->shpool = octx->shpool;
return NGX_OK;
}
/*slab */
ctx->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
if (shm_zone->shm.exists) {
/* , */
ctx->sh = ctx->shpool->data;
return NGX_OK;
}
/* shpool(slab ) */
ctx->sh = ngx_slab_alloc(ctx->shpool, sizeof(ngx_http_limit_req_shctx_t));
if (ctx->sh == NULL) {
return NGX_ERROR;
}
ctx->shpool->data = ctx->sh;
/* */
ngx_rbtree_init(&ctx->sh->rbtree, &ctx->sh->sentinel,
ngx_http_limit_req_rbtree_insert_value);
/* LRU */
ngx_queue_init(&ctx->sh->queue);
len = sizeof(" in limit_req zone \"\"") + shm_zone->shm.name.len;
/* */
ctx->shpool->log_ctx = ngx_slab_alloc(ctx->shpool, len);
if (ctx->shpool->log_ctx == NULL) {
return NGX_ERROR;
}
ngx_sprintf(ctx->shpool->log_ctx, " in limit_req zone \"%V\"%Z",
&shm_zone->shm.name);
ctx->shpool->log_nomem = 0;
return NGX_OK;
}
이하 ngxhttp_limit_req_module 의 처리 프로 세 스 1. http 처리 pre 마 운 트access 처리 함수 포인터
static ngx_int_t
ngx_http_limit_req_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);
/* pre_access */
h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
if (h == NULL) {
return NGX_ERROR;
}
/* ngx_http_limit_req_module */
*h = ngx_http_limit_req_handler;
return NGX_OK;
}
2. pre 에서access 단계 트리거 처리 함수
static ngx_int_t
ngx_http_limit_req_handler(ngx_http_request_t *r)
{
...
/* decline http */
if (r->main->limit_req_set) {
return NGX_DECLINED;
}
lrcf = ngx_http_get_module_loc_conf(r, ngx_http_limit_req_module);
/* location */
limits = lrcf->limits.elts;
excess = 0;
rc = NGX_DECLINED;
#if (NGX_SUPPRESS_WARN)
limit = NULL;
#endif
for (n = 0; n < lrcf->limits.nelts; n++) {
/*
( ngx_http_limit_req_limit_t )
*/
limit = &limits[n];
ctx = limit->shm_zone->data;
if (ngx_http_complex_value(r, &ctx->key, &key) != NGX_OK) {
/* key key */
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
if (key.len == 0) {
/*key */
continue;
}
if (key.len > 65535) {
/* key */
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
"the value of the \"%V\" key "
"is more than 65535 bytes: \"%V\"",
&ctx->key.value, &key);
continue;
}
/* key hash */
hash = ngx_crc32_short(key.data, key.len);
ngx_shmtx_lock(&ctx->shpool->mutex);
/* */
rc = ngx_http_limit_req_lookup(limit, hash, &key, &excess,
(n == lrcf->limits.nelts - 1));
ngx_shmtx_unlock(&ctx->shpool->mutex);
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"limit_req[%ui]: %i %ui.%03ui",
n, rc, excess / 1000, excess % 1000);
if (rc != NGX_AGAIN) {
//
break;
}
}
if (rc == NGX_DECLINED) {
/* location decline*/
return NGX_DECLINED;
}
/* */
r->main->limit_req_set = 1;
if (rc == NGX_BUSY || rc == NGX_ERROR) {
/* */
if (rc == NGX_BUSY) {
/* */
ngx_log_error(lrcf->limit_log_level, r->connection->log, 0,
"limiting requests, excess: %ui.%03ui by zone \"%V\"",
excess / 1000, excess % 1000,
&limit->shm_zone->shm.name);
}
while (n--) {
/* location */
ctx = limits[n].shm_zone->data;
/*
node ngx_http_limit_req_lookup
*/
if (ctx->node == NULL) {
continue;
}
/* ( ) worker */
ngx_shmtx_lock(&ctx->shpool->mutex);
// node
ctx->node->count--;
ngx_shmtx_unlock(&ctx->shpool->mutex);
/* node ngx_http_limit_req_ctx_t*/
ctx->node = NULL;
}
return lrcf->status_code; // nginx
}
/* */
/* rc == NGX_AGAIN || rc == NGX_OK */
if (rc == NGX_AGAIN) {
excess = 0;
}
/*
*/
delay = ngx_http_limit_req_account(limits, n, &excess, &limit);
/*
( no_delay ) decline
*/
if (!delay) {
return NGX_DECLINED;
}
ngx_log_error(lrcf->delay_log_level, r->connection->log, 0,
"delaying request, excess: %ui.%03ui, by zone \"%V\"",
excess / 1000, excess % 1000, &limit->shm_zone->shm.name);
/* */
if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
/* */
r->read_event_handler = ngx_http_test_reading;
r->write_event_handler = ngx_http_limit_req_delay;
/* */
r->connection->write->delayed = 1;
/* */
ngx_add_timer(r->connection->write, delay);
return NGX_AGAIN;
}
지연 쓰기 이벤트 처리 함수
static void
ngx_http_limit_req_delay(ngx_http_request_t *r)
{
ngx_event_t *wev;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"limit_req delay");
wev = r->connection->write;
if (wev->delayed) {
/* */
if (ngx_handle_write_event(wev, 0) != NGX_OK) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
}
return;
}
/* */
if (ngx_handle_read_event(r->connection->read, 0) != NGX_OK) {
ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
/* */
r->read_event_handler = ngx_http_block_reading;
r->write_event_handler = ngx_http_core_run_phases;
/* http pre_access */
ngx_http_core_run_phases(r);
}
레 드 블랙 트 리 조회 및 삽입 처리
static ngx_int_t
ngx_http_limit_req_lookup(ngx_http_limit_req_limit_t *limit, ngx_uint_t hash,
ngx_str_t *key, ngx_uint_t *ep, ngx_uint_t account)
{
//
now = ngx_current_msec;
//
ctx = limit->shm_zone->data;
node = ctx->sh->rbtree.root;
sentinel = ctx->sh->rbtree.sentinel;
while (node != sentinel) {
if (hash < node->key) {
/* */
node = node->left;
continue;
}
if (hash > node->key) {
/* */
node = node->right;
continue;
}
/* hash == node->key */
lr = (ngx_http_limit_req_node_t *) &node->color;
/* key */
rc = ngx_memn2cmp(key->data, lr->data, key->len, (size_t) lr->len);
if (rc == 0) {
/* LRU*/
ngx_queue_remove(&lr->queue);
ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
/* */
ms = (ngx_msec_int_t) (now - lr->last);
/* */
excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
if (excess < 0) {
// 0 0
excess = 0;
}
*ep = excess;
if ((ngx_uint_t) excess > limit->burst) {
// busy
return NGX_BUSY;
}
if (account) { // location
/* */
lr->excess = excess;
lr->last = now;
return NGX_OK;
}
lr->count++; //
ctx->node = lr; // node
return NGX_AGAIN;
}
node = (rc < 0) ? node->left : node->right;
}
*ep = 0;
/*
color + data +key
*/
size = offsetof(ngx_rbtree_node_t, color)
+ offsetof(ngx_http_limit_req_node_t, data)
+ key->len;
/* LRU */
ngx_http_limit_req_expire(ctx, 1);
node = ngx_slab_alloc_locked(ctx->shpool, size);
if (node == NULL) {
/* LRU ( 3 )*/
ngx_http_limit_req_expire(ctx, 0);
//
node = ngx_slab_alloc_locked(ctx->shpool, size);
if (node == NULL) {
/* */
ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0,
"could not allocate node%s", ctx->shpool->log_ctx);
return NGX_ERROR;
}
}
// key
node->key = hash;
// color
lr = (ngx_http_limit_req_node_t *) &node->color;
// key
lr->len = (u_short) key->len;
lr->excess = 0;
// key
ngx_memcpy(lr->data, key->data, key->len);
//
ngx_rbtree_insert(&ctx->sh->rbtree, node);
// LRU
ngx_queue_insert_head(&ctx->sh->queue, &lr->queue);
if (account) {
lr->last = now;
lr->count = 0;
return NGX_OK;
}
lr->last = 0;
/* */
lr->count = 1;
/* */
ctx->node = lr;
return NGX_AGAIN;
}
http 처리 지연 시간 계산
static ngx_msec_t
ngx_http_limit_req_account(ngx_http_limit_req_limit_t *limits, ngx_uint_t n,
ngx_uint_t *ep, ngx_http_limit_req_limit_t **limit)
{
...
excess = *ep; //
if (excess == 0 || (*limit)->nodelay) {
// location nodelay( )
/* 0*/
max_delay = 0;
} else {
/* */
ctx = (*limit)->shm_zone->data;
/* ( 1000 ) ( )*/
max_delay = excess * 1000 / ctx->rate;
}
while (n--) {
/* location */
ctx = limits[n].shm_zone->data;
/* */
lr = ctx->node;
if (lr == NULL) {
/* */
continue;
}
ngx_shmtx_lock(&ctx->shpool->mutex);
/* /
/* */
now = ngx_current_msec;
ms = (ngx_msec_int_t) (now - lr->last);
/* */
excess = lr->excess - ctx->rate * ngx_abs(ms) / 1000 + 1000;
if (excess < 0) {
/* 0*/
excess = 0;
}
/* */
lr->last = now;
lr->excess = excess;
lr->count--;
/* */
ngx_shmtx_unlock(&ctx->shpool->mutex);
/* */
ctx->node = NULL;
if (limits[n].nodelay) {
/*location nodelay */
continue;
}
/* */
delay = excess * 1000 / ctx->rate;
if (delay > max_delay) {
/*
max_delay
max_delay
*/
max_delay = delay;
// excess
*ep = excess;
// location ( )
*limit = &limits[n];
}
}
/*
*/
return max_delay;
}
ngx_http_limit_req_module LRU 탈락 알고리즘 처리
static void
ngx_http_limit_req_expire(ngx_http_limit_req_ctx_t *ctx, ngx_uint_t n)
{
...
//
now = ngx_current_msec;
/*
1. n==1 2 0
2. n==0 LRU 2 0
*/
while (n < 3) {
if (ngx_queue_empty(&ctx->sh->queue)) {
/*LRU */
return;
}
/* LRU */
q = ngx_queue_last(&ctx->sh->queue);
/* LRU */
lr = ngx_queue_data(q, ngx_http_limit_req_node_t, queue);
if (lr->count) {
//
return;
}
if (n++ != 0) {
//n==1 n==0
/* */
ms = (ngx_msec_int_t) (now - lr->last);
ms = ngx_abs(ms);
if (ms < 60000) {
/* 60 */
return;
}
/*
*/
excess = lr->excess - ctx->rate * ms / 1000;
if (excess > 0) { //
return;
}
}
/* LRU */
ngx_queue_remove(q);
/* */
node = (ngx_rbtree_node_t *)
((u_char *) lr - offsetof(ngx_rbtree_node_t, color));
/* */
ngx_rbtree_delete(&ctx->sh->rbtree, node);
/* */
ngx_slab_free_locked(ctx->shpool, node);
}
}
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
간단! Certbot을 사용하여 웹 사이트를 SSL(HTTPS)화하는 방법초보자가 인프라 주위를 정돈하는 것은 매우 어렵습니다. 이번은 사이트를 간단하게 SSL화(HTTP에서 HTTPS통신)로 변경하는 방법을 소개합니다! 이번에는 소프트웨어 시스템 Nginx CentOS7 의 환경에서 S...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.