Nginx 流量控制/限流(原理介绍以及两个实验)

发布时间:2024年01月24日

1. Nginx 限流介绍

Nginx 限流是一种用于控制并发连接数或请求速率的机制,旨在保护服务器免受过多的请求影响,防止因请求过载而导致系统性能下降或崩溃。对 Nginx 限流的介绍可能涉及以下几个关键点:

  1. 什么是限流: 限流是一种流量控制手段,用于限制单位时间内可以通过系统的请求数或连接数。这有助于防止系统超负荷运行,保持系统的稳定性和可用性。
  2. 为何需要限流: 在高并发的网络环境中,突然涌入的大量请求可能会超出服务器的处理能力,导致性能下降甚至崩溃。通过限流,可以平滑处理请求,防止服务器不堪重负。
  3. Nginx限流配置: 在 Nginx 中,限流通常通过 ngx_http_limit_req_module 模块来实现。该模块允许你定义请求的速率限制,以及对超出限制的请求进行处理的方式,例如返回503错误或进入排队等待。
  4. 限流算法: Nginx 默认使用令牌桶算法进行限流。该算法基于令牌桶的概念,每个令牌代表一个请求。系统以一定的速率往令牌桶中放入令牌,请求时需要从令牌桶中取出令牌,当令牌不足时进行限流处理。
  5. 监控和调优: 监控 Nginx 的限流情况,并在需要时进行调优。监控可以通过 Nginx 的日志或专业监控工具实现,而调优可能涉及到调整限流参数、合理设置令牌桶容量等。

2. Nginx 如何限流?

漏桶算法是一种经典的流量控制算法,它以固定的速率接收请求,并以固定的速率处理请求,超过容量的请求将被丢弃或排队等待。
在 Nginx 中,使用 ngx_http_limit_req_module 模块来实现流量限制,其中漏桶算法是默认的限流算法。

以下是漏桶算法在 Nginx 中的一般工作原理:

  1. 令牌桶初始化: 在配置中,使用 limit_req_zone 指令来定义一个共享内存区域,作为令牌桶的存储。这个令牌桶会以指定的速率生成令牌。
    http {
        limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
        # 其他配置...
    }
    
  2. 令牌生成: 令牌桶以每秒1个的速率生成令牌。每个令牌表示一个允许通过的请求。
  3. 请求处理: 当有请求到达时,Nginx 会尝试从令牌桶中获取一个令牌。如果成功获取到令牌,请求将被处理;否则,请求将被拒绝或进入排队等待,具体取决于配置。
    server {
        location / {
            limit_req zone=mylimit burst=5;
            # 其他配置...
        }
    }
    
    在这个例子中,burst=5表示在令牌桶为空时,允许瞬时突发的最大请求数为5。
  4. 漏桶补充: 每秒固定生成的令牌会不断补充令牌桶,但令牌桶的容量是有限的。如果请求过多,超过了令牌桶的容量,多余的请求将被丢弃或进行相应的处理。

通过使用漏桶算法,Nginx能够有效地控制请求的流量,防止过多的请求影响系统的稳定性。这对于保护服务器免受突发大流量的冲击是非常有用的。

3. Nginx 限流配置详解

Nginx 限流需要使用 ngx_http_limit_req_module 模块实现
“流量限制”配置两个主要的指令,limit_req_zonelimit_req

  • limit_req_zone 指令设置创建共享内存区,用于存储限制请求的相关状态,但是它实际上并不限制请求速率的配置
    • 拓展:共享内存区域的作用
      • 共享内存区域是一种可以被多个进程或线程同时访问的内存区域。
      • 在 Nginx 中,limit_req_zone 指令定义的共享内存区域用于存储客户端请求的限制信息,例如请求的时间戳、请求计数器等。多个nginx worker进程可以同时访问这个共享内存区域,这样就可以实现对客户端请求的全局限制。
      • 共享内存区域的优点是:访问速度快、效率高,因为多个进程可以直接访问同一块内存, 避免了进程间通信的开销。
      • 但是需要注意的是,共享内存区域的大小是有限制的,如果定义的区域过小,可能会导致无法存储足够的请求信息,从而影响限制的效果。因此,在使用共享内存区域时,需要根据实际情况选择合适的大小。
  • 所以需要通过添加 limit_req 指令用于启用前面定义的共享内存区,从而实际限制请求速率
  • 应用在特定的 location 或者 server 块。

