太棒了!我们终于来到了实战篇的最后一块“黑科技”拼图——用户签到。
在绝大多数的互联网 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 设计规范
为了在代码中优雅地实现签到,我们需要制定一套严格的映射规则:
-
Key 的设计: 按照用户和月份进行隔离。
- 格式:
sign:{userId}:{yyyyMM} - 示例:
sign:1001:202603(代表用户 1001 在 2026 年 3 月的签到记录)
- 格式:
-
Offset 的设计: * 公式:
offset = 今天是本月的第几天 - 1- 示例:3 月 15 日签到,offset 就是 14。
学习总结
BitMap 是后端开发中用来处理“二值状态(0或1)”的究极兵器。除了用户签到,它还经常被用来应对:
- 统计日活用户(DAU): 把
userId当作 offset,今天登录了就把该位设为 1,最后BITCOUNT就能光速算出今天的日活。 - 布隆过滤器(Bloom Filter): 防止缓存穿透的最强防线,底层也是利用了极其庞大的 BitMap 来标记数据是否存在。