死磕nginx系列--nginx 限流配置

限流算法

令牌桶算法

令牌桶算法

算法思想是:html

  • 令牌以固定速率产生,并缓存到令牌桶中;
  • 令牌桶放满时,多余的令牌被丢弃;
  • 请求要消耗等比例的令牌才能被处理;
  • 令牌不够时,请求被缓存。

漏桶算法

漏桶算法

算法思想是:node

  • 水(请求)从上方倒入水桶,从水桶下方流出(被处理);
  • 来不及流出的水存在水桶中(缓冲),以固定速率流出;
  • 水桶满后水溢出(丢弃)。
  • 这个算法的核心是:缓存请求、匀速处理、多余的请求直接丢弃。
    相比漏桶算法,令牌桶算法不一样之处在于它不但有一只“桶”,还有个队列,这个桶是用来存放令牌的,队列才是用来存放请求的。

从做用上来讲,漏桶和令牌桶算法最明显的区别就是是否容许突发流量(burst)的处理,漏桶算法可以强行限制数据的实时传输(处理)速率,对突发流量不作额外处理;而令牌桶算法可以在限制数据的平均传输速率的同时容许某种程度的突发传输。nginx

Nginx按请求速率限速模块使用的是漏桶算法,即可以强行保证请求的实时处理速度不会超过设置的阈值。算法

Nginx官方版本限制IP的链接和并发分别有两个模块:shell

  • limit_req_zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 "leaky bucket"。
  • limit_req_conn 用来限制同一时间链接数,即并发限制。

limit_req_zone 参数配置

Syntax: limit_req zone=name [burst=number] [nodelay];
Default:    —
Context:    http, server, location

limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;缓存

  • 第一个参数:$binary_remote_addr 表示经过remote_addr这个标识来作限制,“binary_”的目的是缩写内存占用量,是限制同一客户端ip地址。
  • 第二个参数:zone=one:10m表示生成一个大小为10M,名字为one的内存区域,用来存储访问的频次信息。
  • 第三个参数:rate=1r/s表示容许相同标识的客户端的访问频次,这里限制的是每秒1次,还能够有好比30r/m的。

limit_req zone=one burst=5 nodelay;服务器

  • 第一个参数:zone=one 设置使用哪一个配置区域来作限制,与上面limit_req_zone 里的name对应。
  • 第二个参数:burst=5,重点说明一下这个配置,burst爆发的意思,这个配置的意思是设置一个大小为5的缓冲区当有大量请求(爆发)过来时,超过了访问频次限制的请求能够先放到这个缓冲区内。
  • 第三个参数:nodelay,若是设置,超过访问频次并且缓冲区也满了的时候就会直接返回503,若是没有设置,则全部请求会等待排队。

例子:网络

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;
    server {
        location /search/ {
            limit_req zone=one burst=5 nodelay;
        }
}

下面配置能够限制特定UA(好比搜索引擎)的访问:并发

limit_req_zone  $anti_spider  zone=one:10m   rate=10r/s;
limit_req zone=one burst=100 nodelay;
if ($http_user_agent ~* "googlebot|bingbot|Feedfetcher-Google") {
    set $anti_spider $http_user_agent;
}

其余参数ide

Syntax: limit_req_log_level info | notice | warn | error;
Default:    
limit_req_log_level error;
Context:    http, server, location

当服务器因为limit被限速或缓存时,配置写入日志。延迟的记录比拒绝的记录低一个级别。例子:limit_req_log_level notice延迟的的基本是info。

Syntax: limit_req_status code;
Default:    
limit_req_status 503;
Context:    http, server, location

设置拒绝请求的返回值。值只能设置 400 到 599 之间。

ngx_http_limit_conn_module 参数配置

这个模块用来限制单个IP的请求数。并不是全部的链接都被计数。只有在服务器处理了请求而且已经读取了整个请求头时,链接才被计数。

Syntax: limit_conn zone number;
Default:    —
Context:    http, server, location
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    location /download/ {
        limit_conn addr 1;
    }

一次只容许每一个IP地址一个链接。

limit_conn_zone $binary_remote_addr zone=perip:10m;
limit_conn_zone $server_name zone=perserver:10m;

server {
    ...
    limit_conn perip 10;
    limit_conn perserver 100;
}

能够配置多个limit_conn指令。例如,以上配置将限制每一个客户端IP链接到服务器的数量,同时限制链接到虚拟服务器的总数。

Syntax: limit_conn_zone key zone=name:size;
Default:    —
Context:    http
limit_conn_zone $binary_remote_addr zone=addr:10m;

在这里,客户端IP地址做为关键。请注意,不是$ remote_addr,而是使用$ binary_remote_addr变量。 $ remote_addr变量的大小能够从7到15个字节不等。存储的状态在32位平台上占用32或64字节的内存,在64位平台上老是占用64字节。对于IPv4地址,$ binary_remote_addr变量的大小始终为4个字节,对于IPv6地址则为16个字节。存储状态在32位平台上始终占用32或64个字节,在64位平台上占用64个字节。一个兆字节的区域能够保持大约32000个32字节的状态或大约16000个64字节的状态。若是区域存储耗尽,服务器会将错误返回给全部其余请求。

