Nginx限流

Nginx限流原理和实战

Posted by 张晨 on March 17, 2021

简介

我们经常会遇到这样的问题,当我们系统在开秒杀活动的时候,用户就会疯狂的点击刷新按钮.然后每个人都这么点击的话.就会导致系统崩溃了.所以这个时候我们就需要运用一些措施来防止用户的这种疯狂的行为.那就是我们今天要讲到的限流了.

限流的方式有很多种啊,今天我们主要讲的是nginx的模块上限流.但这并不是唯一的解决方案,其实你也可以使用代码实现自己的限流机制.也可以达到非常好的效果.

限流的两种算法

今天我们讲解两种限流的算法.这两种算法都是比较经典的.我们的nginx是按照下面的漏桶算法来实现的.先给大家看看原理哈.

  • 令牌桶算法

程序会按照一定的速率向桶里面放入令牌.并且保证放入的令牌的数量,比如最多只会放入100个令牌.那么同时可以并发的处理100个请求.注意了.这里是并发执行的.

只有拿到令牌的请求,才会被继续执行,如果拿不到令牌,这条请求可能直接被丢弃.或者是被缓存起来.

  • 漏桶算法(Nginx采用的方式)

设置一个桶的最大容量.然后还需要设置一个桶的处理速率.也就是流出的速度.当请求过来之后,会判断桶是否溢出.如果桶已经满了,这个请求可能被直接丢弃.如果桶没有满,那么就进入排队.程序会按照桶的速率来进行处理.

令牌桶和漏桶的本质区别就是.一个是可以并发的(令牌桶).一个是不可以并发的(漏桶).

Nginx限流配置

  • limit_req_zone
Syntax: limit_req_zone key zone=name:size rate=rate [sync];
Default:
Context: http

示例:

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

上面这条命令作用于http层.它的作用是定义一个限流的程序.

这个程序是按照$binary_remote_addr来进行限流的.

这个程序的名字叫做one.

分配给这个程序的存储空间是10m.

这个程序的限流速率是每秒钟10个请求10r/s

这里解释一下这一行配置.

$binary_remote_addr是nginx官方推荐的方式.它能获取用户的ip地址.并且占用的空间比较少.

大概是1m空间可以存储16000个地址

这里的10r/s不好理解.我解释一下10r/s.这里的意思就是每100ms可以处理一个请求.因为nginx这里的配置是基于漏桶算法的.它的流出速率就是100ms才处理一个请求

原文如下

1
A client IP address serves as a key. Note that instead of $remote_addr, the $binary_remote_addr variable is used here. The $binary_remote_addr variable’s size is always 4 bytes for IPv4 addresses or 16 bytes for IPv6 addresses. The stored state always occupies 64 bytes on 32-bit platforms and 128 bytes on 64-bit platforms. One megabyte zone can keep about 16 thousand 64-byte states or about 8 thousand 128-byte states.
  • limit_req
Syntax: limit_req zone=name [burst=number] [nodelay | delay=number];
Default:
Context: http, server, location

示例:

1
2
3
4
5
6
limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

