2

我有一个 NGINX 服务器服务于一个 Web 应用程序,其中客户拥有他们访问其网站的域 (CNAMES)。

NGINX 是否有办法限制一段时间内对这些域之一的访问次数?

例子:

我需要的限制:2000 个请求/域/分钟

所以,在特定的一段时间内...

www.websiteA.com.br --- 1456 个请求/分钟 OK!

www.websiteB.com.br --- 1822 个请求/分钟 OK!

www.websiteC.com.br --- 2001 个请求/分钟 暂时锁定

有谁知道如何做出这样的限制?

4

2 回答 2

7

我正在使用 nginx 的速率限制功能来定义一些速率限制规则,然后将这些规则应用于特定的主机。
这里有一些技巧(使用 if 语句以及通过 error_page 处理程序重定向) - 我还没有找到更好的方法。如果您有更好的选择,请发表评论。

nginx.conf

http {

    # ...

    # Define rate-limit key to use
    map $http_authorization $rl_zone_key{
        default $binary_remote_addr;
        '~*.*'  $http_authorization;
    }

    # Define Rate Limits
    limit_req_zone $rl_zone_key zone=rateLimit_Standard:10m rate=1r/s;
    limit_req_zone $rl_zone_key zone=rateLimit_Class_A:10m rate=5r/s;
    limit_req_zone $rl_zone_key zone=rateLimit_Class_B:10m rate=10r/s;
    limit_req_zone $rl_zone_key zone=rateLimit_Class_C:10m rate=100r/s;

    # Define the rate Limit Category applied to the particular host
    map $http_host $apply_rate_limit{
        hostnames;
        default             rateLimit_Standard;
        example.com         rateLimit_Class_B;
        *.example.com       rateLimit_Class_A;
    }

    #upstream server definition for php fast-cgi using port 9000
    upstream phpfcgi {
        server 127.0.0.1:9000;
    }

    # ...

}

地图 $http_authorization $rl_zone_key

用于比较两个请求是否来自同一个地方的键使用$binary_remote_addrnginx 变量,除非$http_authorization存在标头,在这种情况下它将使用该标头。这意味着在用户通过身份验证(使用摘要身份验证)之前,速率限制由 ip 应用 - 此后它由登录用户会话应用。

limit_req_zone

这里我设置了速率限制缓存。

  • 标准:10MB 缓存,每秒允许 1 个请求
  • A 类:10MB 缓存,每秒允许 5 个请求
  • B 类:10MB 缓存,每秒允许 10 个请求
  • C 类:10MB 缓存,每秒允许 100 个请求

地图 $http_host $apply_rate_limit

使用该map指令,我检查主机名$http_host以确定目标域,然后$apply_rate_limit使用我要应用的速率限制类的名称进行定义。

example.com.conf

server {
    listen              80;
    server_name         example.com;
    root                /var/www/example.com;

    location / {
        index               index.php;
        try_files           $uri =404;

        location ~ ^/index\.php(/|$) {
            error_page               420 =200 @rateLimit_Standard;
            error_page               421 =200 @rateLimit_Class_A;
            error_page               422 =200 @rateLimit_Class_B;
            error_page               423 =200 @rateLimit_Class_C;

            if ( $apply_rate_limit = 'rateLimit_Standard' ) {return 420;}
            if ( $apply_rate_limit = 'rateLimit_Class_A' ) {return 421;}
            if ( $apply_rate_limit = 'rateLimit_Class_B' ) {return 422;}
            if ( $apply_rate_limit = 'rateLimit_Class_C' ) {return 423;}
        }

    }

    location @rateLimit_Standard {
        limit_req                   zone=rateLimit_Standard burst=5;
        add_header                  X-Rate-Limit-Class $apply_rate_limit;

        include                     fastcgi_params;
        fastcgi_index               index.php;
        fastcgi_split_path_info     ^(.+\.php)(/.*)$;
        fastcgi_param               SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param               HTTPS off;
        fastcgi_pass                phpfcgi;

    }

    location @rateLimit_Class_A {
        limit_req                   zone=rateLimit_Class_A burst=10 nodelay;
        add_header                  X-Rate-Limit-Class $apply_rate_limit;

        include                     fastcgi_params;
        fastcgi_index               index.php;
        fastcgi_split_path_info     ^(.+\.php)(/.*)$;
        fastcgi_param               SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param               HTTPS off;
        fastcgi_pass                phpfcgi;
    }

    location @rateLimit_Class_B {
        limit_req                   zone=rateLimit_Class_B burst=100 nodelay;
        add_header                  X-Rate-Limit-Class $apply_rate_limit;

        include                     fastcgi_params;
        fastcgi_index               index.php;
        fastcgi_split_path_info     ^(.+\.php)(/.*)$;
        fastcgi_param               SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param               HTTPS off;
        fastcgi_pass                phpfcgi;
    }

    location @rateLimit_Class_C {
        limit_req                   zone=rateLimit_Class_C burst=1000;
        add_header                  X-Rate-Limit-Class $apply_rate_limit;

        include                     external_definition_of_fastcgi_parameters.conf
    }

}

