Nginx流控

流量限制(rate-limiting),是Nginx中一个很是实用,却常常被错误理解和错误配置的功能。咱们能够用来限制用户在给定时间内HTTP请求的数量。请求,能够是一个简单网站首页的GET请求,也能够是登陆表单的POST请求。php

流量限制能够用做安全目的,好比能够减慢暴力密码破解的速率。经过将传入请求的速率限制为真实用户的典型值,并标识目标URL地址(经过日志),还能够用来抵御DDOS攻击。更常见的状况,该功能被用来保护上游应用服务器不被同时太多用户请求所压垮。node

本篇文章将会介绍Nginx的 流量限制 的基础知识和高级配置,”流量限制”在Nginx Plus中也适用。nginx

Nginx如何限流

Nginx的”流量限制”使用漏桶算法(leaky bucket algorithm),该算法在通信和分组交换计算机网络中普遍使用,用以处理带宽有限时的突发状况。就比如,一个桶口在倒水,桶底在漏水的水桶。若是桶口倒水的速率大于桶底的漏水速率,桶里面的水将会溢出;一样,在请求处理方面,水表明来自客户端的请求,水桶表明根据”先进先出调度算法”(FIFO)等待被处理的请求队列,桶底漏出的水表明离开缓冲区被服务器处理的请求,桶口溢出的水表明被丢弃和不被处理的请求。  git

配置基本的限流

“流量限制”配置两个主要的指令,limit_req_zonelimit_req,以下所示:github

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_zone指令定义了流量限制相关的参数,而limit_req指令在出现的上下文中启用流量限制(示例中,对于”/login/”的全部请求)。算法

limit_req_zone指令一般在HTTP块中定义,使其可在多个上下文中使用,它须要如下三个参数:安全

  • Key - 定义应用限制的请求特性。示例中的Nginx变量$binary_remote_addr,保存客户端IP地址的二进制形式。这意味着,咱们能够将每一个不一样的IP地址限制到,经过第三个参数设置的请求速率。(使用该变量是由于比字符串形式的客户端IP地址$remote_addr,占用更少的空间)bash

  • Zone - 定义用于存储每一个IP地址状态以及被限制请求URL访问频率的共享内存区域。保存在内存共享区域的信息,意味着能够在Nginx的worker进程之间共享。定义分为两个部分:经过zone=keyword标识区域的名字,以及冒号后面跟区域大小。16000个IP地址的状态信息,大约须要1MB,因此示例中区域能够存储160000个IP地址。服务器

  • Rate - 定义最大请求速率。在示例中,速率不能超过每秒10个请求。Nginx实际上以毫秒的粒度来跟踪请求,因此速率限制至关于每100毫秒1个请求。由于不容许”突发状况”(见下一章节),这意味着在前一个请求100毫秒内到达的请求将被拒绝。网络

当Nginx须要添加新条目时存储空间不足,将会删除旧条目。若是释放的空间仍不够容纳新记录,Nginx将会返回 503状态码(Service Temporarily Unavailable)。另外,为了防止内存被耗尽,Nginx每次建立新条目时,最多删除两条60秒内未使用的条目。

limit_req_zone指令设置流量限制和共享内存区域的参数,但实际上并不限制请求速率。因此须要经过添加limit_req指令,将流量限制应用在特定的location或者server块。在上面示例中,咱们对/login/请求进行流量限制。

如今每一个IP地址被限制为每秒只能请求10次/login/,更准确地说,在前一个请求的100毫秒内不能请求该URL。

处理突发

若是咱们在100毫秒内接收到2个请求,怎么办?对于第二个请求,Nginx将给客户端返回状态码503。这可能并非咱们想要的结果,由于应用本质上趋向于突发性。相反地,咱们但愿缓冲任何超额的请求,而后及时地处理它们。咱们更新下配置,在limit_req中使用burst参数:

location /login/ {
    limit_req zone=mylimit burst=20;
    proxy_pass http://my_upstream;
}

burst参数定义了超出zone指定速率的状况下(示例中的mylimit区域,速率限制在每秒10个请求,或每100毫秒一个请求),客户端还能发起多少请求。上一个请求100毫秒内到达的请求将会被放入队列,咱们将队列大小设置为20。

