返回首页

Redis高级数据结构3.1: Bitmap 位图

适用版本:Redis 7.2.x

一、什么是 Bitmap

Bitmap(位图)并不是 Redis 中一种独立的底层数据类型,它本质上是 基于 String 的按位操作能力。也就是说,Redis 会把一个字符串看作连续的二进制位数组,我们可以把每一位当成一个布尔状态位来使用:

  • 0 表示未发生
  • 1 表示已发生

这种设计非常适合表示“某个对象是否具备某种状态”。例如:

  • 用户今天是否签到
  • 某个用户是否在线
  • 某一天某个用户是否活跃
  • 某个实验桶中的标记位是否被设置

如果用普通字符串、哈希或集合来保存这些状态,在数据量大时会非常占空间;而 Bitmap 只需要 1 bit 表示一个状态,因此空间效率极高。


二、Bitmap 的核心原理

1. 基于 String 存储位信息

Redis 的字符串底层是字节数组,而一个字节等于 8 位,所以一个字符串天然就可以被当成位数组。

例如,一个长度为 1 字节的字符串可以表示 8 个状态位:

字节内容:00000000
位偏移:  01234567

当我们执行:

SETBIT sign:20260609 0 1
SETBIT sign:20260609 3 1

可以理解为把第 0 位、第 3 位设置为 1。

2. 用 offset 表示下标

Bitmap 中最关键的概念是 位偏移量 offset。每个 offset 对应一个 bit 位。

比如:

  • 用户 ID = 10086,可以把它映射到 offset = 10086
  • 一个月签到场景中,第 1 天可以映射到 offset = 0,第 2 天映射到 offset = 1

这样就能用一个 key 存储成千上万甚至上亿个状态位。

3. 空间优势非常明显

假设要记录 1 亿个用户是否登录:

  • 用 Bitmap:1 亿 bit ≈ 12 MB
  • 用 Set 保存登录用户 ID:通常远大于 12 MB

这也是 Bitmap 在用户画像、活跃统计、签到系统中常见的原因。


三、Bitmap 适用场景

Bitmap 不是万能结构,它最适合解决“海量对象 + 二值状态”的问题。

1. 用户签到

例如记录 2026 年 6 月用户 1001 的签到情况:

  • 第 1 天签到:offset 0 = 1
  • 第 2 天未签到:offset 1 = 0
  • 第 3 天签到:offset 2 = 1

可以进一步统计:

  • 本月签到总天数
  • 连续签到天数
  • 今天是否签到

2. 日活/月活统计

可以为某一天建立一个 key:

active:20260609

把用户 ID 映射到偏移量,活跃过就置为 1。然后通过 BITCOUNT 统计这一天有多少用户活跃。

3. 用户标签或状态开关

例如:

  • 是否完成新手引导
  • 是否开启消息通知
  • 是否参加活动

如果状态只有是/否两种,Bitmap 就非常合适。

4. 批量位运算分析

Redis 支持按位与、按位或、按位异或等操作,因此可以做:

  • 连续两天都活跃的用户数
  • 任意一天活跃的用户数
  • 某两类标签的交集统计

四、常用命令与示例

下面结合具体例子来理解 Bitmap 的常用命令。

1. SETBIT:设置某一位

SETBIT sign:user:1001:202606 0 1
SETBIT sign:user:1001:202606 1 1
SETBIT sign:user:1001:202606 2 0
SETBIT sign:user:1001:202606 3 1

含义:

  • 第 1 天签到
  • 第 2 天签到
  • 第 3 天没签到
  • 第 4 天签到

返回值是该位设置前的旧值。

2. GETBIT:获取某一位

GETBIT sign:user:1001:202606 0
GETBIT sign:user:1001:202606 2

适合判断某一天是否已签到。

3. BITCOUNT:统计值为 1 的位数

BITCOUNT sign:user:1001:202606

如果结果为 3,表示这个月已经签到 3 天。

在 DAU 场景里,这个值就可以表示当日活跃人数。

4. BITOP:做多个 Bitmap 的位运算

统计两天都活跃的用户

BITOP AND active:and:20260608_20260609 active:20260608 active:20260609
BITCOUNT active:and:20260608_20260609
  • BITOP AND 取交集
  • BITCOUNT 统计交集人数

统计两天任意一天活跃的用户

BITOP OR active:or:20260608_20260609 active:20260608 active:20260609
BITCOUNT active:or:20260608_20260609

5. BITPOS:查找第一个 0 或 1 出现的位置

BITPOS sign:user:1001:202606 1
BITPOS sign:user:1001:202606 0

这个命令有时可以辅助分析某些连续状态,但在签到系统里,更多还是结合应用层逻辑处理。