limit_req_zone 指令通常在HTTP块中定义,它需要以下三个参数:
limit_req_zone $variable zone=name:size rate=rate;
$variable为限制的键值,可以是IP地址、HTTP头部等;
name为共享内存区域的名字;
size为共享内存区域的大小;
rate为限制速率,即每秒钟处理的请求数量。

4. Nginx 限流实验1

4.1. 环境准备

IP作用备注
192.168.221.130nginx-proxy反向代理服务器
192.168.221.136nginx-backend后端服务器
192.168.221.138ab压测服务器对反向代理服务器对压测

4.2. 后端服务器配置

//192.168.221.136配置:
[root@localhost conf.d]# vim login.conf
server {
        listen 80;
        server_name www.Jltlogin.com;
        location /login {
                root    /usr/share/nginx/html;
                index   index.html index.html;
                }
}
?
//配置html文件
[root@localhost ~]# vim /usr/share/nginx/html/login/index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>登录页面</title>
  <style>
    body {
      background-color: #f2f2f2; 
      font-family: Arial;
    }
    .login-box {
      width: 300px;
      padding: 20px;
      margin: 100px auto;
      background-color: #fff;
      border-radius: 10px;  
    }
    h1 {
      text-align: center; 
    }
    input[type=text], input[type=password] {
      width: 100%;
      padding: 12px 20px;
      margin: 8px 0;
      box-sizing: border-box; 
      border: 2px solid #ccc;
      border-radius: 4px;
    }
    button[type=submit] {
      width: 100%;
      background-color: #4CAF50;
      color: white;
      padding: 14px 20px;
      margin: 8px 0;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    button[type=submit]:hover {
      background-color: #45a049;
    }
  </style>
</head>?
<body>
  <div class="login-box">
    <h1>登录</h1>
    <form>
      <label for="username">用户名:</label>
      <input type="text" id="username" name="username"><br><br>
      <label for="password">密码:</label>  
      <input type="password" id="password" name="password"><br><br>
      <button type="submit">登录</button>
    </form>
  </div>
</body>
</html>
?
[root@localhost login]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@localhost login]# systemctl restart nginx

本地host文件做好解析(windows中)
192.168.221.136 www.Jltlogin.com

浏览器访问测试,保证源站访问正常
http://www.Jltlogin.com/login

4.3. 反向代理服务器配置

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
?Key - 定义应用限制的请求特性。示例中的 Nginx 变量$binary_remote_addr,保存客户端IP地址的二进制形式。
?Zone - 定义用于存储每个IP地址状态以及被限制请求URL访问频率的内存区域。通过zone=mylimit 标识区域的名字(自定义),冒号后面是区域大小。16000个IP地址的状态信息,大约需要1MB。
?Rate - 连接请求。在该例子中,速率不能超过每秒1个请求。

#192.168.221.130
[root@localhost ~]$ cd /etc/nginx/conf.d/
[root@localhost conf.d]$ vim  limit.conf 
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
?
upstream myweb {
    server 192.168.221.136:80 weight=1 max_fails=1 fail_timeout=1;
}
?
   server {
        listen 80; 
        server_name www.Jltlogin-proxy.com;
?
        location /login {
           limit_req zone=mylimit;
           proxy_pass http://myweb;
           proxy_set_header Host $host:$server_port;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       }   
}
?
#不使用upstream
#limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
#   server {
#        listen 80; 
#        server_name www.Jltlogin-proxy.com;
#        location /login {
#           limit_req zone=mylimit;
#           proxy_pass http://www.Jltlogin.com;
#           proxy_set_header Host $host:$server_port;
#           proxy_set_header X-Real-IP $remote_addr;
#           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#       }   
#}
?
#尝试主配置文件http中修改添加,隐藏nginx版本
vim /etc/nginx/nginx.conf   #在http中添加
server_tokens off;

