Nginx 通过 Lua + Redis 实现动态封禁 IP 的方案,是一种高效、灵活且可扩展的解决方案。以下是具体实现步骤和关键要点:
一、方案优势- 动态管理:黑名单可实时更新,无需重启 Nginx。
- 共享性:多台 Nginx 服务器可通过 Redis 共享同一黑名单。
- 低性能开销:利用共享内存和 Redis 的高效查询,减少对服务器性能的影响。
二、实现步骤1. 环境准备2. Nginx 配置在 nginx.conf 的 http 块中添加共享内存和 Lua 脚本路径:
http { lua_shared_dict ip_blacklist 1m; # 1MB 共享内存缓存黑名单 server { listen 80; location / { access_by_lua_file /path/to/lua/ip_blacklist.lua; # 指定 Lua 脚本 proxy_pass http://backend;
} }}3. Lua 脚本实现创建 /path/to/lua/ip_blacklist.lua,实现以下逻辑:
- 检查共享内存缓存:优先从本地缓存查询。
- 回源 Redis:若缓存未命中,则从 Redis 获取最新黑名单并更新缓存。
- 封禁逻辑:若 IP 在黑名单中,返回 403 状态码。
local blacklist = ngx.shared.ip_blacklistlocal redis = require "resty.redis"local red = redis:new()-- 连接 Redisred:connect("127.0.0.1", 6379)-- 获取客户端 IPlocal client_ip = ngx.var.remote_addr-- 检查共享内存缓存local is_blocked = blacklist:get(client_ip)if not is_blocked then -- 回源 Redis 查询 is_blocked = red:sismember("ip_blacklist", client_ip) if is_blocked == 1 then -- 更新本地缓存(有效期 60 秒) blacklist:set(client_ip, true, 60) endend-- 封禁逻辑if is_blocked == 1 then ngx.log(ngx.WARN, "Blocked IP: ", client_ip) ngx.exit(ngx.HTTP_FORBIDDEN)end-- 关闭 Redis 连接red:set_keepalive(10000, 100)4. Redis 数据初始化在 Redis 中创建黑名单集合,并添加 IP:
redis-cli> SADD ip_blacklist "192.168.1.100" "10.0.0.5"5. 验证效果- 被封禁 IP 访问:返回 403 Forbidden。
- 正常 IP 访问:正常代理到后端服务。
三、关键优化点缓存策略
共享内存(lua_shared_dict)减少 Redis 查询频率。
设置合理的缓存过期时间(如 60 秒),平衡实时性和性能。
Redis 高可用
使用 Redis 集群或哨兵模式避免单点故障。
自动化管理
通过脚本或监控系统(如 Prometheus + Grafana)动态更新 Redis 黑名单。
日志记录
在 Lua 脚本中记录封禁事件,便于审计(如 ngx.log)。
四、架构图示流程说明:- 客户端请求到达 Nginx。
- Lua 脚本检查共享内存 → 未命中则查询 Redis。
- 若 IP 在黑名单中,返回 403;否则放行并代理到后端。
五、总结此方案通过 Nginx + Lua + Redis 实现了动态、高效、可共享的 IP 黑名单机制,适用于以下场景:
- 防御恶意爬虫或 DDoS 攻击。
- 多服务器协同封禁。
- 需要频繁更新黑名单的业务(如风控系统)。
注意事项:
- 确保 Redis 和 Nginx 的网络连通性。
- 监控 Lua 脚本的执行性能(如 ngx.timer 避免阻塞)。
- 定期清理共享内存中的过期数据。