单个Bitmap在Redis 6.0中撑不住亿级用户ID,因其底层依赖String类型512MB上限(对应最大偏移量2^32−1),但实际需分片:用户ID若为雪花ID/UUID,直接取模易冲突或负溢出(如Java中Integer.MIN_VALUE取abs后为负),导致SETBIT静默失败;且低比特取模引发热点倾斜,须高位哈希(如右移24位)打散写入;分片数宜固定为256或512,单分片偏移量≤2^28(约32MB),避免BITCOUNT阻塞;key命名需带shard标识便于运维;合并前须EXISTS校验分片完整性,缺失则占位防漏计。

为什么单个Bitmap在Redis 6.0里撑不住亿级用户ID
因为Redis String类型最大只支持512MB,对应最大偏移量是2^32 - 1(约42.9亿),看似够用。但实际中,如果你的用户ID是雪花ID、UUID或字符串,直接取模/哈希后映射到0~N范围时,冲突概率或负溢出会导致位错位——比如Math.abs(uid.hashCode())在Java里遇到Integer.MIN_VALUE会返回负数,SETBIT就静默失败,查不到也报不了错。
分片必须按高位哈希,不能简单取模
按用户ID低几位取模(如uid % 256)会导致热点倾斜:新注册用户ID连续,同一分片被打爆;而高位哈希(如uid >> 24或Murmur3.hash32(uid).unsignedRightShift(24))能打散写入压力。分片数建议固定为256或512,避免扩容重分片。
分片后的key命名要带分片标识,例如:
sign:20261019:shard:127
而不是sign:20261019:127——冒号分隔更利于Redis key扫描和运维识别。
- 每个分片Bitmap独立控制大小,单个
key偏移量不超过2^28(约268M bit ≈ 32MB),避免BITCOUNT阻塞主线程(实测offset=2^30-1时耗时≈80ms) - 分片数太多(如4096)会显著增加
BITOP OR合并开销,且Redis集群模式下跨slot命令受限 - 不要依赖
TTL自动过期——异步删除可能延迟数小时,应每日定时DEL过期key,或用EXPIREAT精确控制
并发写入必须靠Redis原子性,别自己加锁
Redis 6.0的SETBIT、GETBIT、BITCOUNT全部是服务端原子操作,无需客户端加锁。但要注意:
- 别用Lua脚本封装多个
SETBIT再做逻辑判断——若脚本执行超时被中断,部分位已写入,状态不一致 - 高吞吐场景下,禁用
pipeline批量发一堆SETBIT——万一网络断连,整批丢失,得靠幂等重试兜底 - 如果业务要求“首次签到才记”,不能只靠
GETBIT+SETBIT两步——中间可能被其他请求覆盖,得用EVAL带CAS逻辑,例如:if redis.call("GETBIT", KEYS[1], ARGV[1]) == 0 then redis.call("SETBIT", KEYS[1], ARGV[1], 1) return 1 else return 0 end
BITOP合并前先确认分片完整性
统计近7天UV时,要用BITOP OR把7个日期的256个分片分别合并,再对结果做BITCOUNT。但容易忽略的是:某一分片key可能因写入失败、磁盘满或运维误删而缺失。
安全做法是:
- 合并前用
EXISTS批量检查所有目标key是否存在,缺失则用SETBIT空写一位(如SETBIT sign:20261018:shard:127 0 0)占位,避免BITOP跳过该key导致漏计 -
BITOP结果key必须设EXPIRE,否则残留大量临时key拖慢Redis - 别在主从架构下对从节点执行
BITOP——从节点只读,命令会失败且无明确错误提示
最常被跳过的细节是:分片键名拼接时没统一编码(比如用户ID含中文或特殊字符),导致哈希结果不一致,同一用户落到不同分片——这种错位无法通过统计发现,只能靠抽样校验ID映射链路。


