6. BITFIELD:按位段读写整数

BITFIELD user:flags SET u4 0 10 GET u4 0

这个命令可以把某一段 bit 当成整数读写,适合紧凑存储多个小整数标记。不过它比 SETBIT/GETBIT 更难理解,初学阶段先掌握基础命令即可。


五、一个典型案例:签到功能设计

1. Key 设计

假设要记录用户每月签到信息,可以设计成:

sign:{userId}:{yyyyMM}

例如:

sign:1001:202606

2. 写入签到

6 月 9 日签到,对应本月第 9 天,可以映射到 offset = 8:

SETBIT sign:1001:202606 8 1

3. 判断今天是否签到

GETBIT sign:1001:202606 8

4. 统计本月签到总天数

BITCOUNT sign:1001:202606

5. 连续签到如何做

连续签到通常不是 Redis 单条命令直接完成,而是:

  1. 取出相关 bit 信息
  2. 在业务代码中从当天开始向前遍历
  3. 遇到第一个 0 就停止

有些系统也会通过 Lua 脚本提高原子性和效率。


六、Bitmap 的优点

1. 极致节省空间

最大优点就是存储密度高。1 个状态只占 1 bit。

2. 统计效率高

Redis 对位操作做了优化,像 BITCOUNTBITOP 这类命令在海量数据下依然很高效。

3. 适合布尔型状态建模

当业务问题能抽象成“是否”“有没有”“发生/未发生”时,Bitmap 往往是非常自然的选择。


七、使用 Bitmap 时的注意事项

1. Bitmap 适合二值状态,不适合复杂属性

如果你想保存的不只是“是否签到”,而是“签到时间、签到地点、签到来源”等复杂信息,Bitmap 就不够用了,需要配合 Hash、Stream、ZSet 等结构。

2. offset 过大可能造成空间浪费

Bitmap 的空间分配与 最大 offset 有关,而不是与设置了多少位有关。

例如你只设置:

SETBIT demo 100000000 1

那么 Redis 也需要为中间所有位扩容。因此:

  • 用户 ID 稀疏、不连续时要谨慎
  • 可以考虑建立用户 ID 到连续编号的映射

3. 大范围 BITOP 会带来额外开销

BITOP 会生成结果 key,如果参与运算的 Bitmap 很大,CPU 和内存都会有压力。

实践中要注意:

  • 控制单次运算规模
  • 对临时结果及时删除
  • 避免在业务高峰期做超大范围离线统计

4. String 最大容量限制

Bitmap 基于 String,因此会受 String 最大容量限制。虽然上限已经很大,但在超大规模场景下仍应评估 key 的尺寸。

5. 不要把 Bitmap 当成“万能统计结构”

如果场景是去重计数,更适合 HyperLogLog;如果是地理位置检索,更适合 GEO;如果是消息队列,则更适合 Stream。数据结构选对了,系统才会简单。


八、实战设计建议

1. 优先明确“位偏移”的映射规则

Bitmap 的核心不是命令,而是映射关系。例如:

  • 用户签到:offset = day - 1
  • 用户活跃:offset = userId 映射值
  • 小时活跃:offset = hour

映射规则一旦混乱,后续统计会很麻烦。

2. Key 命名中体现统计维度

建议把时间维度和对象维度放入 key 中,例如:

sign:user:{uid}:{yyyyMM}
active:day:{yyyyMMdd}
feature:flag:{name}

这样便于维护和清理。

3. 配合过期时间管理数据生命周期

例如日活、签到明细可能只保留近 3 个月,可以给 key 设置 TTL,减少长期堆积。

EXPIRE active:day:20260609 7776000

九、小结

Bitmap 是 Redis 中一个非常实用但又常被低估的能力。它不是独立数据类型,而是 String 的位级操作模型,最适合处理 海量数据下的二值状态统计

可以重点记住以下几点:

  • Bitmap 本质是对 String 做按位读写
  • 一个状态只占 1 bit,空间利用率极高
  • 适合签到、活跃统计、状态标记等场景
  • 常用命令包括 SETBITGETBITBITCOUNTBITOP
  • 需要特别注意 offset 设计和大范围位运算的成本

如果把 Redis 比作一个工具箱,那么 Bitmap 就像一把非常省空间的“状态记录器”。在“是否”问题上,它往往比 Set、Hash 更轻、更快、更划算。


📝 版权声明:本文为原创技术博客,转载请注明出处。

如文章中存在错误或不准确之处,欢迎在评论区指正,感谢您的阅读与支持!

上一篇

Redis核心2.4: 持久化与内存配置实践手册

下一篇

Redis高级数据结构3.2:HyperLogLog 基数统计