bitmaps 比 set 节省约 99.75% 内存,1000 万用户签到状态仅需约 1.25mb(理论)且 rle 压缩可再降 60%~90%,关键在于 offset 必须为连续数字、避免空洞,并确保 redis ≥4.0 且 key 未被污染。

Bitmaps 比 Set 节省多少内存?
用 SET 存 1000 万个用户某天的签到状态,每个 user_id 至少占 32 字节(Redis String 编码下 key + value 开销),加上哈希表扩容冗余,实际轻松突破 500MB。而 BITFIELD 或 SETBIT 存同样数据,仅需约 1.25MB —— 因为本质是 1000 万 bit ≈ 1.25MB 连续位数组。
关键不是“能存”,而是“Redis 对 Bitmaps 做了底层压缩”:当一段位全是 0 或有规律重复时(比如大量未签到用户),会自动转成 RLE(行程长度编码)格式存储,实测稀疏签到场景下内存再降 60%~90%。
- 必须用
user_id作为偏移量(offset),不能用字符串 ID;否则得先查映射表,失去原子性和空间优势 - 偏移量建议从 0 开始连续编号(如用自增 ID 或分段 ID 映射),避免出现超大空洞(比如 offset=9999999 但前面全是 0)—— 空洞越大,RLE 压缩效果越差,甚至退化为 raw 存储
- 单个 Bitmap 最好控制在 5000 万 bit 以内(≈6MB),过大时
BITCOUNT等操作可能阻塞主线程
怎么安全地把现有 Set 迁移到 Bitmaps?
直接删 SET 再重写 Bitmap 是高危操作:迁移期间新签到会丢失,且无法原子回滚。稳妥做法是双写 + 渐进式切换:
- 上线前先开一个新 key,比如
sign:20240501:bitmap,所有新签到同时执行SETBIT sign:20240501:bitmap {uid} 1和老逻辑SADD sign:20240501:set {uid} - 用后台任务分批读
SMEMBERS sign:20240501:set,每批 1000 个,调BITFIELD sign:20240501:bitmap SET u1 0 1批量写入(注意 offset 要映射为数字) - 确认无误后,把读逻辑切到
BITFIELD ... GET u1 {uid},并停写老SET;最后删掉旧 key
⚠️ 容易踩的坑:SMEMBERS 在大数据量下会阻塞,务必用 SSCAN 游标分页;BITFIELD 的 u1 表示无符号 1 位整数,别错写成 i1(有符号)或 u8(占 1 字节,完全失去压缩意义)
为什么开了压缩还是内存没降?
Redis 的位图压缩(RLE)只对 STRING 类型的 bitmap key 生效,且依赖具体数据分布。如果发现 MEMORY USAGE 和理论 bit 数接近,大概率是以下原因:
Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。
- 用了非数字 offset:比如用字符串
"u1001"当 offset,Redis 会拒绝写入,或静默失败(取决于客户端),实际没存进去 - 写入太稀疏:offset 分布极度离散(如只存了 1、1000000、2000000),中间全是空洞,RLE 无法压缩,退化为 raw 格式
- key 被其他命令污染:比如不小心对 bitmap key 执行了
APPEND或SETRANGE,导致类型混杂,压缩失效 - Redis 版本低于 4.0:RLE 压缩是 4.0+ 引入的,低版本只能靠
ziplist编码勉强省点,效果有限
验证方法:用 DEBUG OBJECT sign:20240501:bitmap 查看 encoding,如果是 raw 或 embstr 就说明没压上;理想状态是 quicklist(内部用 RLE 编码的 listpack)
BITFIELD 多操作原子性够用吗?
BITFIELD 本身是原子命令,一次调用里多个 GET/SET 不会穿插其他客户端操作。但要注意它不等价于事务:
- 如果某个子操作越界(比如 offset 超出当前 bitmap 长度),Redis 默认补 0 并继续执行后续操作,不会报错中断 —— 这容易掩盖逻辑错误
- 没有回滚机制:前 3 个
SET成功,第 4 个失败,前面已生效,没法撤回 - 返回值是数组,顺序严格对应命令顺序,但部分客户端(如某些 Python SDK)可能把多返回值自动解包成单值,导致误判
真实场景建议:签到只需单次 SETBIT,简单可靠;BITFIELD 更适合批量统计(如本周连续签到天数),用 INCRBY 配合掩码提取位字段 —— 但务必提前规划好每个 bit 的语义,改起来成本很高
Bitmaps 真正的复杂点不在语法,而在 offset 设计和生命周期管理。一个没对齐的 ID 映射,或者一段被遗忘的老迁移脚本,就能让压缩形同虚设。










