Nginx 限流是一种用于控制并发连接数或请求速率的机制,旨在保护服务器免受过多的请求影响,防止因请求过载而导致系统性能下降或崩溃。对 Nginx 限流的介绍可能涉及以下几个关键点:
- 什么是限流: 限流是一种流量控制手段,用于限制单位时间内可以通过系统的请求数或连接数。这有助于防止系统超负荷运行,保持系统的稳定性和可用性。
- 为何需要限流: 在高并发的网络环境中,突然涌入的大量请求可能会超出服务器的处理能力,导致性能下降甚至崩溃。通过限流,可以平滑处理请求,防止服务器不堪重负。
- Nginx限流配置: 在 Nginx 中,限流通常通过 ngx_http_limit_req_module 模块来实现。该模块允许你定义请求的速率限制,以及对超出限制的请求进行处理的方式,例如返回503错误或进入排队等待。
- 限流算法: Nginx 默认使用令牌桶算法进行限流。该算法基于令牌桶的概念,每个令牌代表一个请求。系统以一定的速率往令牌桶中放入令牌,请求时需要从令牌桶中取出令牌,当令牌不足时进行限流处理。
- 监控和调优: 监控 Nginx 的限流情况,并在需要时进行调优。监控可以通过 Nginx 的日志或专业监控工具实现,而调优可能涉及到调整限流参数、合理设置令牌桶容量等。
漏桶算法是一种经典的流量控制算法,它以固定的速率接收请求,并以固定的速率处理请求,超过容量的请求将被丢弃或排队等待。
在 Nginx 中,使用ngx_http_limit_req_module
模块来实现流量限制,其中漏桶算法是默认的限流算法。
以下是漏桶算法在 Nginx 中的一般工作原理:
limit_req_zone
指令来定义一个共享内存区域,作为令牌桶的存储。这个令牌桶会以指定的速率生成令牌。http {
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=1r/s;
# 其他配置...
}
server {
location / {
limit_req zone=mylimit burst=5;
# 其他配置...
}
}
在这个例子中,burst=5
表示在令牌桶为空时,允许瞬时突发的最大请求数为5。通过使用漏桶算法,Nginx能够有效地控制请求的流量,防止过多的请求影响系统的稳定性。这对于保护服务器免受突发大流量的冲击是非常有用的。
Nginx 限流需要使用 ngx_http_limit_req_module
模块实现
“流量限制”配置两个主要的指令,limit_req_zone
和 limit_req
:
limit_req_zone
指令设置创建共享内存区,用于存储限制请求的相关状态,但是它实际上并不限制请求速率的配置
limit_req_zone
指令定义的共享内存区域用于存储客户端请求的限制信息,例如请求的时间戳、请求计数器等。多个nginx worker进程可以同时访问这个共享内存区域,这样就可以实现对客户端请求的全局限制。limit_req
指令用于启用前面定义的共享内存区,从而实际限制请求速率
limit_req_zone
指令通常在HTTP块中定义,它需要以下三个参数:
limit_req_zone $variable zone=name:size rate=rate;
$variable
为限制的键值,可以是IP地址、HTTP头部等;
name
为共享内存区域的名字;
size
为共享内存区域的大小;
rate
为限制速率,即每秒钟处理的请求数量。
IP | 作用 | 备注 |
---|---|---|
192.168.221.130 | nginx-proxy | 反向代理服务器 |
192.168.221.136 | nginx-backend | 后端服务器 |
192.168.221.138 | ab压测服务器 | 对反向代理服务器对压测 |
//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
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)
//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" "-"
#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。
一般情况下,客户端超过配置的流量限制时,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)