server {
    location /search/ {
        limit_req zone=one burst=5;
    }

上面这条命令作用于http, server, location这几个地方.主要是应用刚才上面定义的那个限流程序.输入正确的限流程序名字就好的.

这里有几个重要的参数需要解释一下

burst是爆发的意思,就是当请求量已经超出的时候,应该怎么处理.上面这条命令就设置了,可以缓冲5个请求.

nodelay 还可以设置这个参数.如果不需要在限制请求时延迟过多的请求,则应使用参数nodelay

原文如下

1
2
3
4
5
allow not more than 1 request per second at an average, with bursts not exceeding 5 requests.

If delaying of excessive requests while requests are being limited is not desired, the parameter nodelay should be used:

limit_req zone=one burst=5 nodelay;

实际例子

  • 限制每个ip地址,访问one.html的速率是每秒5次.(也就是200ms一次)

  • 限制server访问次数为每秒100次(也就是每10ms处理一个请求).

Nginx配置如下

文件名:limiting.test.com.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 定义一个名为one的限流程序.空间大小是10m,限流依据是ip地址.访问速率是(1000/5=)200ms一个请求.
limit_req_zone $binary_remote_addr zone=one:10m rate=5r/s;
# 定义一个名为two的限流程序.空间大小为10,限流依据是server.访问速率是(1000/100=)10ms一个请求.
limit_req_zone $server_name zone=two:10m rate=200r/s;

server {
    listen       80;
    server_name  limiting.test.com;

    # Define the vhost to serve files
    root         "/shared/httpd/limiting";
    index        index.html index.htm;

    location = /one.html {
        # 应用one限流程序 爆发缓存5个 多余的请求不等待
				limit_req zone=one burst=5 nodelay;
    }


    location = /two.html {
        # 应用two限流程序 爆发缓存10个 多余的请求不等待
				limit_req zone=two burst=10 nodelay;
    }
}

分别使用ab测试工具访问one.html和two.html的结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
➜  ~ ab -n 100 -c 100  "http://limiting.test.com/one.html"
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking limiting.test.com (be patient).....done


Server Software:        nginx/1.18.0
Server Hostname:        limiting.test.com
Server Port:            80

Document Path:          /one.html
Document Length:        13 bytes

Concurrency Level:      100
Time taken for tests:   0.063 seconds
Complete requests:      100
Failed requests:        94
   (Connect: 0, Receive: 0, Length: 94, Exceptions: 0)
Non-2xx responses:      94
Total transferred:      39244 bytes
HTML transferred:       18596 bytes
Requests per second:    1595.05 [#/sec] (mean)
Time per request:       62.694 [ms] (mean)
Time per request:       0.627 [ms] (mean, across all concurrent requests)
Transfer rate:          611.29 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    3   1.0      3       5
Processing:    12   40   6.0     38      49
Waiting:        7   40   6.0     38      49
Total:         12   43   6.0     42      53

Percentage of the requests served within a certain time (ms)
  50%     42
  66%     43
  75%     50
  80%     50
  90%     51
  95%     51
  98%     52
  99%     53
 100%     53 (longest request)

失败了94个请求.也就是成功了6个请求.

因为我们设置的是200ms处理一个请求.并且爆发请求为5个.那么说.这里成功6个请求就是合理的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
➜  ~ ab -n 100 -c 100  "http://limiting.test.com/two.html"
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking limiting.test.com (be patient).....done


Server Software:        nginx/1.18.0
Server Hostname:        limiting.test.com
Server Port:            80

Document Path:          /two.html
Document Length:        14 bytes

Concurrency Level:      100
Time taken for tests:   0.078 seconds
Complete requests:      100
Failed requests:        79
   (Connect: 0, Receive: 0, Length: 79, Exceptions: 0)
Non-2xx responses:      79
Total transferred:      37375 bytes
HTML transferred:       15857 bytes
Requests per second:    1285.23 [#/sec] (mean)
Time per request:       77.807 [ms] (mean)
Time per request:       0.778 [ms] (mean, across all concurrent requests)
Transfer rate:          469.10 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    3   1.1      3       5
Processing:    17   45   7.6     46      60
Waiting:       12   45   7.8     45      60
Total:         17   48   7.8     48      64

Percentage of the requests served within a certain time (ms)
  50%     48
  66%     49
  75%     50
  80%     51
  90%     62
  95%     62
  98%     63
  99%     64
 100%     64 (longest request)

这里失败了79个请求.成功了21个请求

我们设置的是每10ms处理一个请求.爆发请求为10.那么说在10ms内最多处理11个请求

那么在这里的值就是按照并发的时间来计算的.所以每次请求得到的成功响应还是有变化的.

总结

这里只是简单介绍了比较常用的限流配置命令.其实在nginx上面还有其他的一些限流配置.大家也可以去nginx官网了解更加详细的信息.

因为本人电脑没有装jemeter这样的测试工具.所以直接用ab来进行测试了.

还有上面的示例里面有一点可能需要解释一下.

因为我配置的第二个限流是使用server_name作为限流依据的.但是我又把它作用于了location two里面.其实按照正确的配置应该是直接配置在server里面就好了.放在two里面就是在瞎搞.

参考链接