这意味着,若是从一个给定IP地址发送21个请求,Nginx会当即将第一个请求发送到上游服务器群,而后将余下20个请求放在队列中。而后每100毫秒转发一个排队的请求,只有当传入请求使队列中排队的请求数超过20时,Nginx才会向客户端返回503。

无延迟的排队

配置burst参数将会使通信更流畅,可是可能会不太实用,由于该配置会使站点看起来很慢。在上面的示例中,队列中的第20个包须要等待2秒才能被转发,此时返回给客户端的响应可能再也不有用。要解决这个状况,能够在burst参数后添加nodelay参数:

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

    proxy_pass http://my_upstream;
}

使用nodelay参数,Nginx仍将根据burst参数分配队列中的位置,并应用已配置的速率限制,而不是清理队列中等待转发的请求。相反地,当一个请求到达“太早”时,只要在队列中能分配位置,Nginx将当即转发这个请求。将队列中的该位置标记为”taken”(占据),而且不会被释放以供另外一个请求使用,直到一段时间后才会被释放(在这个示例中是,100毫秒后)。

假设如前所述,队列中有20个空位,从给定的IP地址发出的21个请求同时到达。Nginx会当即转发这个21个请求,而且标记队列中占据的20个位置,而后每100毫秒释放一个位置。若是是25个请求同时到达,Nginx将会当即转发其中的21个请求,标记队列中占据的20个位置,而且返回503状态码来拒绝剩下的4个请求。

如今假设,第一组请求被转发后101毫秒,另20个请求同时到达。队列中只会有一个位置被释放,因此Nginx转发一个请求并返回503状态码来拒绝其余19个请求。若是在20个新请求到达以前已通过去了501毫秒,5个位置被释放,因此Nginx当即转发5个请求并拒绝另外15个。

效果至关于每秒10个请求的“流量限制”。若是但愿不限制两个请求间容许间隔的状况下实施“流量限制”,nodelay参数是很实用的。

注意: 对于大部分部署,咱们建议使用burstnodelay参数来配置limit_req指令。

高级配置示例

经过将基本的“流量限制”与其余Nginx功能配合使用,咱们能够实现更细粒度的流量限制。

白名单

下面这个例子将展现,如何对任何不在白名单内的请求强制执行“流量限制”:

geo $limit {
    default         1;
    10.0.0.0/8         0;
    192.168.0.0/64     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指令。geo块将给在白名单中的IP地址对应的$limit变量分配一个值0,给其它不在白名单中的分配一个值1。而后咱们使用一个映射将这些值转为key,以下:

  • 若是$limit变量的值是0,$limit_key变量将被赋值为空字符串
  • 若是$limit变量的值是1,$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指令将限制应用到/的location块,容许在配置的限制上最多超过10个数据包的突发,而且不会延迟转发。

location包含多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_wl,而且被限制到每秒15个请求。不在白名单内的IP地址两个限制能匹配到,因此应用限制更强的那个:每秒5个请求。

配置相关功能

日志记录

默认状况下,Nginx会在日志中记录因为流量限制而延迟或丢弃的请求,以下所示:

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报头中host的值

默认状况下,Nginx以error级别来记录被拒绝的请求,如上面示例中的[error]所示(Ngin以较低级别记录延时请求,通常是info级别)。如要更改Nginx的日志记录级别,须要使用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;
}

指定location拒绝全部请求

若是你想拒绝某个指定URL地址的全部请求,而不是仅仅对其限速,只须要在location块中配置deny all指令:

location /foo.php {
    deny all;
}

总结

前文已经涵盖了Nginx和Nginx Plus提供的“流量限制”的不少功能,包括为HTTP请求的不一样loation设置请求速率,给“流量限制”配置burstnodelay参数。还涵盖了针对客户端IP地址的白名单和黑名单应用不一样“流量限制”的高级配置,阐述了如何去日志记录被拒绝和延时的请求。

 

 

转自:https://legolasng.github.io/2017/08/27/nginx-rate-limiting/#%E6%80%BB%E7%BB%93

https://www.nginx.com/blog/rate-limiting-nginx/

相关文章
相关标签/搜索