Syntax: limit_conn_log_level info | notice | warn | error;
Default:    
limit_conn_log_level error;
Context:    http, server, location

当服务器限制链接数时,设置所需的日志记录级别。

Syntax: limit_conn_status code;
Default:    
limit_conn_status 503;
Context:    http, server, location

设置拒绝请求的返回值。

实战

实例一 限制访问速率

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit;
    }
}

上述规则限制了每一个IP访问的速度为2r/s,并将该规则做用于根目录。若是单个IP在很是短的时间内并发发送多个请求,结果会怎样呢?
单个IP 10ms内发送6个请求

咱们使用单个IP在10ms内发并发送了6个请求,只有1个成功,剩下的5个都被拒绝。咱们设置的速度是2r/s,为何只有1个成功呢,是否是Nginx限制错了?固然不是,是由于Nginx的限流统计是基于毫秒的,咱们设置的速度是2r/s,转换一下就是500ms内单个IP只容许经过1个请求,从501ms开始才容许经过第二个请求。

实例二 burst缓存处理

咱们看到,咱们短期内发送了大量请求,Nginx按照毫秒级精度统计,超出限制的请求直接拒绝。这在实际场景中未免过于苛刻,真实网络环境中请求到来不是匀速的,极可能有请求“突发”的状况,也就是“一股子一股子”的。Nginx考虑到了这种状况,能够经过burst关键字开启对突发请求的缓存处理,而不是直接拒绝。
来看咱们的配置:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4;
    }
}

咱们加入了burst=4,意思是每一个key(此处是每一个IP)最多容许4个突发请求的到来。若是单个IP在10ms内发送6个请求,结果会怎样呢?
设置burst

相比实例一成功数增长了4个,这个咱们设置的burst数目是一致的。具体处理流程是:1个请求被当即处理,4个请求被放到burst队列里,另一个请求被拒绝。经过burst参数,咱们使得Nginx限流具有了缓存处理突发流量的能力。

可是请注意:burst的做用是让多余的请求能够先放到队列里,慢慢处理。若是不加nodelay参数,队列里的请求不会当即处理,而是按照rate设置的速度,以毫秒级精确的速度慢慢处理。

实例三 nodelay下降排队时间

实例二中咱们看到,经过设置burst参数,咱们能够容许Nginx缓存处理必定程度的突发,多余的请求能够先放到队列里,慢慢处理,这起到了平滑流量的做用。可是若是队列设置的比较大,请求排队的时间就会比较长,用户角度看来就是RT变长了,这对用户很不友好。有什么解决办法呢?nodelay参数容许请求在排队的时候就当即被处理,也就是说只要请求可以进入burst队列,就会当即被后台worker处理,请注意,这意味着burst设置了nodelay时,系统瞬间的QPS可能会超过rate设置的阈值。nodelay参数要跟burst一块儿使用才有做用。

延续实例二的配置,咱们加入nodelay选项:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
    }
}

单个IP 10ms内并发发送6个请求,结果以下:
设置burst和nodela

跟实例二相比,请求成功率没变化,可是整体耗时变短了。这怎么解释呢?实例二中,有4个请求被放到burst队列当中,工做进程每隔500ms(rate=2r/s)取一个请求进行处理,最后一个请求要排队2s才会被处理;实例三中,请求放入队列跟实例二是同样的,但不一样的是,队列中的请求同时具备了被处理的资格,因此实例三中的5个请求能够说是同时开始被处理的,花费时间天然变短了。

可是请注意,虽然设置burst和nodelay可以下降突发请求的处理时间,可是长期来看并不会提升吞吐量的上限,长期吞吐量的上限是由rate决定的,由于nodelay只能保证burst的请求被当即处理,但Nginx会限制队列元素释放的速度,就像是限制了令牌桶中令牌产生的速度。

看到这里你可能会问,加入了nodelay参数以后的限速算法,到底算是哪个“桶”,是漏桶算法仍是令牌桶算法?固然还算是漏桶算法。考虑一种状况,令牌桶算法的token为耗尽时会怎么作呢?因为它有一个请求队列,因此会把接下来的请求缓存下来,缓存多少受限于队列大小。但此时缓存这些请求还有意义吗?若是server已通过载,缓存队列愈来愈长,RT愈来愈高,即便过了好久请求被处理了,对用户来讲也没什么价值了。因此当token不够用时,最明智的作法就是直接拒绝用户的请求,这就成了漏桶算法。

示例四 自定义返回值

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=2r/s;
server { 
    location / { 
        limit_req zone=mylimit burst=4 nodelay;
        limit_req_status 598;
    }
}

默认状况下 没有配置 status 返回值的状态:
没有配置 status
自定义 status 返回值的状态:
自定义返回值

参考文档

Nginx限制访问速率和最大并发链接数模块--limit (防止DDOS攻击)
Nginx 限流
关于nginx的限速模块
Nginx 源代码笔记 - HTTP 模块 - 流控
Module ngx_http_limit_conn_module
Module ngx_http_limit_req_module
Nginx限速模块初探

相关文章
相关标签/搜索