NGINX上的限流

本文是对 https://www.nginx.com/blog/rate-limiting-nginx/ 的主要内容(去掉了关于NGINX Plus相关内容)的翻译

限流(rate limiting)是NGINX众多特性中最有用的,也是经常容易被误解和错误配置的,特性之一。该特性可以限制某个用户在一个给定时间段内能够产生的HTTP请求数。请求可以简单到就是一个对于主页的GET请求或者一个登陆表格的POST请求。

限流也可以用于安全目的上,比如减慢暴力密码破解攻击。通过限制进来的请求速率,并且(结合日志)标记出目标URLs来帮助防范DDoS攻击。一般地说,限流是用在保护上游应用服务器不被在同一时刻的大量用户请求湮没。

下面介绍NGINX限流的基本用法。

NGINX限流是如何工作的

NGINX限流使用漏桶算法(leaky bucket algorithm),该算法广泛应用于通信和基于包交换计算机网络中,用来处理当带宽被限制时的突发情况。和一个从上面进水,从下面漏水的桶的原理很相似;如果进水的速率大于漏水的速率,这个桶就会发生溢出。

在请求处理过程中,水代表从客户端来的请求,而桶代表了一个队列,请求在该队列中依据先进先出(FIFO)算法等待被处理。漏的水代表请求离开缓冲区并被服务器处理,溢出代表了请求被丢弃并且永不被服务。

配置基本的限流功能

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

server {
    location /login/ {
        limit_req zone=mylimit;

        proxy_pass http://my_upstream;
    }
} 

limit_req 在它出现的环境中启用了限流(在上面的例子中,作用在所有对于/login/的请求上),则limit_req_zone指令定义了限流的参数。

limit_req_zone指令一般定义在http块内部,使得该指令可以在多个环境中使用。该指令有下面三个参数:

  • Key — 在限流应用之前定义了请求的特征。在上面例子中,它是$binary_remote_addr(NGINX变量),该变量代表了某个客户端IP地址的二进制形式。这意味着我们可以将每个特定的IP地址的请求速率限制为第三个参数所定义的值。(使用这个变量的原因是因为它比用string代表客户端IP地址的$remote_addr变量消耗更少的空间。)
  • Zone — 定义了存储每个IP地址状态和它访问受限请求URL的频率的共享内存区域。将这些信息保存在共享内存中,意味着这些信息能够在NGINX工作进程之间共享。定义有两个部分:由zone=关键字标识的区域名称,以及冒号后面的区域大小。约16000个IP地址的状态信息消耗1M内存大小,因此我们的区域(zone)大概可以存储约160000个地址。
    当NGINX需要添加新的记录时,如果此时存储耗尽了,最老的记录会被移除。如果释放的存储空间还是无法容纳新的记录,NGINX返回503 (Service Temporarily Unavailable)状态码。此外,为了防止内存被耗尽,每次NGINX创建一个新的记录的同时移除多达两条前60秒内没有被使用的记录。
  • Rate — 设置最大的请求速率。在上面的例子中,速率不能超过10个请求每秒。NGINX事实上可以在毫秒级别追踪请求,因此这个限制对应了1个请求每100毫秒。因为我们不允许突刺(bursts,短时间内的突发流量,详细见下一部分。),这意味着如果某个请求到达的时间离前一个被允许的请求小于100毫秒,它会被拒绝。

limit_req_zone指令设置限流和共享内存区域的参数,但是该指令实际上并不限制请求速率。为了限制起作用,需要将该限制应用到某个特定的locationserver块(block),通过包含一个limit_req指令的方式。在上面的例子中,我们将请求限制在/login/上。

所以现在对于/login/,每个特定的IP地址被限制为10个请求每秒— 或者更准确地说,不能在与前一个请求间隔100毫秒时间内发送请求。

处理流量突刺(Bursts)

如果在100毫秒内得到2个请求会怎么样?对于第2个请求,NGINX返回503状态码给客户端。这可能不是我们想要的,因为事实上,应用是趋向于突发性的。相反,我们想要缓存任何过多的请求并且及时地服务它们。下面是我们使用limit_reqburst参数来更新配置:

location /login/ {
      limit_req zone=mylimit burst=20;

      proxy_pass http://my_upstream;
}

burst参数定义了一个客户端能够产生超出区域(zone)规定的速率的请求数量(在我们示例mylimit区域中,速率限制是10个请求每秒,或1个请求每100毫秒)。一个请求在前一个请求后的100毫秒间隔内达到,该请求会被放入一个队列,并且该队列大小被设置为20.

这意味着如果从某个特定IP地址来的21个请求同时地达到,NGINX立即转发第一个请求到上游的服务器组,并且将剩余的20个请求放入队列中。然后,NGINX每100毫秒转发一个队列中的请求,并且只有当某个新进来的请求使得队列中的请求数目超过了20,则返回503给客户端。

