实战篇 15. 用户签到 - BitMap 原理与功能学习文档

4 阅读5分钟

太棒了!我们终于来到了实战篇的最后一块“黑科技”拼图——用户签到

在绝大多数的互联网 App(如淘宝、网易云音乐、掘金等)中,都有每日签到领积分的功能。这个功能看似简单,但如果用户量级达到千万级别,如何用最极致的内存代价把这海量的签到状态存下来,是一个极其考验基本功的架构设计题。


📚 实战篇 15. 用户签到 - BitMap 原理与功能学习文档

一、 核心痛点:为什么不用传统方案?

假设我们有一个千万级日活的 App,需要记录用户每一天的签到状态。

❌ 传统方案 1:MySQL 关系表

  • 设计:一张 tb_sign 表,包含 id, user_id, sign_date
  • 痛点:如果 1000 万用户天天签到,一天产生 1000 万条记录,一个月就是 3 亿条!单表瞬间爆炸,查询极慢,极度浪费存储空间。

❌ 传统方案 2:Redis 普通的 Set 或 String

  • 痛点:如果把每个用户的签到日期用逗号拼接成一个很长的 String,或者放到 Set 集合里。虽然查询快了,但字符串和对象的额外开销很大,依然极其吃内存。

✅ 终极方案:Redis BitMap (位图)

签到状态本质上只有两种:签了(1)没签(0)

在计算机底层,表示 0 和 1 只需要 1 个 bit (比特位)

一个月最多 31 天,我们只需要 31 个 bit 就能记录一个用户整整一个月的签到记录!31 个 bit 连 4 个字节(Byte)都不到。

即使是一千万用户,一个月的签到数据也只需要大概 40 MB 的内存!这就是极其恐怖的空间压缩率。


二、 核心认知:什么是 BitMap?

🌟 面试高频防坑重点:

面试官:“BitMap 是 Redis 的一种新的数据结构吗?”

你的回答: “不是的!BitMap 的底层其实就是 String(字符串) 数据结构。Redis 里的 String 底层是由字节(Byte)组成的,而每个字节是由 8 个比特(bit)组成的。BitMap 就是 Redis 提供的一套命令,让我们能够直接操作 String 底层的这些 bit 位。所以 BitMap 并不是新的数据类型,它的本质还是 String。”


三、 核心命令实操指南

在实现签到功能前,必须先掌握 BitMap 的基本操作。我们可以把 BitMap 想象成一个极长的二进制数组(由 0 和 1 组成),数组的下标被称为 offset(偏移量)

1. 设置某一位的值:SETBIT

  • 语法: SETBIT key offset value (value 只能是 0 或 1)

  • 业务映射: 记录签到。

  • 演示: 假设用户 1001 在 3 月 1 日和 3 月 5 日签到了。

    (注意:日常习惯是从 1 开始,但数组下标 offset 从 0 开始,所以 3 月 1 日对应的 offset 是 0,3 月 5 日对应 4)

    Bash

    SETBIT sign:1001:202603 0 1  # 3月1日签到
    SETBIT sign:1001:202603 4 1  # 3月5日签到
    

2. 获取某一位的值:GETBIT

  • 语法: GETBIT key offset

  • 业务映射: 查询今天是否已经签到过。

  • 演示:

    Bash

    GETBIT sign:1001:202603 4  # 返回 1,说明 3月5日签了
    GETBIT sign:1001:202603 1  # 返回 0,说明 3月2日没签
    

3. 统计 1 的个数:BITCOUNT

  • 语法: BITCOUNT key [start end]

  • 业务映射: 统计用户这个月一共签到了多少天。

  • 演示:

    Bash

    BITCOUNT sign:1001:202603  # 返回 2,说明这个月总共签到了两天
    

4. 位域操作 (大招):BITFIELD

  • 语法: BITFIELD key [GET type offset]

  • 业务映射: 这是后续实现**“连续签到统计”**的核心命令。它可以一次性查出多个 bit 位的值,并将其转化为一个十进制数字返回。

  • 演示场景: 今天是 3 月 5 日(offset 为 4)。我想把从 1 号到 5 号这 5 天的签到记录一次性拿出来。

    Bash

    # 从 offset 为 0 的地方开始,取 5 个 bit 位,当做一个无符号整数 (u5) 返回
    BITFIELD sign:1001:202603 GET u5 0
    

    解析: 刚才我们在 1 号和 5 号签了,中间没签。底层的 bit 数组前 5 位是 10001。转化为十进制整数就是 17。


四、 签到功能 Key 与 Offset 设计规范

为了在代码中优雅地实现签到,我们需要制定一套严格的映射规则:

  1. Key 的设计: 按照用户和月份进行隔离。

    • 格式:sign:{userId}:{yyyyMM}
    • 示例:sign:1001:202603 (代表用户 1001 在 2026 年 3 月的签到记录)
  2. Offset 的设计: * 公式:offset = 今天是本月的第几天 - 1

    • 示例:3 月 15 日签到,offset 就是 14。

学习总结

BitMap 是后端开发中用来处理“二值状态(0或1)”的究极兵器。除了用户签到,它还经常被用来应对:

  • 统计日活用户(DAU):userId 当作 offset,今天登录了就把该位设为 1,最后 BITCOUNT 就能光速算出今天的日活。
  • 布隆过滤器(Bloom Filter): 防止缓存穿透的最强防线,底层也是利用了极其庞大的 BitMap 来标记数据是否存在。