BitMap的使用
Redis Bitmap 是什么?
Redis Bitmap 本质上并不是一种独立的数据结构,它底层仍然是 Redis 的 String 类型。
Redis 将一个字符串的每个二进制位(bit)当成一个数组元素来操作:
- 一个 bit 只有两种状态:0 或 1
- 1 Byte = 8 bit
- Redis 可以直接对指定偏移量(offset)的 bit 进行读写
例如:
偏移量:0 1 2 3 4 5 6 7
值 :1 0 1 0 0 1 1 0
实际上存储的只是一个字节:
10100110
Bitmap 的核心命令
设置某个位
SETBIT user:sign:202506 0 1
SETBIT user:sign:202506 1 1
SETBIT user:sign:202506 2 0
表示:
第0位 = 1
第1位 = 1
第2位 = 0
获取某个位
GETBIT user:sign:202506 1
返回:
1
统计1的数量
BITCOUNT user:sign:202506
返回:
15
表示有15个位为1。
位运算
支持:
BITOP AND
BITOP OR
BITOP XOR
BITOP NOT
例如:
BITOP AND result key1 key2
求两个 Bitmap 的交集。
Bitmap 原理
假设有 1 亿用户:
userId:
1
2
3
...
100000000
使用 Bitmap:
offset = userId
如果用户登录:
SETBIT login:20250601 100000000 1
表示:
用户100000000今天登录了
如果没登录:
bit = 0
存储空间计算
1亿用户:
100000000 bit
换算:
100000000 / 8
≈ 12.5 MB
只需要:
12.5MB
即可记录 1 亿用户是否登录。
为什么 Bitmap 节省空间?
Set方案
如果用 Set:
SADD login:20250601 user1 user2 user3
假设:
一个用户ID占8字节
1亿用户:
8 × 100000000
≈ 800MB
实际 Redis 对象开销更大,可能:
1~2GB
Bitmap方案
100000000 bit
≈ 12.5MB
节省几十到上百倍空间。
应用场景
场景1:用户签到
最经典场景。
记录签到
2025年6月:
1号签到
2号签到
5号签到
存储:
位置:
1 2 3 4 5
值:
1 1 0 0 1
代码:
SETBIT sign:10001:202506 1 1
SETBIT sign:10001:202506 2 1
SETBIT sign:10001:202506 5 1
查询是否签到
GETBIT sign:10001:202506 5
返回:
1
表示已签到。
统计签到天数
BITCOUNT sign:10001:202506
返回:
3
场景2:连续签到
例如:
111110
表示:
连续签到5天
可以通过:
BITFIELD
取出整个整数后进行位运算。
Java示例:
long signData = ...;
int count = 0;
while ((signData & 1) == 1) {
count++;
signData >>= 1;
}
统计连续签到天数。
场景3:用户活跃统计(DAU)
记录每日登录:
SETBIT dau:20250601 userId 1
统计当天活跃用户:
BITCOUNT dau:20250601
即可得到:
DAU
场景4:在线用户统计
例如:
用户ID -> bit位置
上线:
SETBIT online:user 10001 1
下线:
SETBIT online:user 10001 0
统计在线人数:
BITCOUNT online:user
场景5:用户标签系统
例如:
是否VIP
是否实名认证
是否绑定手机
分别建立 Bitmap:
vip:user
real:user
phone:user
用户:
10001
设置:
SETBIT vip:user 10001 1
SETBIT real:user 10001 1
查询:
VIP 且 实名用户
BITOP AND result vip:user real:user
BITCOUNT result
场景6:用户画像分析
统计:
今天登录
昨天登录
购买过商品
充值过
分别建立 Bitmap:
login:today
login:yesterday
pay:user
recharge:user
例如:
求留存用户
昨天登录且今天登录:
BITOP AND retain login:yesterday login:today
统计:
BITCOUNT retain
求新增用户
今天登录但昨天没登录:
BITOP XOR diff login:yesterday login:today
场景7:海量布尔状态存储
例如:
消息已读/未读
优惠券领取/未领取
任务完成/未完成
都适合 Bitmap。
例如:
SETBIT coupon:10001 couponId 1
表示:
用户10001领取了该优惠券
Bitmap 的局限性
1. 只能表示二值状态
只能:
0
1
不能直接存:
年龄
金额
昵称
2. 用户ID过大浪费空间
例如:
最大用户ID = 1000000000
实际用户数 = 1000
那么:
需要分配10亿个bit
≈125MB
大量空间被浪费。
因此 Bitmap 更适合:
连续ID
稠密ID
不适合:
UUID
雪花ID
随机ID
3. 删除困难
SETBIT key offset 0
只是置0。
Redis 不会自动缩容。
Bitmap 与 HyperLogLog 对比
| 特性 | Bitmap | HyperLogLog |
|---|---|---|
| 是否精确 | 精确 | 近似 |
| 去重统计 | 可以 | 专门用于去重 |
| 判断用户是否存在 | 可以 | 不可以 |
| 统计人数 | 精确 | 误差约0.81% |
| 存储空间 | 随用户数增长 | 固定约12KB |
| 适用场景 | 签到、活跃用户 | UV统计 |
例如:
DAU(要求精确)
使用:
Bitmap
网站UV(允许误差)
使用:
HyperLogLog
总结
Bitmap 的核心思想就是:
一个用户(或对象)对应一个 bit
利用 1 bit 记录一个布尔状态,以极低的内存成本实现:
- 用户签到
- DAU统计
- 在线用户统计
- 用户标签
- 用户画像分析
- 留存分析
- 消息已读状态
其最大优势是:
空间极小
查询极快
支持位运算
因此在互联网业务中,Bitmap 经常用于 海量用户状态统计,而 HyperLogLog 更偏向于 海量用户去重计数(UV)。
浙公网安备 33010602011771号