配置本地host文件(138中,压力测试)
192.168.221.136 www.Jltlogin.com?
?
配置本地host文件(windows中)
192.168.221.130 www.Jltlogin-proxy.com

?浏览器访问测试
http://www.Jltlogin-proxy.com/login/
一秒一次/一秒钟多次访问(一秒多次按Ctrl+F5)

4.4. 对反向代理服务器进行压力测试

//192.168.221.138安装压力测试工具
[root@localhost ~]# yum install httpd-tools
?
//添加hosts解析
[root@localhost ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
?
192.168.221.130 www.Jltlogin-proxy.com
?
[root@localhost ~]# curl  -I www.Jltlogin-proxy.com/login
HTTP/1.1 301 Moved Permanently
Server: nginx/1.24.0
Date: Sat, 29 Jul 2023 09:51:58 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: http://www.Jltlogin-proxy.com/login/
?
[root@localhost ~]# ab -n1000 -c2 http://www.Jltlogin-proxy.com/login
-n 请求数
-c 并发数
?
//130代理机器看错误日志:
[root@localhost nginx]# tailf /var/log/nginx/error.log 
2023/07/29 17:55:28 [error] 3996#3996: *1053 limiting requests, excess: 0.112 by zone "mylimit", client: 192.168.221.136, server: www.Jltlogin-proxy.com, request: "GET /login HTTP/1.0", host: "www.Jltlogin-proxy.com"

日志字段

  • limiting requests - 表明日志条目记录的是被“流量限制”请求
  • excess - 每毫秒超过对应“流量限制”配置的请求数量
  • zone - 定义实施“流量限制”的区域
  • client - 发起请求的客户端IP地址
  • server - 服务器IP地址或主机名
  • request - 客户端发起的实际HTTP请求
  • host - HTTP报头中host的值
//130查看访问日志出现503
[root@localhost nginx]# tail -f /var/log/nginx/access.log 
192.168.221.136 - - [29/Jul/2023:17:55:28 +0800] "GET /login HTTP/1.0" 503 197 "-" "ApacheBench/2.3" "-"

5. Nginx 限流实验2

#192.168.221.130反向代理服务器操作
[root@localhost conf.d]$ cp limit.conf{,.bak} 
[root@localhost conf.d]$ vim limit.conf
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
?
upstream myweb {
    server 192.168.221.136:80 weight=1 max_fails=1 fail_timeout=1;
}
?
   server {
        listen 80; 
        server_name www.Jltlogin-proxy.com;
?
        location /login {
           limit_req zone=mylimit burst=5;
           #limit_req zone=mylimit burst=5 nodelay; 
           proxy_pass http://myweb;
           proxy_set_header Host $host:$server_port;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       }   
}

burst=5 表示最大延迟请求数量不大于5。超出的请求返回503状态码。
limit_req zone=mylimit burst=5;
这意味着在任何给定的一秒钟内,只有 5 个请求会被允许通过。如果超过了这个限制,请求将会被暂时延迟,直到可以被处理为止。
?
limit_req zone=mylimit burst=5 nodelay;
该指令与上一个指令非常相似,但是添加了 nodelay 参数。
这个参数的作用是在达到限速阈值时不会延迟请求的处理。也就是说,如果超过了限速阈值,请求将不会被延迟,而是立即被处理。这可能会对服务器的性能产生【负面】影响,因为服务器需要处理更多的请求。但是,这样做可以提高用户体验,因为用户不需要等待请求被处理。

?//?192.168.221.138压力测试服务器操作:
[root@localhost ~]# cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
//添加host解析
192.168.221.130 www.Jltlogin-proxy.com
?
//开始做压力测试
[root@localhost ~]# ab -n1000 -c50 http://www.Jltlogin-proxy.com/login
?
?
//192.168.221.130 反向代理机器上面看日志
[root@localhost ~]# tail -f /var/log/nginx/access.log
192.168.221.130 - - [29/Jul/2023:21:08:23 +0800] "GET /login HTTP/1.0" 301 169 "-" "ApacheBench/2.3" "-"
192.168.221.130 - - [29/Jul/2023:21:08:23 +0800] "GET /login HTTP/1.0" 503 197 "-" "ApacheBench/2.3" "-"
#nodelay:不延迟转发请求。
………
 location /login {
           #limit_req zone=mylimit burst=5;
           limit_req zone=mylimit burst=5 nodelay;  
           #delay:延迟
           proxy_pass http://myweb;
…………
[root@localhost conf.d]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@localhost conf.d]# systemctl restart nginx
?
//138继续进行压力测试
[root@localhost ~]# ab -n1000 -c50 http://www.Jltlogin-proxy.com/login
//nodelay会使处理请求的速度变得要快很多,一下就处理完了
?
//192.168.221.130 反向代理机器上面看日志
[root@localhost ~]# tail -f /var/log/nginx/access.log
192.168.221.138 - - [29/Jul/2023:21:12:24 +0800] "GET /login HTTP/1.0" 301 169 "-" "ApacheBench/2.3" "-"
192.168.221.138 - - [29/Jul/2023:21:12:24 +0800] "GET /login HTTP/1.0" 503 197 "-" "ApacheBench/2.3" "-"

总结:

  • 如果不加nodelay只有burst的时候,只会延迟转发请求超过限制的请求出现503错误
    举例来说,burst=5,那么1秒内收到7个请求,会先处理前5个,第6和第7个请求会被推迟到下一秒处理,如果接下来很长时间依然超过5个请求,第6和第7个请求最后会收到503错误。
    ?
  • 如果nodelay和burst参数都有,则不会延迟转发请求,并且超出规定的请求次数会返回503
    可以理解为nodelay确保所有请求都得到及时处理,但不会改变burst的限制效果,超限的请求仍会是503。

6. 自定义返回错误代码

一般情况下,客户端超过配置的流量限制时,Nginx 响应状态码为 503(Service Temporarily Unavailable)。
我们可以使用 limit_req_status 指令来设置为其它状态码(例如下面的404状态码)

//130反向代理服务器操作:
[root@localhost conf.d]# vim  limit.conf 
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
?
upstream myweb {
    server 192.168.221.136:80 weight=1 max_fails=1 fail_timeout=1;
}
?
   server {
        listen 80; 
        server_name www.Jltlogin-proxy.com;
?
        location /login {
           limit_req zone=mylimit;
           limit_req_status 404;    #自定义限流的错误代码为404
           proxy_pass http://myweb;
           proxy_set_header Host $host:$server_port;
           proxy_set_header X-Real-IP $remote_addr;
           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       }   
}
?
[root@localhost conf.d]# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
[root@localhost conf.d]# systemctl restart nginx
?//138进行压力测试:
[root@nginx-yum ~]# ab -n10 -c5 http://www.Jltlogin-proxy.com/login
?//130反向代理服务器查看日志
[root@localhost conf.d]# tailf /var/log/nginx/access.log
192.168.221.138 - - [29/Jul/2023:21:17:33 +0800] "GET /login HTTP/1.0" 301 169 "-" "ApacheBench/2.3" "-"
192.168.221.138 - - [29/Jul/2023:21:17:33 +0800] "GET /login HTTP/1.0" 404 153 "-" "ApacheBench/2.3" "-"
//404 153:状态码、返回的数据长度,单位通常是字节(byte)
文章来源:https://blog.csdn.net/m0_62396418/article/details/135814335
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。