nginx_http_limit_req_module用于限制定义key(单个ip)的请求的处理速度限制的方法如同漏斗,每秒固定处理请求数,推迟过多请求,如但客户端ip的每秒请求数.还可以用于安全防护,限制密码撞库暴力破解等操作频率,也可以通过把请求频率限制在一个正常范围来抵御ddos攻击,不过更常见的使用情况是通过限制请求的数量来确保后端的upstream服务器不会在短时间内遭受到大量的流量访问从而导致服务异常。
1. 工作原理
nginx 中限速(rate limiting)的主要算法原理是漏斗算法:基本原理就是以漏斗算法,基本原理就是:以漏斗为例,水从顶部倒入,从底下漏出.
在桶满后该算法有两种处理方式Traffic Shaping和Traffic Policing
1. 暂时拦截住上方水的向下流动,等待桶中的一部分水漏走后,再放行上方水。
2. 溢出的上方水直接抛弃。
将水看作网络通信中数据包的抽象,则方式1起到的效果称为Traffic Shaping,方式2起到的效果称为Traffic Policing
由此可见,Traffic Shaping的核心理念是"等待",Traffic Policing的核心理念是"丢弃"。它们是两种常见的流速控制方法
漏斗在一定程度上代表服务器的处理能力,请求根据先进先出(FIFO)调度算法等待处理,若倒入的水的速度小于漏水的速度,可以理解为服务器能够处理完所有的请求,此时整体服务表现正常。如果倒入水的速度大于漏水的速度,那么水桶内的水会不断增加直到最后溢出,这种情况下在水桶中的水可以理解为在队列中等待的请求,而溢出的水则表示直接被丢弃不处理的请求。
2. limit_req_zone 指令
Syntax: limit_req_zone key zone=name:size rate=rate;
Context: http
Defaule: -
说明:
key: 使用nginx内置变量作为键,用于限制请求的变量,可以使用$binary_remote_addr,它的特点是使用二进制来表示IP地址,如1.1.1.1这个ip在$remote_addr中显示为1.1.1.1,$binary_remote_addr表示为二进制形式,因此$binary_remote_addr占用的空间要比$remote_addr更少.使用$binary_remote_addr意味着将每个唯一的用户ip作为心智速率的判断依据.
zone: 定义用于存储前面定义的key变量和限制其访问请求频率tate变量的共享内存空间,将信息保存在共享内存中的好处是能够在多个worker进程中共享。存储空间的定义由两个部分组成:zone=
后面的名称以及冒号后面的大小,如zone=mylimit:10m
就是一个名为mylimit
的大小为10m
的共享内存空间。以$binary_remote_addr
变量为例,它使用4 bytes来存储IPv4 地址或者是使用16 bytes来存储IPv6地址。存储状态始终在32位平台上占用64个字节,并在64位平台上占用128个字节。考虑到现在的服务器绝大多数都是64位的操作系统,1M的大小可以保留大约8192个128字节的状态。
当存储空间耗尽的时候,如果需要记录新的值,那么就会通过LRU算法移除旧的变量来腾出空间,如果这样腾出来的空间还是不足以接纳新的记录值,那么nginx就会返回状态码503
(Service
Temporarily
Unavailable)
。此外,为了防止内存耗尽,nginx每次创建一个新记录值的时候就会清理掉两个60秒内没被使用过的旧记录值。
rate: 设定允许的最大请求速率。nginx实现的是毫秒级别的控制粒度,10r/s对应的就是1r/100ms,这也就意味着在没有设置bursts
的情况下,如果一个请求接受处理之后的100ms内出现第二个请求,那么它就会被拒绝处理。
limit_req_zone
指令设置了速率限制和共享内存区域的参数,但它实际上并不限制请求速率。因此我们需要通过在contexts
中使用limit_req
指令来将其限制应用于特定location
或server
块。在上面的例子里,我们将请求速率限制在/login/
这个location
块中。因此现在每个唯一的 IP 地址被限制为每秒 10 个**/login/**请求,或者更准确地说,不能在前一个 URL 请求的 100 毫秒内发出对该 URL 的第二次请求
实例:
limit_req_zone $binary_remote_addr zone=cehis:10m rate=3r/s;server {location /login/ {limit_req zone=ceshi;proxy_pass http://my_upstream;}
}
说明: 使用了limit_req_zone指令定义了一个限速zone,名为ceshi,大小为10m,对应的变量是$binary_remote_addr,限制的请求速率是每秒限制3个请求(3requests/secends),在login
这个location
中使用limit_req指定了限制的zone
。
3. limit_req 指令
Syntax: limit_req zone=name [burst=number] [nodelay];
Context: http,server,location
功能: 设置使用哪个共享内存限制域和允许被处理的最大请求数阀值。如果请求的频率超过了限制域配置的值,请求处理会被延迟,所以所有的请求都是以定义的频率被处理的。超过频率限制的请求会被延迟,直到被延迟的请求数超过了定义的阀值,这时,这个请求会被终止,并返回503(Service Tempporarily Unavailable)错误,这个阀值的默认值为0
说明:
burst: 可选项。后面接整数,表示最大允许超过频率限制的请求数(这个配置的意思是设置一个大小为number的缓冲区,当有大量请求(爆发)过来时,超过了访问频次限制的请求可以先放到这个缓冲区内等待,但是这个等待区里的位置只有number个,超过的请求会直接报503的错误然后返回。)
nodelay: 可选项。如果不希望超过的请求被延迟,可以使用 nodelay 参数(如果设置,会在瞬时提供处理(burst + rate)个请求的能力,请求超过(burst + rate)的时候就会直接返回503,永远不存在请求需要等待的情况;如果没有设置,则所有请求会依次等待排队)
例子:
limit_req_zone $binary_remote_addr zone=ceshi:10m rate=10r/m;
limit_req zone=ceshi;
#不加burst和不加nodelay
说明:定义的名为ceshi的limit_req_zone(其定义的限制频率为每分钟的请求数为10个,即每6秒1次),假设同一客户端在同一时刻发起50个请求(前提这50个请求在服务器在6秒内收到),那么,服务器只会成功响应一次请求,对于其余49次请求服务器均不予响应并直接返回了503。
limit_req zone=ceshi burst=5;
#只加burst和不加nodelay
说明: 定义的名为ceshi的limit_req_zone(其定义的限制频率为每分钟的请求数为10个,即每6秒1次),假设同一客户端在同一时刻发起50个请求,那么,服务器只会成功响应5+1=6次请求,但是这6次成功的请求会延时限制(其中第一次成功被服务器处理的请求是在6秒内,第二次是在大于6秒小于12秒内请求成功的,第三次则为大于十二秒小于十八秒内请求成功的,以此类推),对于其余44次请求服务器均不予响应并直接返回了503(这是因为设置了burst=5,在服务器接收到50个并发请求后,先处理1个请求,同时将5个请求放入burst缓冲队列中,等待处理。而超过(burst+1)数量的请求就被直接抛弃了,即直接抛弃了44个请求)
limit_req zone=ceshi burst=5 nodelay;
说明: 定义的名为ceshi的limit_req_zone(其定义的限制频率为每分钟的请求数为10个,即每6秒1次),假设同一客户端在同一时刻发起50个请求(前提这50个请求在服务器在30秒内收到),那么,服务器只会成功响应5+1=6次请求,但是没有时间的限制(即只要服务器处理速度够快,可以在1秒内处理完这6个请求),对于剩下的44个请求,直接返回503,在下一秒如果继续向服务端发送10个请求,服务端会直接拒绝这10个请求并返回503。因为设定了每6s处理1个请求,所以直到30s之后,才可以再处理一个请求,即如果此时向服务端发送10个请求,会返回9个503,一个200
示例
limit_req_zone $binary_remote_addr zone=ceshi:10m rate=5r/s;server {listen 80;location / {limit_req zone=ceshi burst=12 delay=8;proxy_pass http://upstream;}
}
说明: 这个示例是通过limit_req指令和delay参数来实现两段限速,delay参数将nginx配置为允许突发请求以适应典型的web浏览器请求模式,然后将额外的过度请求限制到一定程度,超过该点的额外过度请求将会被拒绝.
zone为ceshi的大小为10m,5r/s的限制速率一般来说网站通常每个页面有4到6个资源,并且永远不会超过12个资源,该配置允许最多12个请求的突发,其中前8个请求会被转发给upstream处理,在达到5r/s的请求限制之后,从第6个到13个请求会被添加到延迟(delay)中,在之后的任何请求都会被拒绝.