在网关层用OpenResty+Redis实现IP黑名单,通过access阶段Lua校验、共享字典缓存、连接池复用及带过期时间的SET/分区KEY结构,兼顾高性能与可控性。

直接在网关层做 IP 黑名单,核心是让每次请求在进入后端前就完成判断,不走应用逻辑、不增加业务负担。OpenResty + Redis 的组合,正是为这种高并发、低延迟场景设计的——它把黑名单校验压到 Nginx 请求生命周期的 access 阶段,用 Lua 脚本调用 Redis 实时查,再配合共享字典缓存热点数据,性能足够扛住万级 QPS。
Redis 存储结构要简洁可扩展
别用复杂嵌套或大 hash,推荐两种实用方式:
-
全局黑名单集合(简单统一):用 Redis 的
SET类型存所有被封 IP,键名如ip_blacklist。每次查只需SMISMEMBER ip_blacklist 192.168.1.100,O(1) 时间复杂度。 -
带 zone 分区的 key(灵活隔离):适合多业务共用一套网关,比如按服务划分:
api_v1_blacklist:192.168.1.100或admin_zone:10.0.5.22。这样添加/删除时可精准操作某区域,不影响其他服务。
注意:所有 key 必须设置过期时间(EXPIRE 或 SETEX),避免误封永久生效;封禁时长建议按风险等级设定,如暴力探测封 5 分钟,高频爬虫封 2 小时。
OpenResty 配置关键三步
不是加个 Lua 脚本就完事,必须保证连接复用、本地缓存、失败降级三者到位:
-
定义共享字典缓存:在
http块中声明,例如lua_shared_dict ip_blacklist_cache 10m;。这相当于 Nginx 进程内的一块内存缓存,避免每秒上千次直连 Redis。 -
连接池管理 Redis:Lua 中不要每次 new 一个 redis 实例,要用
resty.redis:connect+set_keepalive复用连接。超时设为 1000ms,空闲连接保持 60 秒以上,减少 handshake 开销。 -
访问阶段嵌入校验逻辑:在
location或server块中写access_by_lua_file /path/to/check_blacklist.lua;,脚本里先查共享字典,命中则直接拒绝;未命中再查 Redis,并把结果回填字典(带 TTL),同时设置“最近一次更新时间”防止频繁拉取。
黑名单的增删要闭环可控
不能只靠运维手动连 Redis 执行命令,得有配套机制:
-
提供简易管理接口:用 OpenResty 写一个内部
/admin/blacklist接口,支持 POST 添加(带 zone、IP、过期秒数)、DELETE 删除、GET 查询列表。权限用简单 token 或内网 IP 白名单控制即可。 -
支持 X-Forwarded-For 解析:如果前端有负载均衡或 CDN,真实 IP 在请求头里。Lua 脚本需解析该头(注意防伪造),例如:
local ip = ngx.var.http_x_forwarded_for or ngx.var.remote_addr,并优先取第一个非私有地址。 -
日志与告警联动:在 Lua 拒绝逻辑里加
ngx.log(ngx.WARN, "BLACKLIST_BLOCKED: ", ip),配合 filebeat 收集到 ELK;当单 IP 1 分钟内被拦截超 10 次,触发企业微信/钉钉告警,提示可能遭遇扫描或攻击。
性能优化和容错要点
线上跑得稳,关键在边界情况处理:
- Redis 故障时不能拖垮整个网关:Lua 脚本里对
redis:connect和redis:smembers做判空+超时处理,失败则跳过黑名单检查(默认放行),或 fallback 到共享字典里的旧数据(加个 last_update 时间戳比对)。 - 避免“缓存击穿”:多个请求同时发现共享字典没命中、又同时去查 Redis,造成瞬时压力。可在 Lua 中用
ngx.shared.DICT:add的原子性做轻量锁,首个请求去查,其余等待 100ms 后重试读字典。 - 内存别爆:共享字典大小按预估 IP 数 × 32 字节估算,10 万 IP 约占 3MB;Redis 端定期用
KEYS ip_blacklist*+EXPIRE清理残留 key,或直接用带过期的SET替代SETNX。



















