在现代互联网应用中,流量波动是常态。无论是突发的用户访问高峰,还是恶意攻击,都可能导致服务器资源耗尽,进而影响服务的可用性。为了应对这种情况,限流(Rate Limiting)成为了一种常见的保护措施。Nginx 作为一款高性能的 Web 服务器和反向代理服务器,提供了强大的限流功能,可以帮助我们有效控制流量,保护后端服务。
1. 为什么需要限流?
在讨论如何配置限流之前,我们先来了解一下为什么需要限流。
- 防止服务器过载:当流量突然激增时,服务器可能会因为处理不过来而崩溃。限流可以限制单位时间内的请求数量,避免服务器过载。
- 防止恶意攻击:某些恶意用户或攻击者可能会通过大量的请求来消耗服务器资源,导致正常用户无法访问。限流可以有效抵御这种攻击。
- 保障服务质量:通过限流,可以确保核心业务在流量高峰期仍然能够正常运行,避免因为流量激增而导致服务质量下降。
2. Nginx 限流的基本概念
Nginx 提供了两种主要的限流方式:
- 基于请求速率的限流(Rate Limiting):限制单位时间内的请求数量。
- 基于连接数的限流(Connection Limiting):限制同时处理的连接数。
3. 基于请求速率的限流配置
3.1 limit_req_zone
指令
limit_req_zone
是 Nginx 中用于定义请求速率限流策略的指令。它定义了一个共享内存区域,用于存储请求速率的统计信息。
limit_req_zone $binary_remote_addr zone=rate_limit_zone:10m rate=10r/s;
$binary_remote_addr
:表示使用客户端的 IP 地址作为限流的键。你也可以使用其他变量,如$http_x_forwarded_for
或自定义变量。zone=rate_limit_zone:10m
:定义了一个名为rate_limit_zone
的共享内存区域,大小为 10MB。这个区域用于存储限流的状态信息。rate=10r/s
:表示每秒最多允许 10 个请求。你也可以使用r/m
表示每分钟最多允许的请求数。
3.2 limit_req
指令
limit_req
指令用于在具体的 location
或 server
块中应用请求速率限流策略。
server {
location /api/ {
limit_req zone=rate_limit_zone burst=5 nodelay;
proxy_pass http://backend_server;
}
}
zone=rate_limit_zone
:指定使用之前定义的rate_limit_zone
限流区域。burst=5
:表示允许突发请求的数量。如果请求速率超过了限流速率,Nginx 会将这些请求放入一个队列中,burst
参数定义了这个队列的大小。nodelay
:表示不延迟处理突发请求。如果没有这个参数,Nginx 会按照限流速率逐个处理请求,可能会导致响应时间增加。
3.3 请求速率限流的工作原理
当 Nginx 接收到请求时,它会根据 limit_req_zone
中定义的键(如客户端 IP 地址)来判断请求是否超过了限流速率。如果超过了限流速率,Nginx 会根据 burst
参数决定是否将请求放入队列中。如果队列已满,Nginx 会返回 503 错误(Service Temporarily Unavailable)。
4. 基于连接数的限流配置
4.1 limit_conn_zone
指令
limit_conn_zone
是 Nginx 中用于定义连接数限流策略的指令。它定义了一个共享内存区域,用于存储连接数的统计信息。
limit_conn_zone $binary_remote_addr zone=conn_limit_zone:10m;
$binary_remote_addr
:表示使用客户端的 IP 地址作为限流的键。你也可以使用其他变量,如$http_x_forwarded_for
或自定义变量。zone=conn_limit_zone:10m
:定义了一个名为conn_limit_zone
的共享内存区域,大小为 10MB。这个区域用于存储连接数的状态信息。
4.2 limit_conn
指令
limit_conn
指令用于在具体的 location
或 server
块中应用连接数限流策略。
server {
location /api/ {
limit_conn conn_limit_zone 10;
proxy_pass http://backend_server;
}
}
limit_conn conn_limit_zone 10
:表示每个客户端 IP 地址最多允许同时建立 10 个连接。
4.3 连接数限流的工作原理
当 Nginx 接收到连接请求时,它会根据 limit_conn_zone
中定义的键(如客户端 IP 地址)来判断当前连接数是否超过了限制。如果超过了限制,Nginx 会拒绝新的连接请求,并返回 503 错误(Service Temporarily Unavailable)。
5. 配置示例
下面是一个完整的 Nginx 限流配置示例,结合了基于请求速率的限流和基于连接数的限流。
http {
# 定义请求速率限流区域
limit_req_zone $binary_remote_addr zone=rate_limit_zone:10m rate=10r/s;
# 定义连接数限流区域
limit_conn_zone $binary_remote_addr zone=conn_limit_zone:10m;
server {
listen 80;
server_name example.com;
location /api/ {
# 应用请求速率限流策略
limit_req zone=rate_limit_zone burst=20 nodelay;
# 应用连接数限流策略
limit_conn conn_limit_zone 10;
proxy_pass http://backend_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
解释:
limit_req_zone $binary_remote_addr zone=rate_limit_zone:10m rate=10r/s;
:定义了一个名为rate_limit_zone
的请求速率限流区域,每秒最多允许 10 个请求。limit_conn_zone $binary_remote_addr zone=conn_limit_zone:10m;
:定义了一个名为conn_limit_zone
的连接数限流区域。limit_req zone=rate_limit_zone burst=20 nodelay;
:在/api/
路径下应用请求速率限流策略,允许突发请求最多 20 个,并且不延迟处理这些请求。limit_conn conn_limit_zone 10;
:在/api/
路径下应用连接数限流策略,每个客户端 IP 地址最多允许同时建立 10 个连接。
6. 限流的常见问题与优化
6.1 如何处理不同用户的限流需求?
如果你的应用需要对不同用户进行不同的限流策略,可以使用自定义变量来替代 $binary_remote_addr
。例如,如果你使用 JWT 进行身份验证,可以在 Nginx 中解析 JWT 并提取用户 ID,然后使用用户 ID 作为限流的键。
limit_req_zone $jwt_user_id zone=user_rate_limit:10m rate=5r/s;
limit_conn_zone $jwt_user_id zone=user_conn_limit:10m;
6.2 如何处理代理后的客户端 IP?
如果你的 Nginx 服务器位于代理服务器之后(如 CDN 或负载均衡器),你可能需要使用 $http_x_forwarded_for
或 $proxy_add_x_forwarded_for
来获取真实的客户端 IP 地址。
limit_req_zone $http_x_forwarded_for zone=proxy_rate_limit:10m rate=10r/s;
limit_conn_zone $http_x_forwarded_for zone=proxy_conn_limit:10m;
6.3 如何处理限流误伤?
有时候,限流可能会误伤正常用户。为了避免这种情况,你可以:
- 增加
burst
值:允许更多的突发请求。 - 使用
nodelay
参数:避免请求被延迟处理,减少用户体验的影响。 - 动态调整限流策略:根据服务器的负载情况动态调整限流速率。
7. 总结
Nginx 的限流功能是保护服务器免受流量洪峰和连接风暴冲击的重要工具。通过合理配置 limit_req_zone
和 limit_req
指令,你可以有效地控制请求速率;通过配置 limit_conn_zone
和 limit_conn
指令,你可以限制同时处理的连接数。
在实际应用中,限流策略需要根据具体的业务需求进行调整。你可以通过增加 burst
值、使用自定义变量、处理代理后的客户端 IP 等方式来优化限流配置,确保限流策略既能保护服务器,又不会误伤正常用户。