在我的示例 vhost 文件中,我使用了之前定义的速率限制类。这可以以更直接的方式完成,但在我们的场景中,我们有一个服务于多个域的单个 vhost 文件,因此最好在 vhost 文件之外定义哪个域使用哪个速率限制类。
我将在最后提供一个简化的示例 vhost 文件,它消除了这种抽象并使 vhost 速率限制应用程序更加手动。

error_page 420 =200 @rateLimit_Standard

在 nginx 中,没有方便的方法可以重定向到try_files指令之外的命名位置。完成此操作的一个技巧是定义一个错误处理程序(用于不常用的错误代码),然后提供命名位置作为 error_page 处理程序。
在这个特定的行中,我们说当错误代码 420 被触发时,错误处理程序应该重定向到该@rateLimit_Standard 位置并将 HTTP 代码重置为 200。

if ($apply_rate_limit = 'rateLimit_Standard') {return 420;}

在我们的 nginx.conf 文件中,$apply_rate_limit是基于$http_hostheader 定义的。如果设置为rateLimit_Standard,则此 if 语句将通过从 http 请求返回并返回错误代码 420 来完成。这将被我们之前定义的 error_page 处理程序捕获,并将被重定向到指定位置@rateLimit_Standard

位置@rateLimit_Standard

一旦请求被重新路由到@rateLimit_Standard,它将应用速率限制并设置突发值:limit_req zone=rateLimit_Standard burst=5; 然后它将继续按正常方式处理 php 请求。
为了更好地衡量,我还添加了一个标题来跟踪应用的速率限制:add_headerX-Rate-Limit-Class $apply_rate_limit;

包括 external_definition_of_fastcgi_parameters.conf

您会注意到,对于每个命名位置,包含的 fcgi 标头都是相同的。nginx 配置块中没有正式的继承,因此必须对每个位置重复此操作,以便将其转发到 php-fpm。
但是,可以在外部文件中定义公共属性,并使用上面的 include 语句从单个外部文件中获取所有这些重复的配置选项。我把它留在这里是因为这是你真正应该做的。

简化示例虚拟主机

上面的example.conf文件充分展示了我如何抽象主机 <-> 速率限制类配对,因为我们在我们的环境中使用它。
如果您仅对单个域使用单个 vhost 条目,则可能会简单得多:

simple-example.com.conf

server {
    listen              80;
    server_name         simple-example.com;
    root                /var/www/simple-example.com;

    location / {
        index               index.php;
        try_files           $uri =404;

        location ~ ^/index\.php(/|$) {
            limit_req                   zone=rateLimit_Class_A burst=10 nodelay;
            add_header                  X-Rate-Limit-Class rateLimit_Class_A;

            include                     fastcgi_params;
            fastcgi_index               index.php;
            fastcgi_split_path_info     ^(.+\.php)(/.*)$;
            fastcgi_param               SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param               HTTPS off;
            fastcgi_pass                phpfcgi;
        }

    }

}

免责声明:以上所有代码都是从我自己的 nginx 配置中构建的,目的是创建一个示例。这可能无法开箱即用,您需要将这些片段按摩到您的环境中以使其正常工作。

如果您觉得这很有用,请投票。

于 2015-07-23T12:37:57.790 回答
2

You can refer to HttpLimitReqModule in nginx. limit_req and limit_req_zone may helps.

于 2013-08-13T03:46:31.820 回答