目录

  • 1. 引言
  • 2. 开启请求限速功能
  • 3. 源码分析
  • 3.1 配置指令
  • 3.1.1 limit_req_zone指令
  • 3.1.2 limit_req指令
  • 3.1.3 limit_req_dry_run指令
  • 3.1.4 limit_req_log_level指令
  • 3.1.5 limit_req_status指令
  • 3.2 模块初始化


1. 引言

  当构建高流量的Web应用程序时,保护服务器免受过多请求的影响是至关重要的。过多的请求可能会导致服务器过载,降低性能甚至导致系统崩溃。为了解决这个问题,nginx提供了一个强大的请求限速模块。该模块允许您根据自定义规则限制客户端请求的速率,并且还可以使用延迟机制来平滑处理超出限制的请求。在本文中,我们将深入探讨nginx的请求限速模块,了解它的工作原理、配置选项以及如何在实际应用中使用它来保护您的服务器免受恶意或异常请求的影响。

  当涉及到请求限速功能时,nginx采用了一种称为漏桶算法的经典算法来实现。漏桶算法是一种简单而有效的请求限速算法,它允许以固定的速率处理请求,并且可以处理突发的流量。

  漏桶算法的概念类似于一个物理漏桶,请求被视为水滴,而服务器的处理能力被视为漏桶的出水速率。当请求到达时,它们被放入漏桶中。如果漏桶已满,即请求超出了限定的速率,那么这些请求将被延迟处理或直接丢弃,以确保服务器不会超过其处理能力。

  在nginx中,请求限速模块使用漏桶算法来限制请求的速率。您可以根据自己的需求配置漏桶的容量和速率。容量决定了漏桶可以容纳的最大请求数量,而速率决定了漏桶的出水速率,也就是服务器处理请求的速率。

  当请求到达时,nginx会检查漏桶中的当前请求数量。如果请求数量小于漏桶的容量,请求将被立即处理。然而,如果请求数量超过了漏桶的容量,nginx可以选择将请求延迟处理或直接丢弃,以确保请求速率不会超过设定的限制。

  通过采用漏桶算法,nginx能够有效地控制请求的速率,平衡服务器的负载并保护系统免受过多请求的影响。接下来,我们将探讨如何在nginx中配置和使用请求限速模块,以及如何应对突发的请求流量。

2. 开启请求限速功能

  在nginx中,配置和使用请求限速模块相对简单,以下是一些关键的步骤和选项:

  1. 启用请求限速模块:首先,确保您的nginx已经编译并启用了limit_req模块,nginx默认是开启的,因此确认编译的时候没有通过–without-http_limit_req_module 关闭这个模块。
  2. 设置请求限速规则:在nginx的配置文件中,您可以通过使用limit_req_zone指令来定义请求限速的共享内存区域。该指令指定了限速的区域名称、存储限速状态的内存大小以及限速的参数。例如下面按照来源IP进行请求限速:
http {
   limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
 }

  在上面的示例中,我们创建了一个名为mylimit的请求限速区域,使用IP地址作为标识符,并设置了内存大小为10MB,速率为每秒1个请求。

  1. 应用请求限速:在需要应用请求限速的地方,可以使用limit_req指令来定义请求限速的策略。例如:
server {
        location /api {
            limit_req zone=mylimit burst=5 nodelay;
            ...
        }
    }

  在上面的示例中,我们在/api的位置块中应用了请求限速策略。我们使用了之前定义的mylimit区域,并设置了突发请求数为5,并且使用了nodelay参数,表示不延迟处理超出限制的请求。

  您还可以使用其他参数来进一步控制请求限速的行为,例如delay参数可以指定延迟处理超出限制的请求的数量。

  1. 处理突发请求流量:在面对突发的请求流量时,请求限速模块可以通过延迟处理或丢弃请求来应对。通过调整突发请求数量和延迟时间,您可以根据实际需求平衡服务器的负载和响应速度。
    例如,如果您预计会有短暂的高峰请求流量,您可以设置较高的突发请求数量,以允许一定程度的突发。而如果您更关注稳定性和响应时间,您可以设置较小的突发请求数量,并使用适当的延迟来平滑处理请求。

  nginx还允许同时设置多个limit_req指令同时作用于一个请求,譬如:

limit_req_zone $binary_remote_addr zone=perip:10m rate=1r/s;
limit_req_zone $server_name zone=perserver:10m rate=10r/s;

