服务公告

服务公告 > Linux命令 > Openresty通过Lua+Redis实现动态封禁IP

Openresty通过Lua+Redis实现动态封禁IP

发布时间:2026-01-08 00:03

蓝易云:OpenResty 通过 Lua + Redis 实现动态封禁IP(可上线方案)🙂

要把“封禁”做成可运营能力,关键不是“能不能拦”,而是三点:
1)拦截点放在 access_by_lua(最省资源);2)用 Redis 做实时策略源;3)用 ngx.shared.DICT 做本机 L1 缓存,避免每个请求都打 Redis(否则你是在给 Redis 做压测)。


一、架构与数据模型(建议直接照抄落地)✅

封禁键设计(推荐)

  • Key:ban:ip:<IP>
  • Value:1
  • TTL:按封禁时长设置(例如 10 分钟、1 小时、24 小时),到期自动解封

这种设计的最大价值:天然支持“临时封禁”,不需要额外清理任务。


二、OpenResty 配置:接入拦截器(Nginx/OpenResty)🔧

lua_shared_dict ip_ban_cache 50m;

server {
    listen 80;

    location / {
        access_by_lua_file /etc/openresty/lua/ip_ban.lua;
        proxy_pass http://backend_upstream;
    }
}

逐段解释(对应交付验收点)

  • lua_shared_dict ip_ban_cache 50m;:声明 共享内存字典,用于本机缓存“是否封禁”的结果;50m 只是示例,按你业务 QPS 调整。
  • access_by_lua_file ...:在 访问控制阶段 执行 Lua;一旦判定封禁,立刻返回 403,不再走后端,成本最低。
  • proxy_pass ...:仅当通过封禁校验后,才进入后端链路。

三、Lua 代码:Redis 查询 + 本地缓存 + 连接池(核心)🚀

依赖:OpenResty 自带的 lua-resty-redis。它支持超时精细化设置 set_timeouts(相较老式 set_timeout 更细粒度)。(opm.openresty.org)

-- /etc/openresty/lua/ip_ban.lua
local redis = require "resty.redis"
local cache = ngx.shared.ip_ban_cache

local ip = ngx.var.remote_addr

-- 1) L1 缓存:命中就直接决策(避免每次请求打 Redis)
local cached = cache:get(ip)
if cached ~= nil then
    if cached == 1 then
        return ngx.exit(ngx.HTTP_FORBIDDEN)
    end
    return
end

-- 2) 连接 Redis(建议短超时 + 连接池)
local red = redis:new()
red:set_timeouts(50, 50, 50) -- connect/send/read ms

local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
    -- Redis 异常时:建议“放行但短缓存”,避免误伤全站(fail-open)
    cache:set(ip, 0, 1)
    return
end

-- 3) 查询封禁键:存在即封禁
local key = "ban:ip:" .. ip
local val, err = red:get(key)

if val and val ~= ngx.null then
    cache:set(ip, 1, 60) -- 封禁命中缓存 60 秒,降低 Redis 压力
    red:set_keepalive(10000, 200)
    return ngx.exit(ngx.HTTP_FORBIDDEN)
else
    cache:set(ip, 0, 5)  -- 未封禁也缓存 5 秒,减少穿透
end

red:set_keepalive(10000, 200)
return

逐段解释(把坑提前填上)

  • cache:get(ip):本机 L1 缓存 命中后不再访问 Redis;这一步决定了你能否扛高并发。
  • red:set_timeouts(50,50,50):把 Redis 调用的超时压到毫秒级,避免请求被“拖死”;set_timeouts 提供 connect/send/read 三段超时控制。(opm.openresty.org)
  • Redis 连接失败分支:我这里用 fail-open(放行),并加 1 秒短缓存,防止 Redis 故障时每个请求都去撞墙。现实世界里,宁可少拦一点,也别把自己拦到宕机。
  • cache:set(ip, 1, 60):封禁命中缓存 60 秒;即使 Redis TTL 还有很久,也不必每次请求都查 Redis。
  • red:set_keepalive(10000, 200):把连接放回连接池复用;否则高并发下会产生大量短连接,资源消耗很难看。lua-resty-redis 的 keepalive/连接池是标准做法。(GitHub)

四、运营侧:如何“动态下发封禁/解封”🧰

1)封禁某个 IP(带时长)

redis-cli SET ban:ip:1.2.3.4 1 EX 3600
  • SET ban:ip:1.2.3.4 1:写入封禁键。
  • EX 3600:封禁 3600 秒(1 小时),到期自动失效=自动解封。

2)立即解封

redis-cli DEL ban:ip:1.2.3.4
  • DEL:删除封禁键,下一次请求就会放行(最多受 L1 缓存影响几秒)。

五、原理解释表(你对外讲解也能用)📌

模块 角色 关键点 建议值
access_by_lua 拦截入口 越早拦越省后端资源 固定放这里
Redis 策略中心 动态下发、TTL 自动解封 Key=ban:ip:*
shared_dict L1 缓存 抗并发、降 Redis QPS 封禁 60s / 放行 5s
连接池 性能稳定器 复用连接,避免 TIME_WAIT 风暴 keepalive 10s, pool 200

六、工作流程图(Mermaid,vditor 可直接渲染)✅

 
是且封禁
是且放行
未命中
存在
 
不存在
 
请求进入 OpenResty
shared_dict 命中?
返回 403
进入后端
查询 Redis ban:ip:IP
写缓存=封禁 60s
写缓存=放行 5s

七、两条“上线级”提醒(很关键)🙂

1)如果你在 CDN / 反代后面,别直接信 remote_addr:需要配好真实源 IP(否则你封的是代理节点,等于给自己挖坑)。
2)封禁策略别只靠 IP:IP 封禁是“止血措施”,建议同时配合限频、挑战、行为评分等策略做“组合拳”,成本更低、误伤更少。

如果你愿意把封禁升级成“平台能力”,下一步我会建议你加:封禁原因(reason)、封禁来源(rule_id)、封禁计数与审计日志(便于回溯),以及批量封禁/解封接口——这样才像一个能持续运营的防护系统。

已经是第一篇啦!

下一篇: 服务器路由命令有哪些常用技巧?