链接:https://juejin.cn/post/7475693874510315583
背景:访问时不时会被暴力刷量,爬虫和恶意攻击导致数据库,服务等瘫痪。。需求:在Nginx上实现一个动态拦截IP的方法,具体是当某个IP在1分钟内访问超过60次时,将其加入Redis并拦截,拦截时间默认1天。
技术选型:使用Nginx+Lua+Redis的方法。这种方案通过Lua脚本在Nginx处理请求时检查Redis中的黑名单,同时统计访问频率,超过阈值就封禁。这应该符合用户的需求。
需要结合Lua脚本和Redis的计数功能。安装OpenResty,配置Nginx的Lua模块,编写Lua脚本统计访问次数,使用Redis存储和过期键,以及设置拦截逻辑。连接池的使用,避免频繁连接Redis影响性能。
一、环境准备
-
安装OpenResty
OpenResty集成了Nginx和Lua模块,支持直接运行Lua脚本:# Ubuntu/Debian
sudo apt-get install openresty
# CentOS
yum install openresty -
安装Redis服务
sudo apt-get install redis-server # Debian系
sudo yum install redis # RedHat系
二、Nginx配置
-
主配置文件(nginx.conf)
在http
块中添加共享内存和Lua脚本路径:http {
lua_package_path "/usr/local/openresty/lualib/?.lua;;";
lua_shared_dict ip_limit 10m; # 共享内存区
server {
listen 80;
server_name _;
location / {
access_by_lua_file /usr/local/lua/ip_block.lua; # 核心拦截脚本
root /var/www/html;
}
}
}
三、Lua脚本实现动态拦截
脚本路径
创建Lua脚本:/usr/local/lua/ip_block.lua
-
脚本内容
local redis = require "resty.redis"
local red = redis:new()
-- Redis连接参数
local redis_host ="127.0.0.1"
local redis_port =6379
local redis_timeout =1000-- 毫秒
local redis_auth =nil-- 无密码留空
-- 拦截参数
local block_time =86400-- 封禁时间(1天)
local time_window =60-- 统计窗口(1分钟)
local max_requests =60-- 最大请求数
-- 获取客户端IP
localfunctionget_client_ip()
local headers = ngx.req.get_headers()
return headers["X-Real-IP"]or headers["x_forwarded_for"]or ngx.var.remote_addr
end
-- 连接Redis
localfunctionconnect_redis()
red:set_timeout(redis_timeout)
local ok, err = red:connect(redis_host, redis_port)
ifnot ok then
ngx.log(ngx.ERR,"Redis连接失败: ", err)
returnnil
end
if redis_auth then
local ok, err = red:auth(redis_auth)
ifnot ok then ngx.log(ngx.ERR,"Redis认证失败: ", err)end
end
return ok
end
-- 主逻辑
local client_ip =get_client_ip()
local counter_key ="limit:count:".. client_ip
local block_key ="limit:block:".. client_ip
-- 检查是否已封禁
local is_blocked, err = red:get(block_key)
iftonumber(is_blocked)==1then
ngx.exit(ngx.HTTP_FORBIDDEN)-- 直接返回403
end
-- 统计请求次数
connect_redis()
local current_count = red:incr(counter_key)
if current_count ==1then
red:expire(counter_key, time_window)-- 首次设置过期时间
end
-- 触发封禁条件
if current_count > max_requests then
red:setex(block_key, block_time,1)-- 封禁并设置1天过期
red:del(counter_key)-- 删除计数器
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- 释放Redis连接
red:set_keepalive(10000,100)
四、性能优化
Redis连接池
通过set_keepalive
复用连接,避免频繁建立TCP连接
共享内存缓存
使用lua_shared_dict
缓存高频访问IP,减少Redis查询压力
-
异步日志记录
封禁操作异步写入日志文件,避免阻塞请求处理:ngx.timer.at(0,function()
local log_msg = string.format("%s - IP %s blocked at %s",
ngx.var.host, client_ip, ngx.localtime())
local log_file = io.open("/var/log/nginx/blocked_ips.log","a")
log_file:write(log_msg,"\n")
log_file:close()
end)
五、验证与测试
-
手动触发封禁
# 模拟高频请求
ab -n 100-c10 http://your-domain.com/
# 检查Redis
redis-cli keys "limit:block:*" -
自动解封验证
等待24小时后检查封禁IP是否自动删除:redis-cli ttl "limit:block:1.2.3.4"# 返回剩余秒数
六、扩展方案
-
分布式封禁
在多台Nginx服务器间共享Redis黑名单,实现集群级拦截可视化监控
通过Grafana+Prometheus展示实时拦截数据:# 采集Redis指标
prometheus-redis-exporter --redis.address=localhost:6379动态调整阈值
通过Redis Hash存储不同路径的拦截规则:local rule_key="limit:rule:" .. ngx.var.uri
local custom_rule=red:hget(rule_key, "max_requests")
引用说明
核心拦截逻辑参考了Nginx+Lua+Redis的经典架构设计
Redis键过期机制确保自动解封
性能优化方案借鉴了OpenResty最佳实践
END
想要学习Linux系统的读者可以点击"阅读原文"按钮来了解书籍《Linux就该这么学》,同时也非常适合专业的运维人员阅读,成为辅助您工作的高价值工具书!

优网科技秉承"专业团队、品质服务" 的经营理念,诚信务实的服务了近万家客户,成为众多世界500强、集团和上市公司的长期合作伙伴!
优网科技成立于2001年,擅长网站建设、网站与各类业务系统深度整合,致力于提供完善的企业互联网解决方案。优网科技提供PC端网站建设(品牌展示型、官方门户型、营销商务型、电子商务型、信息门户型、DIY体验、720全景展厅及3D虚拟仿真)、移动端应用(手机站、APP开发)、微信定制开发(微信官网、微信商城、企业微信)、微信小程序定制开发等一系列互联网应用服务。