在 Redis Lua 脚本中,SISMEMBER 命令用于判断集合成员是否存在——存在返回 1,否则返回 0。若 key 不存在也返回 0。许多新手容易犯一个错误:将返回值当作布尔值处理。请务必使用数字比较,如 == 1,而非 == true,否则逻辑判断永远失效。这是实现黑名单实时拦截的关键细节。

Redis Lua 脚本中如何编写 SISMEMBER 判断并立即返回结果
在 Lua 脚本中,直接使用 redis.call('SISMEMBER', 'blacklist', KEYS[1]) 即可,返回值是整数 1 或 0。常见错误是将返回值作为 Lua 布尔值处理,例如 if redis.call(...) == true,这会导致永远进入 false 分支,拦截逻辑失效。正确的做法是显式与数字比较:
local is_blocked = redis.call('SISMEMBER', 'blacklist', KEYS[1]) == 1
if is_blocked then
return 1
else
return 0
end
原子性是使用 Lua 脚本的核心原因。如果先 GET 再判断,会存在时间窗口:A 请求查到不在黑名单,但此时 B 将 A 加入黑名单,A 随后通过。Lua 脚本在 Redis 单线程内执行,SISMEMBER 与后续动作(如记录日志、限流计数)可封装在同一原子操作中,实现“查拦一体”。
在 API 网关层实现实时拦截,要求“查+拦”不可分割。如果仅需读取状态且允许短暂窗口,客户端逻辑也可行,但既然标题强调“实时拦截”,就必须保证原子性。
关键注意事项:
- EVAL 脚本必须将 key 名作为
KEYS[1]传入,不能硬编码,否则 Redis Cluster 无法路由。 - 参数个数需匹配:
EVAL script 1 blacklist_key user_id,第一个数字代表 key 的数量。 - 脚本长度超过 1KB 会影响主从复制带宽,简单拦截逻辑应保持在百行以内。
如何让拦截结果直接用于 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_v1、blacklist: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_calls和lua_scriptcachemisses指标,高频 miss 说明脚本未复用 SHA1。
真正难的不是写对那行 SISMEMBER,而是确保 key 命名可运维、过期机制可收敛、错误路径不静默失败——这些地方一旦遗漏,线上就只能依赖日志倒查了。
