Redis位图本身不导致缓存击穿,但key设计错误或缺乏原子性会引发击穿;打满问题主因是offset越界和key粒度过粗;需ID映射压缩、Lua原子操作、时区统一、预聚合统计、集群下强制同slot或客户端计算,并配合定时清理过期key。

直接说结论:Redis位图本身不会导致缓存击穿,但用错 key 设计或漏掉原子性保障,会把“防击穿”变成“主动制造击穿”。打满问题则几乎全出在 offset 映射和 key 粒度上。
SETBIT 的 offset 越界会导致 Redis 内存暴涨
很多人把 user_id 直接当 offset 用,比如 SETBIT sign:202510 。这在用户 ID 是 1000 万时,offset 就是 10000000,Redis 会自动把字符串扩展到 ⌈10000000/8⌉ = 1.25MB。10 万个用户签到,这个 key 就占 125GB——不是夸张,是真实发生过的线上事故。
正确做法是做 ID 映射压缩:
- 用分桶(sharding)把用户分到 1000 个 slot,
slot = user_id % 1000,再用user_id / 1000当 offset - 或者更稳妥:用全局连续自增 ID(如雪花 ID 的序列号部分)作为 offset,确保 offset 始终 ≤ 31(单月天数)或 ≤ 366(年)
- 永远不要信任原始
user_id的数值范围,加一层校验:if (offset > 366) throw new IllegalArgumentException()
GETBIT + SETBIT 非原子操作会引发缓存击穿
前端查“今天是否已签到”,后端先 GETBIT,再决定要不要 SETBIT。高并发下多个请求同时读到 0,全部执行 SETBIT,不仅浪费,还可能触发重复积分发放逻辑。
必须用 Lua 脚本保证原子性:
local result = redis.call('GETBIT', KEYS[1], ARGV[1])
if result == 1 then
return 1
else
redis.call('SETBIT', KEYS[1], ARGV[1], 1)
return 0
end
关键点:
-
KEYS[1]是带年月的 key,如sign:202610;ARGV[1]是当天 day-of-month - 1(即 0~30) - 返回值明确区分“已签”(1)和“新签”(0),业务层据此跳过积分逻辑
- 别用
SETBIT ... NX——Redis 没这个选项,SETBIT总是覆盖
BITCOUNT 统计本月签到天数时要注意时区与跨日边界
BITCOUNT sign:202610 看起来没问题,但若服务部署在 UTC+0,而业务要求按北京时间(UTC+8)统计,10 月 19 日 00:00–07:59 的签到会被记到 10 月 18 日的 key 里,导致 BITCOUNT 漏算。
解决方案只有两个:
- 所有时间计算统一用
LocalDateTime.now(ZoneId.of("Asia/Shanghai")),key 后缀严格按北京时间生成 - 避免用
BITCOUNT做实时排行榜——它扫描整个字符串,大 key 下延迟抖动明显;改用预聚合:每天凌晨用BITCOUNT算一次,结果存进 Hash 或 Sorted Set - 补签场景下,不能只看当前 key,要检查上月 key 的最后几天(如 10 月 1 日补 9 月 30 日),否则
BITCOUNT会少算
BITOP 多 key 运算在集群模式下会失败
想算“连续 7 天都签到的用户”,自然想到用 BITOP AND 把 7 个 key 做与运算。但在 Redis Cluster 中,这些 key 如果不在同一个 slot,命令直接报 CROSSSLOT Keys in request don't hash to the same slot。
绕过方式很实际:
- 强制 key 落在同一 slot:在 key 后缀加
{shard}标签,如sign:202610{1001}、sign:202609{1001} - 改用客户端聚合:用
GET拉取 7 个 key 的原始字符串,在内存里做位运算(Java 可用BitSet.and()) - 根本不用 BITOP:把“连续签到”逻辑下沉到签到写入时维护一个
consecutive_days:user:123计数器,靠 Lua 更新,查的时候直接GET
最常被忽略的一点:位图 key 的过期时间不能设太长。每月一个 key,按理该设 90 天过期,但如果你用 SETEX 设置了 90 天,而某用户只在 2026 年 1 月签过一次,这个 key 会一直占内存到 4 月——没有自动清理机制。必须配合定时任务,每月 1 号删掉前两个月的 key,哪怕它们还没过期。