无延迟排队

带有burst的配置产生平滑的网络流量,但是不实用,因为该配置会使得你的网站表现的很慢。在上面的例子中,队列中第20个数据包等待2秒才能被转发,这时该数据包的响应可能对于客户端已经没有了意义。为了处理这种情况,除了burst参数外,添加nodelay参数。

location /login/ {
      limit_req zone=mylimit burst=20 nodelay;

      proxy_pass http://my_upstream
}

带有nodelay参数,NGINX仍然会按照burst参数在队列中分配插槽(slot)以及利用已配置的限流,但是不是通过间隔地转发队列中的请求。相反,当某个请求来的太快,只要队列中有可用的空间(slot),NGINX会立即转发它。该插槽(slot)被标记为“已使用”,并且不会被释放给另一个请求,一直到经过适当的时间(在上面的例子中,是100毫秒)。

像之前一样假设有20个插槽的队列是空的,并且来自于给定的IP地址的21个请求同时地到达。NGINX立即转发这21个请求以及将队列中的20个插槽标记为“已使用”,然后每隔100毫秒释放一个插槽。(相反,如果有25个请求,NGINX会立即转发25个中的21个请求,标记20个插槽为“已使用”,并且用503状态拒绝4个请求。)

现在假设在转发第一个请求集合之后的101毫秒,有另外的20个请求同时地到达。队列中只有1个插槽被释放,因此NGINX转发1个请求,并且用503状态拒绝其它的19个请求。相反,如果在这20个新请求到达之前过去了501毫秒,则有5个插槽被释放,因此NGINX立即转发5个请求,并且拒绝其它15个请求。

效果等同于10个请求每秒的限流。如果你想利用请求之间的无限制性间隔的限流,nodelay选项则是非常有用的。

注意:对于大多数的部署,我们推荐在limit_req指令中包含burstnodelay参数。

高级设置的例子

通过结合基本的限流和其它的NGINX特性,你可以实现更多的细微的流量限制。

白名单

下面的例子展示了如何将限流作用在任何一个不在“白名单”中的请求上。

geo $limit {
        default 1;
        10.0.0.0/8 0;
        192.168.0.0/24 0;
}

map $limit $limit_key {
        0 "";
        1 $binary_remote_addr;
}

limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;

server {
        location / {
                limit_req zone=req_zone burst=10 nodelay;

                # ...
        }
}

这个例子同时使用了geomap指令。对于IP地址在白名单中的,geo块分配0值给$limit;其它所有不在白名单中的IP地址,分配1值。然后我们使用一个map去将这些值映射到某个key中,例如:

  • 如果$limit0$limit_key被设置为空字符串
  • 如果$limit1$limit_key被设置为客户端的IP地址的二进制格式

这个两个结合起来,对于白名单中的IP地址,$limit_key被设置为空字符串;否则,被设置为客户端的IP地址。当limit_req_zone指令的第一个参数是一个空字符串,限制不起作用,因此白名单的IP地址(在10.0.0.0/8和192.168.0.0/24子网中)没有被限制。其它所有的IP地址都被限制为5个请求每秒。

limit_req指令将限制作用在/定位中,并且允许在没有转发延迟的情况下,转发多达10个数据包。

在一个定位中包含多个limit_req指令

可以在单个定位(location)中包含多个limit_req指令。匹配给定的请求限制都会被使用,这意味着采用最严格的限制。例如,如果多于一个的指令使用了延迟,最终使用最长的延迟。类似地,如果某个指令使得请求被拒绝,即使其它的指令允许请求通过,最终还是被拒绝。

我们可以在白名单中的IP地址上应用某个限流来扩展之前的例子:

http {
      # ...

      limit_req_zone $limit_key zone=req_zone:10m rate=5r/s;
      limit_req_zone $binary_remote_addr zone=req_zone_wl:10m rate=15r/s;

      server {
            # ...
            location / {
                  limit_req zone=req_zone burst=10 nodelay;
                  limit_req zone=req_zone_wl burst=20 nodelay;
                  # ...
            }
      }
}

在白名单上的IP地址不匹配第一个限流(req_zone),但是能匹配第二个(req_zone_wl),因此这些IP地址被限制为15个请求每秒。不在白名单上的IP地址两个限流都能匹配上,因此最严格的那个限流起作用:5个请求每秒。

配置相关的特性

日志(Logging)

默认,NGNIX记录由于限流导致的延迟或丢弃的请求的日志,如下面的例子:

2015/06/13 04:20:00 [error] 120315#0: *32086 limiting requests, excess: 1.000 by zone "mylimit", client: 192.168.1.2, server: nginx.com, request: "GET / HTTP/1.0", host: "nginx.com"

