直接用 set 存黑名单会导致内存无限增长且无法批量判断,而 bitmap 通过整数映射和 setbit 实现高效批量判别,但需注意确定性哈希、offset 范围限制及无 ttl 问题。

缓存穿透时,为什么不能直接用 SET 存黑名单
缓存穿透本质是大量请求查不存在的 key(比如恶意构造的用户 ID),导致请求打穿缓存直击 DB。加黑名单本意是拦截这些非法 key,但若直接用 SET blacklist:user:9999999 "1" 存,会遇到两个硬伤:一是没过期时间,内存无限涨;二是无法批量判断多个 key 是否在黑名单里——而实际场景中,一次接口常要校验十几个参数(如订单 ID、商品 ID、用户 ID 组合)。
用 Redis 的 Bitmap 实现高效批量判别
Bitmap 是空间利用率最高的方案:每个 bit 表示一个 key 是否存在。适合“只写不读详情、只关心真假”的黑名单场景。关键在于把原始 key 映射成整数 ID,再用 SETBIT 打标:
SETBIT user_blacklist 123456789 1
常见踩坑点:
- 映射函数必须确定性(如
abs(hash(key) % 2^32)),否则同个 key 多次计算出不同 offset,判别失效 - offset 超过 512MB(Redis 单个 String 最大值)会报错
ERR invalid argument,需按业务量预估并分片(如按用户 ID 取模分 100 个 key:user_blacklist_0~user_blacklist_99) - Bitmap 不支持 TTL,得靠外部定时任务清理老数据,或改用
RedisTimeSeries+ 标签字段模拟(更重,慎选)
用 HyperLogLog 做轻量级去重+存在性提示
如果黑名单只需“大概率识别重复非法请求”,且能接受极低误判率(HyperLogLog 比 Bitmap 更省内存(固定 12KB),还能天然支持去重计数:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
PFCOUNT user_blacklist_hll
但它不能精确判断某个 key 是否在集合中——所以真实做法是:先用 PFCADD 记录非法请求,再配合一个短 TTL 的普通 key(如 EXISTS user_blacklist_fallback:123456789)做兜底确认。这种混合模式在风控初筛阶段很实用,但别用于强一致场景。
黑名单更新必须绕过主从同步延迟
当运营后台拉黑某个用户后,若直接 SET user_blacklist_fallback:123456789 "1" EX 3600,从节点可能延迟几百毫秒才同步,这期间请求仍会穿透。解决方案只有两个:
- 写操作强制打到主节点(客户端加
READONLY false或显式指定MASTER节点) - 用
EVAL脚本封装写入+本地缓存刷新逻辑,确保原子性(例如:脚本内同时更新 Redis 和本地 LRU cache)
真正难的是冷热 key 切换——比如某个热点用户突然被拉黑,瞬间流量全打到 fallback key 上,容易触发 Redis 单 key 热点。这时候得提前在应用层做二级布隆过滤器(BloomFilter),把 99% 的无效请求挡在 Redis 之前。