server {
    ...
    limit_req zone=perip burst=5 nodelay;
    limit_req zone=perserver burst=10;
}

  以上配置将限制来自单个IP地址的请求处理速率,并同时按照虚拟服务器维度进行请求限制速率处理。

  通过配置和使用Nginx的请求限速模块,您可以灵活地控制请求的速率,保护服务器免受过多请求的影响。这对于确保Web应用程序的稳定性、可用性和性能至关重要。请记住,在实际应用中,您可能需要根据您的特定需求进行一些调整和优化,以获得最佳的结果。

  在配置指令中,比较令人费解的是limit_req指令中的burst参数、delay参数和nodelay参数。下面进行说明:

  • burst参数: 如果nginx短时间内收到了大量请求,超出限制的请求直接拒绝,这在实际场景中未免过于严苛了。在真实的应用环境中,请求到来并不是匀速的,而是存在潮汐现象,当一个“突发波峰”来的时候,nginx可以通过burst关键字开启对突发请求的缓存,采用漏桶算法对进来的请求进行平滑处理,而不是生硬地直接拒绝。
  • nodelay参数:开启这个参数,表示在漏桶中缓存的过量的请求不进行延时处理,直接提供服务。
  • delay参数:这个参数设定了延迟处理多少个漏桶中缓存的过量的请求数量。

3. 源码分析

3.1 配置指令

3.1.1 limit_req_zone指令

  limit_req_zone指令的作用是定义一个共享内存区,用于在worker进程间共享限速的状态信息,因此请求限速模块的限速功能是服务器级别的,而不是单个worker进程级别的。其配置指令定义如下:

{ ngx_string("limit_req_zone"),
      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE3,
      ngx_http_limit_req_zone,
      0,
      0,
      NULL 
    },

   ngx_http_limit_req_zone函数的重要逻辑就是解析相应的参数,然后创建共享内存区:

static char *
ngx_http_limit_req_zone(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
......
	ngx_http_limit_req_ctx_t *ctx;
......  

	ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_limit_req_ctx_t));

	解析配置指令
......

    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;     /* 设置共享内存区的上下文信息 */
}

3.1.2 limit_req指令

  limit_req指令则是开启请求限速功能,它需要引用前面limit_req_zone指令定义的共享内存区,并且指定允许的busrt突发值和delay值。其配置指令定义如下:

{ ngx_string("limit_req"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE123,
      ngx_http_limit_req,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },
static char *
ngx_http_limit_req(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	ngx_http_limit_req_conf_t *lrcf = conf;
	ngx_http_limit_req_limit_t *limit, *limits;

	......
	解析配置指令


	/* 如果还没有创建限速规则数组,则创建一个 */	
    limits = lrcf->limits.elts;

    if (limits == NULL) {
        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++) {
        if (shm_zone == limits[i].shm_zone) {
            return "is duplicate";
        }
    }

	/* 将当前limit_req指令添加到lrcf->limits数组中 */
    limit = ngx_array_push(&lrcf->limits);
    if (limit == NULL) {
        return NGX_CONF_ERROR;
    }

    limit->shm_zone = shm_zone;
    limit->burst = burst * 1000;    /* busrt的单位是r/ms,所以乘以1000 */
    limit->delay = delay * 1000;    /* delay的单位是r/ms,所以乘以1000 */

    return NGX_CONF_OK;
}

3.1.3 limit_req_dry_run指令

   该指令设置了一个开关,如果是on的话,如果发生了限流事件,只是在error日志中打印日志,而不是实际执行限流动作。这个指令主要用于开启限流操作前进行测试验证工作。配置指令定义如下:

{ ngx_string("limit_req_dry_run"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_limit_req_conf_t, dry_run),
      NULL },

3.1.4 limit_req_log_level指令

  该指令设置了当发生限流事件的时候,在nginx的error日志中输出的日志的日志级别。配置指令定义如下:

{ ngx_string("limit_req_log_level"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_enum_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_limit_req_conf_t, limit_log_level),
      &ngx_http_limit_req_log_levels },

3.1.5 limit_req_status指令

  该指令设置了当发生限流事件的时候,nginx返回给客户端的响应码。

{ ngx_string("limit_req_status"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_num_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_limit_req_conf_t, status_code),
      &ngx_http_limit_req_status_bounds },

3.2 模块初始化

   模块初始化回调函数ngx_http_limit_req_init中设置了在NGX_HTTP_PREACCESS_PHASE阶段的处理函数,该阶段在对应的请求匹配到了location配置之后,因此,可以应用对应location配置下的limit_req指令规则。源码如下:

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

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

    *h = ngx_http_limit_req_handler;

    return NGX_OK;
}