该日志记录包含的字段:

  • limiting requests — 日志条目记录了某个限流的标志
  • excess — 超过这个请求代表的配置的速率的每毫秒请求数目
  • zone — 定义了启用了限流的区域
  • client — 产生请求的客户端IP地址
  • server — 服务器的IP地址或主机名
  • request — 客户端产生的实际的HTTP请求
  • host — HTTP头部主机名的值

默认,NGINX日志在error级别拒绝请求,如上面例子中的[error]所示。(它在低一个级别上记录延迟的请求,因此默认是info。)用limit_req_log_level指令来改变日志级别。下面我们设置在warn级别上记录被拒绝的请求的日志:

location /login/ {
      limit_req zone=mylimit burst=20 nodelay;
      limit_req_log_level warn;

      proxy_pass http://my_upstream;
}
发送给客户端的错误码

默认,当某个客户端超过它的限流,NGINX用503(Service Temporarily Unavailable)状态码来响应。使用limit_req_status指令设置一个不同的状态码(在下面的例子是444):

location /login/ {
      limit_req zone=mylimit burst=20 nodelay;
      limit_req_status 444;
}
拒绝对特定位置的所有请求

如果你想拒绝对于某个特定URL的所有请求,而不是仅仅的限制它们,可以为这个URL配置一个location块,并且在其中包含deny all指令:

location /foo.php {
      deny all;
}

原文地址:https://www.cnblogs.com/Template/p/11237249.html

时间: 2024-08-28 08:47:53

NGINX上的限流的相关文章

用nginx实现分布式限流(防DDOS攻击)

1.前言 一般对外暴露的系统,在促销或者黑客攻击时会涌来大量的请求,为了保护系统不被瞬间到来的高并发流量给打垮, 就需要限流 . 本文主要阐述如何用nginx 来实现限流. 听说 Hystrix 也可以, 各位有兴趣可以去研究哈 . 2. 首先部署一个对外暴露接口的程序 我这里部署的是一个spring boot 项目 里面暴露了如下接口, 很简单 package com.anuo.app.controller; import org.slf4j.Logger; import org.slf4j.

限流(四)nginx接入层限流

一.nginx限流模块 接入层指的是请求流量的入口,我们可以在这里做很多控制,比如:负载均衡,缓存,限流等. nginx中针对限流有两个模块可以处理: 1)ngx_http_limit_req_module:连接数限流模块 2)ngx_http_limit_conn_module:请求限流模块 连接数限流模块是基于计数器方式实现的,请求限流模块是基于漏桶算法实现的: 关于限流算法可以参考:https://www.cnblogs.com/lay2017/p/9060497.html 除了以上两种,

nginx网站限速限流配置——网站被频繁攻击,nginx上的设置limit_req和limit_conn

利用ngx_http_limit_req_module模块,可根据键值(如ip)限制每分钟的速率: limit_req_zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 "leaky bucket"  , http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html limit_req_conn 用来限制同一时间连接数,即并发限制  http://nginx.org/en/docs/http/ngx_http

nginx限流配置

一.前言 为了防止访问服务器的流量超过服务器最大承载量,需要对服务器访问流量进行限制,因为业务访问通过nginx进行转发,所以采取nginx配置进行限流操作.使用了nginx自带的两个模块ngx_http_limit_conn_module,ngx_http_limit_req_module进行限流,具体参考来源官网文档. http://nginx.org/en/docs/http/ngx_http_limit_req_module.html http://nginx.org/en/docs/h

高并发限流策略

在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹:而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开:而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀.抢购).写服务(如评论.下单).频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流. 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到

基于Redis实现分布式应用限流--转

原文地址:https://my.oschina.net/giegie/blog/1525931 摘要: 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务. 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务. 前几天在DD的公众号,看了一篇关于使用 瓜娃 实现单应用限流的方案 -->原文,参考<redis in action> 实现了一个jedis版本的,

聊聊高并发系统之限流特技-1 开涛

在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹:而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开:而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀.抢购).写服务(如评论.下单).频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流. 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到

高并发系统之限流特技

在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹:而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开:而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀.抢购).写服务(如评论.下单).频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流. 限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到

谈谈高并发系统的限流

开涛大神在博客中说过:在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.本文结合作者的一些经验介绍限流的相关概念.算法和常规的实现方式. 缓存 缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪.使用缓存不单单能够提升系统访问速度.提高并发访问量,也是保护数据库.保护系统的有效方式.大型网站一般主要是"读",缓存的使用很容易被想到.在大型"写"系统中,缓存也常常扮演者非常重要的角色.比如累积一些数据批量写入,内存里面的缓存