位图
Redis 位图 Bitmap:SETBIT/BITCOUNT/BITFIELD 位级统计、签到与活跃用户场景、内存估算与稀疏位图陷阱。
1. 位图概述
位图(Bitmap)不是独立数据类型,而是基于 String 类型的位操作,每个 String 键最多存储 个位(约 512MB)。
类比:一排开关,每个开关只有开/关两种状态,位图用 1 个 bit 记一个开关——「用户 42 今天登录过」就是拨亮第 42 号开关。1 亿用户的「是否活跃」只需要约 12MB,这是它相对 Set 的核心优势(Set 存 1 亿个整数成员约需数 GB)。
适用判断:被记录的对象能用小整数编号表示、且答案是「是/否」二值时优先位图;编号稀疏巨大(如雪花 ID 直接过 offset)则先映射成自增序号。
2. 基本操作
SETBIT key offset value -- 设置指定位(offset 从 0 开始)
GETBIT key offset -- 获取指定位
BITCOUNT key [start end] -- 统计1的个数
BITPOS key bit [start end] -- 查找第一个0/1的位置
BITOP op destkey key [key ...] -- 位运算
# 完整会话(redis-cli 实际输出)
127.0.0.1:6379> SETBIT user:login:2026 42 1
(integer) 0 # 返回该位【之前】的值(0=原来没登录过)
127.0.0.1:6379> SETBIT user:login:2026 42 1
(integer) 1 # 再次设置,返回旧值 1
127.0.0.1:6379> GETBIT user:login:2026 42
(integer) 1
127.0.0.1:6379> SETBIT user:login:2026 7 1
127.0.0.1:6379> BITCOUNT user:login:2026
(integer) 2 # 两个用户登录过
127.0.0.1:6379> BITPOS user:login:2026 1
(integer) 7 # 最早登录的是 7 号用户
注意 SETBIT 的返回值是「旧值」,可以用来判断「首次登录」(返回 0 且
业务需要时发奖励),这是很多实现忽略的细节。
3. BITFIELD:把位段当整数读
位图的位本身没有类型,BITFIELD 允许按有/无符号整数切片读取, 是「连续签到天数」类需求的标配:
# 假设 sign:u42 记录 6 月每日签到(第 i 位 = 第 i+1 天)
SETBIT sign:u42 0 1 # 6月1日
SETBIT sign:u42 1 1 # 6月2日
SETBIT sign:u42 2 1 # 6月3日
# 无符号读取(u 后跟位数):从第 0 位取 31 位看成一个 u31 整数
BITFIELD sign:u42 GET u31 0
# 返回: 1) (integer) 7 ← 二进制 111,低 3 位连续为 1
# 一次执行多个子命令
BITFIELD sign:u42 GET u8 0 INCRBY u8 3 1 OVERFLOW SAT
# INCRBY 对位段做无符号自增;OVERFLOW SAT 溢出饱和不回绕
#(默认 WRAP 回绕、可选 FAIL 溢出时返回 nil)
# 只读版 BITFIELD_RO(不占写缓冲,Redis 6.0+)
BITFIELD_RO sign:u42 GET u31 0
连续天数计算技巧:取出的整数 n,循环 n & 1 统计低位连续 1 的个数
(或预生成 0~2^31 的查表),无须逐位 GETBIT 网络往返。
4. 应用场景
3.1 用户在线状态
-- 用户上线
SETBIT online:users 42 1
-- 用户下线
SETBIT online:users 42 0
-- 检查在线
GETBIT online:users 42
-- 在线人数
BITCOUNT online:users
3.2 用户标签
-- 用户42有标签0和标签3
SETBIT user:42:tags 0 1
SETBIT user:42:tags 3 1
-- 统计标签数
BITCOUNT user:42:tags
3.3 活跃用户统计
-- 每日活跃用户位图
SETBIT dau:2026-06-14 42 1
-- 计算月活跃用户(OR运算)
BITOP OR mau:2026-06 dau:2026-06-01 dau:2026-06-02 ... dau:2026-06-30
BITCOUNT mau:2026-06
5. 位运算
-- AND:交集
BITOP AND result key1 key2
-- OR:并集
BITOP OR result key1 key2
-- XOR:异或
BITOP XOR result key1 key2
-- NOT:取反
BITOP NOT result key1
# 同时活跃(当日 DAU 位图做 AND)
BITOP AND dau:both dau:2026-06-13 dau:2026-06-14
BITCOUNT dau:both
# 返回: 连续两天都活跃的用户数
BITOP 对最长输入键的长度操作,结果是 String;参与的键较多时注意 一次性 O(N) 的耗时(N 为总字节数),亿级位图建议在低峰执行。
6. 陷阱与内存估算
- offset 上限 2^32-1:SETBIT 一个超大 offset 会瞬间分配到该字节,
SETBIT k 4000000000 1直接申请约 500MB——绝不能拿雪花 ID 当 offset。 - 稀疏位图反而不省:内存由最大 offset 决定而不是 1 的个数; 极稀疏场景改用 Set 或 HyperLogLog(只要基数不要明细时)。
- BITCOUNT 的 start/end 默认按字节:
BITCOUNT key 0 2统计的是前 3 个字节(前 24 位)而非前 3 位;Redis 7.0+ 可加BIT后缀改为 位语义(BITCOUNT key 0 23 BIT)。BITPOS 同理。 - 位图键无 TTL 区分:签到场景通常「按月/按天分键 + 各自设 TTL」, 避免单键无限增长。
- 主从与持久化:位图就是 String,继承通用过期/持久化规则; RDB 中按实际分配字节存储。
7. 小结
- 初学者要点:SETBIT/GETBIT/BITCOUNT 三板斧;offset 是小整数编号; 统计「是/否 + 数量」先想位图。
- 进阶注意:SETBIT 返回旧值可用于「首次」判断;BITFIELD 处理多位段 与连续签到;注意字节 vs 位区间语义(7.0+ BIT 后缀);稀疏大 offset 会造成内存事故。
- 下一步:《数值统计》(redis/050-NumberStats)覆盖 INCR/HLL 等其他 计数方案;《缓存穿透击穿雪崩》(redis/120-CachePenetrationBreakdownAvalanche) 了解位图亲戚「布隆过滤器」在穿透防护中的用法。
基本操作
基本写法:设置指定位的值
SETBIT <key> <offset> <value>
# 设置用户42在第100天登录
SETBIT user:login:2026 42 1
基本写法:获取指定位的值
GETBIT <key> <offset>
# 检查用户42是否在第100天登录
GETBIT user:login:2026 42
基本写法:统计为 1 的位数
BITCOUNT <key> [start end]
# 统计2026年登录用户数
BITCOUNT user:login:2026
基本写法:查找第一个 0/1 的位置
BITPOS <key> <bit> [start end]
# 查找第一个为1的位置
BITPOS user:login:2026 1
位运算操作
基本写法:AND 交集运算
BITOP AND <destkey> <key> [key ...]
# 对 key1 和 key2 执行 AND 交集运算
BITOP AND result key1 key2
基本写法:OR 并集运算
BITOP OR <destkey> <key> [key ...]
# 对 key1 和 key2 执行 OR 并集运算
BITOP OR result key1 key2
基本写法:XOR 异或运算
BITOP XOR <destkey> <key> [key ...]
# 对 key1 和 key2 执行 XOR 异或运算
BITOP XOR result key1 key2
基本写法:NOT 取反运算
BITOP NOT <destkey> <key>
# 对 key1 执行 NOT 取反运算
BITOP NOT result key1
用户在线状态
基本写法:标记用户上线
SETBIT <online_key> <user_id> 1
# 用户42上线
SETBIT online:users 42 1
基本写法:标记用户下线
SETBIT <online_key> <user_id> 0
# 用户42下线
SETBIT online:users 42 0
基本写法:检查用户是否在线
GETBIT <online_key> <user_id>
# 检查用户42是否在线
GETBIT online:users 42
基本写法:统计在线人数
BITCOUNT <online_key>
# 统计当前在线人数
BITCOUNT online:users
用户标签
基本写法:为用户打标签
SETBIT <user_tags_key> <tag_id> 1
# 用户42拥有标签0
SETBIT user:42:tags 0 1
多标签写法:为用户打多个标签
SETBIT <user_tags_key> <tag_id> 1
# 用户42拥有标签0和标签3
SETBIT user:42:tags 0 1
SETBIT user:42:tags 3 1
基本写法:统计用户标签数
BITCOUNT <user_tags_key>
# 统计用户42的标签数量
BITCOUNT user:42:tags
活跃用户统计
基本写法:记录每日活跃用户
SETBIT <dau_key> <user_id> 1
# 记录用户42在2026-06-14活跃
SETBIT dau:2026-06-14 42 1
换行写法:计算月活跃用户(OR 运算合并)
BITOP OR <destkey> <key> [key ...]
# 计算月活跃用户(OR运算合并每日数据)
BITOP OR mau:2026-06 dau:2026-06-01 dau:2026-06-02 dau:2026-06-03
基本写法:获取月活跃用户数
BITCOUNT <mau_key>
# 获取2026年6月的月活跃用户数
BITCOUNT mau:2026-06