限速限流

限流(rate limiting)

在生产环境中,为了保护WEB服务器的安全,我们都会对用户的访问做出一些限制,保证服务器的安全及资源的合理分配。

限流是NGINX众多特性中最有用的,也是经常容易被误解和错误配置的。

特性之一访问请求限速。该特性可以限制某个用户在一个给定时间段内能够产生的HTTP请求数。请求可以简单到就是一个对于主页的GET请求或者一个登陆表格的POST请求。用于安全目的上,比如减慢暴力密码破解攻击。通过限制进来的请求速率,并且(结合日志)标记出目标URLs来帮助防范DDoS攻击。一般地说,限流是用在保护上游应用服务器不被在同一时刻的大量用户请求湮没

限速说的很笼统,其实限速分为很多种限速方法:

1)下载速度限速

2)单位时间内请求数限制

3)基于客户端的并发连接限速

nginx限速模块

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

limit_req_zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 “leaky bucket”。

limit_req_conn 用来限制同一时间连接数,即并发限制。

应用场景

下载限速:限制现在速度及并发连接数,应用在下载服务器中,保护带宽及服务器的IO资源。

请求限速:限制单位时间内用户访问请求,防止恶意攻击,保护服务器及资源安全。

限速原理


漏桶算法



算法思想是:

    水(请求)从上方倒入水桶,从水桶下方流出(被处理);

    来不及流出的水存在水桶中(缓冲),以固定速率流出;

    水桶满后水溢出(丢弃)。

    这个算法的核心是:缓存请求、匀速处理、多余的请求直接丢弃。

    相比漏桶算法,令牌桶算法不同之处在于它不但有一只“桶”,还有个队列,这个桶是用来存放令牌的,队列才是用来存放请求的。


nginx出口流量限制 nginx的限流原理_服务器

限速实现


1)单位时间内请求数限制: 可用于防止DDoS攻击,或防止上游服务器同时被太多请求淹没。


16000个IP地址的状态信息,大约需要1MB


#基于IP对下载速率做限制  限制每秒处理1次请求,对突发超过5个以后的请求放入缓存区
http {
    limit_req_zone $binary_remote_addr zone=baism:10m rate=1r/s;
    server {
        location /abc {
            limit_req zone=baism burst=5 nodelay;
        }
}

参数解释:

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

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

limit_req zone=baism burst=5 nodelay;

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

burst详解及试验:


        设定一个场景,一个nginx反向代理的后端服务单页请求数是30个,如果我们设置30r/s的话,那么这个burst设置就没有意义了,但是如果我们不想把rate设置成这么大,比如5r/s,也要完成这个一个页面30个请求的需求。这个时候burst就有作用了。(前后端一体化的服务,其静态资源单次刷新后,再访问时只会刷新动态请求)。

        假设我们一个页面是30个请求,后面每秒都会刷新一个页面,但是新页面由于静态资源已经刷新,只有2-3个请求,因此我们把burst设置成200个(可以让用户连续强制刷新大约8次)。

        当我们第一次访问时,由于rate是5个,只能处理5个,另外的25个会放到burst队列,虽然设置成nodelay会把这25个也处理了,但是队列中占用的25个位置,需要25/rate=5秒来清除,如果第二次请求是3个,那么我们实时处理了3个请求,并且rate剩余2个可以清空burst队列,这个时候,队列就是占用了23个位置。最后如果我们队列满了的话,那么就会出现503的请求

        验证效果:基本按上面的方式,连续每秒30个请求,即f5强制刷新,那么大约会再多少次后出现503的情况呢,答案大约是8次(200/25)。


burst=(每次请求数-请求限制数)*允许强制刷新的次数

2)限制并发连接数 

limit_conn_zone 中 1MB 的内存空间可以存储 32 000 个 32B 或 16 000 个 64B 的变量计数状态。

变量计数状态在 32 位系统平台占用 32B 或 64B,在 64 位系统平台占用 64B

10M的话换算下来可以保留16万个IP地址信息

#基于IP做连接限制  限制同一IP并发为1
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        location /abc {
           limit_conn addr 1;
        }
    }
}


3)限制下载速度


# 下载速度为100k
server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        location /abc {
           limit_rate 100k;
        }
    }
}



4)综合案例



http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    #基于IP做连接限制
    limit_conn_zone $binary_remote_addr zone=addr:10m;
    #基于IP对下载速率做限制,限制每秒处理1次请求,
    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

    server {
        listen       80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        location /abc {
           # 对突发超过5个以后的请求放入缓存区
           limit_req zone=one burst=5 nodelay;
           # 限制并发连接数为4
           limit_conn addr 4;
                    # 限制下载速度为100K
           limit_rate 100k;
        }
    }
}

静态资源优化配置

sendfile:开启零拷贝

默认值:off

位置: http、server、location



tcp_nopush:该指令必须在sendfile打开的状态下才会生效,主要是用来提升网络包的传输'效率'




默认值:off

位置:http、server、location


tcp_nodelay:该指令必须在keep-alive连接开启的情况下才生效,来提高网络包传输的'实时性' 

默认值:on

位置:http、server、location

 "tcp_nopush"和”tcp_nodelay“看起来是"互斥的",在linux2.5.9以后的版本中两者是可以兼容的

三个指令都开启的好处是:

sendfile可以开启高效的文件传输模式。

tcp_nopush开启可以确保在发送到客户端之前数据包已经充分“填满”, 这大大减少了网络开销,并加快了文件发送的速度。

当它到达最后一个可能因为没有“填满”而暂停的数据包时,Nginx会忽略tcp_nopush参数, 然后,tcp_nodelay强制套接字发送数据。

由此可知,tcp_nopush可以与tcp_nodelay一起设置,它比单独配置tcp_nodelay具有更强的性能。

所以我们可以使用如下配置来优化Nginx静态资源的处理。

sendfile on;
tcp_nopush on;
tcp_nodelay on;