当用户访问网站时,在使用CDN或SLB的情况下,访问链路通常是这样的:
用户浏览器 → CDN节点/SLB节点 → 源服务器在这种情况下,源服务器接收到的请求来源IP实际上是CDN或SLB节点的IP,而不是用户的真实IP。如果我们直接基于这个IP进行限流,就会出现以下问题:
- 限制了CDN/SLB节点而非真实用户
- 无法对真实的恶意用户进行有效拦截
- 限流策略失效
二、普通限流配置
在没有CDN/SLB的简单场景下,我们可以直接基于$binary_remote_addr进行限流:
## 以用户IP地址作为Key,每个IP地址最多有50个并发连接 limit_conn_zone $binary_remote_addr zone=TotalConnLimitZone:10m; limit_conn TotalConnLimitZone 50; limit_conn_log_level notice; ## 以用户IP地址作为Key,每个IP地址每秒处理10个请求 limit_req_zone $binary_remote_addr zone=ConnLimitZone:10m rate=10r/s; limit_req_log_level notice; ## 具体服务器配置 server { listen 80; location ~ \.php$ { ## 最多5个排队,由于每秒处理10个请求+5个排队, ## 你一秒最多发送15个请求过来,再多就直接返回503错误给你了 limit_req zone=ConnLimitZone burst=5 nodelay; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; } }这种方式适用于用户直接访问源服务器的场景,但对于经过CDN/SLB代理的情况则不再适用。
三、CDN/SLB模式下的限流解决方案
1. 理解X-Forwarded-For头部
当CDN/SLB代理服务器将用户请求转发到后端服务器时,会在HTTP头中加入记录:
X-Forwarded-For: 用户IP, 代理服务器1-IP, 代理服务器2-IP, ...经过多层代理后,用户的真实IP在第一个位置,后面会跟一串中间代理服务器的IP地址。
2. 获取真实IP的配置
http { ## 获取原始用户的真实IP地址 map $http_x_forwarded_for $clientRealIp { default $remote_addr; ~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr; } ## 设置IP白名单,对内部的IP不限制 map $clientRealIp $limit { default $clientRealIp; xx.xx.xx.xx ""; } ## 以真实IP为单位,限制请求数,并返回429状态; limit_req_status 429; limit_req_zone $limit zone=ConnLimitZone:20m rate=5r/s; limit_req_log_level notice; ## 以真实IP为单位,限制该IP的并发连接数,并返回429状态; limit_conn_status 429; limit_conn_zone $limit zone=TotalConnLimitZone:20m; limit_conn TotalConnLimitZone 100; limit_conn_log_level notice; ## 以访问域名为单位,限制总并发链接数; limit_conn_zone $server_name zone=SumConnLimitZone:20m; } ## 具体Server配置 server { listen 80; location ~ \.php$ { ## 限制总并发连接数 limit_conn SumConnLimitZone 10000; ## 最多5个排队,由于每秒处理10个请求+5个排队, ## 你一秒最多发送15个请求过来,再多就直接返回429错误给你了 limit_req zone=ConnLimitZone burst=5 nodelay; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; } }3. 配置说明
- map指令:用于从
X-Forwarded-For头部提取用户真实IP - 白名单机制:对特定IP(如内部服务器)不进行限流
- 三层限流策略:
- 基于真实IP的请求频率限制
- 基于真实IP的并发连接数限制
- 基于域名的总并发连接数限制
四、验证方法
1. 白名单测试
对于白名单内的服务器进行压测,应该全部通过。
2. 非白名单测试
对于不在白名单内的IP,最多允许通过10+5个请求,其余部分应该返回429错误。
3. 压测工具
CentOS一般自带siege压测工具:
yum -yinstallsiege# 使用方法siege -c3-r10-b https://xxxx.xx.com/api/xxxx# -c 3 表示3个用户# -r 10 表示访问10次# 以上表示:3个用户,每个用户访问10次请求,共计30次五、调试技巧:Echo模块
为了验证配置是否正确,可以使用Nginx的echo模块进行调试:
1. 集成Echo模块
cd/usr/local/src# 下载echo模块并解压wgethttps://github.com/openresty/echo-nginx-module/archive/v0.61.tar.gztarzxvf v0.61.tar.gz# 下载nginx并解压wgethttp://nginx.org/download/nginx-1.12.2.tar.gztar-xzvf nginx-1.12.2.tar.gzcdnginx-1.12.2/# 查看当前nginx的编译参数/usr/local/nginx/sbin/nginx -V# 在旧的编译参数基础上新增【--add-module=/echo模块的解压路径】参数,开始编译./configure --prefix=/usr/local/nginx/nginx --add-module=/usr/local/src/echo-nginx-module-0.61# make编译make-j2# 平滑升级nginxmv/usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.oldcp-f objs/nginx /usr/local/nginx/sbin/makeupgrade2. Echo使用示例
server { listen 80; server_name yourdomain.com; ## 以下新增规则: ## 当用户访问 /ip 的时候,我们输出 $clientRealIp 和 $limit 变量, ## 看看这个变量值是不是真的 用户源IP 地址 location /ip { echo $clientRealIp; echo $limit; } }通过访问http://xxx.xxx.cn/ip可以验证获取到的IP是否为用户真实IP。
六、最佳实践建议
- 合理设置限流参数:根据业务实际情况调整请求频率和并发连接数限制
- 维护白名单:将内部服务器、合作伙伴服务器等加入白名单
- 监控告警:建立完善的监控体系,及时发现异常流量
- 渐进式部署:先在测试环境验证,再逐步推广到生产环境
- 定期评估:根据业务发展定期评估和调整限流策略
结语
通过合理配置Nginx的限流模块,即使在复杂的CDN/SLB架构下,我们也能有效识别用户真实IP并实施精准的访问控制。这不仅能够保护服务器免受恶意攻击,还能确保正常用户的访问体验。在实际应用中,需要根据具体的业务场景和安全要求来调整相关参数,以达到最佳的防护效果。