Redis 位图 Bitmap
位图本质上是普通字符串(String),只是把每个字节拆成 8 个 bit 来用,一个 bit 就能表示"是/否"两个状态。用它做统计类功能极其省内存:1 个用户只占 1 bit,适合日活统计、连续签到、在线状态等海量布尔型数据场景。
写入与读取单个位
SETBIT key offset value 把第 offset 位设为 0/1 并返回该位原来的值;GETBIT 读取某一位。offset 从 0 开始,最大到 2^32-1(单个 key 最长 512MB),没写过的位默认是 0:
SETBIT sign:202406 0 1
# 输出:(integer) 0 # 6 月 1 日签到
SETBIT sign:202406 1 1
# 输出:(integer) 0 # 6 月 2 日签到
SETBIT sign:202406 5 1
# 输出:(integer) 0 # 6 月 6 日签到
GETBIT sign:202406 1
# 输出:(integer) 1 # 第 2 天已签到
GETBIT sign:202406 3
# 输出:(integer) 0 # 第 4 天未签到
统计与查找
BITCOUNT 统计 key 中为 1 的位数(可加 [start end] 字节范围);BITPOS 找第一个为 0 或 1 的位:
BITCOUNT sign:202406
# 输出:(integer) 3 # 6 月共签到 3 天
BITPOS sign:202406 1
# 输出:(integer) 0 # 从第 0 位起第一个 1
位运算:BITOP
BITOP AND/OR/XOR/NOT 把多个位图按位计算后写入目标 key,返回结果的字节长度,可用来求交集、并集(如连续活跃、共同在线):
SETBIT online:0701 5 1
# 输出:(integer) 0
SETBIT online:0702 5 1
# 输出:(integer) 0
SETBIT online:0702 8 1
# 输出:(integer) 0
BITOP AND both online:0701 online:0702
# 输出:(integer) 2 # 结果为 2 字节(取较长的输入长度)
BITCOUNT both
# 输出:(integer) 1 # 两天都在线的只有第 5 位
BITOP OR any online:0701 online:0702
# 输出:(integer) 2
BITCOUNT any
# 输出:(integer) 2 # 两天合计在线 2 人
典型场景:日活统计
每天一个 key,用户 ID 作为 offset,当天有访问就 SETBIT 1,第二天新建 key 互不干扰:
SETBIT uv:20240601 10001 1
# 输出:(integer) 0
SETBIT uv:20240601 20002 1
# 输出:(integer) 0
BITCOUNT uv:20240601
# 输出:(integer) 2 # 当天活跃 2 人
BITPOS uv:20240601 1
# 输出:(integer) 10001 # 活跃用户中 ID 最小的是 10001
内存上:1000 万用户一天只需 10000000 / 8 / 1024 ≈ 1.2MB,1 亿用户约 12MB,比逐个存用户 ID 的集合省几个数量级。跨天求"连续活跃"直接用 BITOP AND 即可。
注意点
- 位图适合 ID 连续、分布密集的场景;若用户 ID 是 10 位以上的随机大数,稀疏 offset 会产生大量空洞浪费内存,可改用 Set 或先对 ID 重映射/分桶。
- BITCOUNT、BITPOS 的 start/end 参数是"字节"下标而非位下标,容易踩坑。
- 底层是 String,没有独立类型,别把普通字符串与位图混用同一 key 互相覆盖。
小结
Bitmap = 用 bit 数组记录布尔状态,核心命令 SETBIT/GETBIT/BITCOUNT/BITPOS/BITOP。日活、签到、在线状态这类"每人每天一个位"的海量统计是它的经典应用,内存占用约为传统方案的几十分之一。