如何利用Redis Lua脚本实现黑名单的实时拦截_封装原子性的SISMEMBER判断

作者:袖梨 2026-07-10
Redis SISMEMBER命令用于判断指定成员是否为集合的成员,返回1(是)或0(否),key不存在时也返回0;在Lua脚本中须用数字比较(如==1),不可当布尔值使用,以保障原子性。

Redis Lua脚本里怎么写 SISMEMBER 判断并立即返回结果

直接用 redis.call('SISMEMBER', 'blacklist', KEYS[1]) 就行,但必须注意:返回值是整数 10,不是布尔值。Lua 脚本里别写 if redis.call(...) == true —— 这会永远走 false 分支。

常见错误是把返回值当 Lua 布尔用,导致拦截逻辑失效。正确写法是显式比对数字:

local is_blocked = redis.call('SISMEMBER', 'blacklist', KEYS[1]) == 1if is_blocked then  return 1else  return 0end

为什么非得用 EVAL 而不能先 GET 再判断

因为网络往返 + 客户端条件判断会破坏原子性:A 请求查到不在黑名单,B 在此时把 A 的 ID 加进去,A 接着就通过了。Lua 脚本在 Redis 单线程内执行,SISMEMBER 和后续动作(比如记录日志、限流计数)能包进同一原子操作里。

使用场景典型如 API 网关层的实时拦截,要求“查+拦”不可分割。如果只是读取状态且允许短暂窗口期,那用客户端逻辑也行,但不符合“实时拦截”需求。

要点:

  • EVAL 脚本必须把 key 名作为 KEYS[1] 传入,不能硬编码,否则无法被 Redis Cluster 路由
  • 参数个数要匹配:EVAL script 1 blacklist_key user_id,第一个数字是 key 的数量
  • 脚本长度超过 1KB 会影响 Redis 主从复制带宽,简单拦截逻辑保持在百行内

如何让拦截结果直接用于 Nginx/OpenResty 的 access_by_lua*

OpenResty 的 access_by_lua* 阶段需要明确返回状态码或中断请求,不能只靠 Lua 脚本返回 1/0。得配合 redis.call + ngx.exit 使用:

local redis = require "resty.redis"local red = redis:new()red:set_timeout(100)red:connect("127.0.0.1", 6379)local ok, err = red:eval(  "return redis.call('SISMEMBER', KEYS[1], ARGV[1]) == 1 and 1 or 0",  1, "blacklist", ngx.var.arg_uid)if ok == 1 then  ngx.exit(403)end

注意这里用了 eval 方法而非 evalsha,首次调用会自动缓存 SHA1;但若脚本变更,需清空 Redis 的脚本缓存(SCRIPT FLUSH),否则旧逻辑仍在运行。

容易踩的坑:

  • 没设 set_timeout,超时默认是 60s,会卡住整个请求生命周期
  • 没检查 connect 返回值,连接失败时 eval 报错,Nginx 日志里是 nil 调用错误
  • ARGV[1] 是字符串,如果传的是数字型 user_id,Lua 里比较仍安全(Redis 自动转),但建议统一用字符串避免歧义

黑名单 key 设计和过期策略怎么配才不爆内存

别用一个大集合存所有黑名单用户,应该按业务维度拆分,比如 blacklist:api_v1blacklist:payment。这样既能隔离影响面,又方便单独清理。

如果黑名单项有自然过期时间(如封禁 1 小时),不要依赖 EXPIRE 整个 key —— 集合没法给单个成员设 TTL。正确做法是用 ZSET 存时间戳,再用 ZCOUNT + ZREMRANGEBYSCORE 清理过期项;但若只是静态封禁(永久或手动解封),SET + SISMEMBER 最轻量。

性能提示:

  • SISMEMBER 平均时间复杂度 O(1),但最坏是 O(N),当集合元素极多(千万级)且哈希冲突严重时可能变慢,这时应考虑分片(如对 user_id 取模分 16 个 key)
  • Redis 6+ 启用 io-threads 对 EVAL 性能无提升,因为 Lua 脚本强制单线程执行
  • 监控 lua_callslua_scriptcachemisses 指标,高频 miss 说明脚本没复用 SHA1

真正难的不是写对那一行 SISMEMBER,而是确保 key 命名可运维、过期机制可收敛、错误路径不静默失败——这些地方一漏,线上就只能靠日志倒查。

相关文章

精彩推荐