Redis 命令及数据类型 -- Bitmap
摘要
- 本文介绍 Redis Bitmap 数据类型
- 本文基于
redis-7.4.7,springboot-3.5.8 - Redis官网:https://redis.io/
- Redis 命令文档:https://redis.io/docs/latest/commands/
Bitmap 数据类型
-
Redis Bitmap 并非独立数据类型,而是基于 String 类型的位操作扩展
-
String 底层是字节数组,Bitmap 就是对数组中单个 bit 做读写(bit 只有 0/1 两个值)
Bitmap 命令使用方式
-
核心围绕「位设置、位查询、位统计、位运算」四类命令,是日常使用的基础
1. 位设置:SETBIT key offset value
-
给指定 key 的第 offset 位设 0/1(offset 从 0 开始,支持超大偏移量,Redis 会自动扩容)
-
offset 从左往右递增,从左到右为 0、1、2…,至少申请 8bit 空间,不足 8bit 时,会自动扩展到 8bit 即 1byte
1 | SETBIT bitkey 1 1 # 实际的bit为 01000000 |
2. 位查询:GETBIT key offset
-
查询指定偏移量的位值,不存在的 offset 默认返回 0
1 | GETBIT bitkey 1 |
3. 位统计:BITCOUNT key [start end]
-
统计 key 中值为 1 的 bit 总数,可选按字节范围(start/end 是字节索引)筛选
1 | BITCOUNT bitkey |
4. 位运算:BITOP op destkey key1 key2...
-
对多个 Bitmap 做 与(AND)、或(OR)、异或(XOR)、非(NOT)运算,结果存入 destkey
1 | # 将key1 与 key2 做按位与运算,结果存入 destkey |
5. 位查找:BITPOS key value [start end]
-
查找第一个值为 0/1 的 bit 偏移量,快速定位目标位
1 | BITPOS bitkey 1 |
6. BITFIELD 批量位操作
• BITFIELD 是 Redis 用于把一个 字符串值视为一个由二进制“位数组”组成的存储区,并对其中任意指定位置的整数域进行读取、写入、自增等操作的命令。
• 这些整数域可以是任意位宽(例如 1 位、4 位、8 位、31 位、63 位等),可指定为有符号(signed)或无符号(unsigned)。
• BITFIELD 命令支持在一次调用中执行多个操作,并将结果按操作顺序返回。
1 | BITFIELD key [GET encoding offset | [OVERFLOW <WRAP | SAT | FAIL>] |
• 参数说明(核心部分)
| 参数 | 含义 |
|---|---|
| key | 操作的 Redis 字符串键 |
| GET encoding offset | 从指定位偏移量读取一个整数 |
| SET encoding offset value | 在指定位置写入整数 |
| INCRBY encoding offset increment | 在指定位置对整数做增量操作 |
| OVERFLOW WRAP/SAT/FAIL | 配置随后的算数操作溢出行为 |
• 数据类型(encoding):用于指定整数的位宽和符号类型
| 前缀 | 含义 | 示例 |
|---|---|---|
u<number> |
无符号整数(unsigned),占位 bits = number | u5 — 5 位无符号整数 |
i<number> |
有符号整数(signed),占位 bits = number | i10 — 10 位有符号整数 |
• 溢出(OVERFLOW):默认算数操作中可能发生溢出,OVERFLOW 允许你控制处理策略。注意,这部分必须在后续的 SET/INCRBY 操作之前指定。
| 策略 | 行为 |
|---|---|
| WRAP | 环绕(默认)溢出按环形计数处理 |
| SAT | 饱和,在边界值保持最大/最小 |
| FAIL | 溢出时操作失败并返回错误 |
1 | # 4 位无符号整数加 1 时,如果超过 15,则保持 15(饱和)。 |
• 返回值: BITFIELD 会为每个子命令返回一个整数数组,数组各元素按操作顺序对应执行结果
1 | BITFIELD mykey INCRBY i5 100 1 GET u4 0 |
综合示例
-
示例 1. 设置并读取简单整数
1 | > SET mykey "" # 此时mykey 的值是空字符串,长度为 0 |
负数的二进制表示
- 以 -2 为例,先写出 +2 的二进制,以8位为例,就是
00000010,4位就是0010 - 按位取反(得到反码),例如
00000010,按位取反就是11111101 - 加 1(得到补码),
11111101+ 1,得到11111110,这就是 -2 的二进制表示 - 如果是4位,则 -2 就是
1110
-
示例 2. 自增计数器
1 | # 如果 counter 之前为空,则视为 0 |
-
示例 3. 带溢出控制的操作
1 | # OVERFLOW SAT: 设置溢出策略为 SAT(饱和) |
-
示例 4. 批量多个操作
1 | # GET u4 0 GET u4 4: 依次读取多个不同 bit 偏移量上的小整数 |
核心使用场景 + 实操举例(贴合开发实战)
-
Bitmap 的核心优势是极致省内存+高效统计(1 个字节=8 个 bit,存储 1000 万条状态仅需约 1.2MB),以下是高频场景
场景1: 用户签到/打卡(最经典场景)
• 需求:记录用户每日签到状态,查询某用户某天是否签到、统计某用户月度签到次数
• 设计:key 格式 user:sign:uid:202512(用户2025年12月签到),offset 为日期(1号=0、2号=1…31号=30),签到设 1、未签到默认 0
• 实操命令:
-
12月1号签到:
SETBIT user:sign:uid:202512 0 1 -
查询12月1号是否签到:
GETBIT user:sign:uid:202512 0(返回1=签到) -
统计12月总签到次数:
BITCOUNT user:sign:uid:202512
• 优势:1个用户1个月签到仅占 4 字节(31 bit),百万用户月度签到仅占约 3.9MB,远超数据库存储的性价比。
场景2: 日活/周活/月活(DAU/WAU/MAU)统计(高并发场景首选)
• 需求:统计每日访问平台的用户数,快速计算周活(7天内至少访问1次)、月活,去重统计
• 设计:按日期建 Bitmap,key 格式 active:user:20251217(当日活跃),offset 设为用户唯一ID(需确保ID是连续或可映射为数字,避免超大偏移量),用户访问则设 1
• 实操命令:
-
用户ID 10086 12月17日访问:
SETBIT active:user:20251217 10086 1 -
统计12月17日日活:
BITCOUNT active:user:20251217 -
统计12月15-17日周活(3天内活跃):
BITOP OR active:user:20251215_17 active:user:20251215 active:user:20251216 active:user:20251217→ 再执行BITCOUNT active:user:20251215_17
• 优势:百万级用户日活统计,单 Bitmap 仅占约 125KB,位运算合并统计速度毫秒级,远快于数据库 group by 去重。
场景3: 功能开关/状态标记(多维度轻量标记)
• 需求:给用户标记多类轻量状态(如是否开通会员、是否绑定手机、是否参与活动),无需单独存多个key
• 设计:1个key对应1个用户,key 格式 user:status:10086,不同 offset 对应不同状态(offset0=是否绑定手机、offset1=是否会员、offset2=是否参与活动),1=是、0=否
• 实操命令:
-
给用户10086绑定手机:
SETBIT user:status:10086 0 1 -
开通会员:
SETBIT user:status:10086 1 1 -
查询是否是会员:
GETBIT user:status:10086 1
• 优势:1个key承载用户N个状态,无需维护多个 String/Hash,查询和修改均为O(1),极简高效。
场景4: 布隆过滤器底层实现(核心依赖Bitmap)
• 需求:实现海量数据的快速去重判断(如缓存穿透防护、海量URL去重),允许极小误判率,不允许漏判
• 设计:用1个大 Bitmap 作为底层存储,配合多个哈希函数 —— 数据存入时,通过多个哈希函数算出多个 offset,将对应 bit 设为1;查询时,若所有哈希对应的 offset 都是1,则大概率存在,否则一定不存在
• 实操:Redis 7 可直接用 Bitmap 手动实现,也可结合 RedisBloom 扩展(更易用),核心原理是 Bitmap 的位设置与查询。
• 优势:存储1亿条数据,误判率5%的布隆过滤器,仅需约 12MB 内存,查询速度极致快。
注意事项(避坑关键)
-
offset 不要无限制过大:虽 Redis 支持超大 offset,但过大(如超过10亿)会导致 Bitmap 占用内存骤增,需合理规划 offset 范围(如用户ID做哈希映射压缩)
-
避免单 key 过大:单个 Bitmap 建议控制在1GB内(对应约85亿 bit),过大易导致Redis持久化/迁移耗时过长
-
注意编码兼容:Bitmap 基于 String,Redis 会自动用 RAW 编码存储,无需手动设置
Bitmap 命令
-
SpringBoot 的
StringRedisTemplate.opsForValue()中 Bitmap 数据类型 的操作方法与 Redis 原生命令的对应关系如下:
注意这里一定要用
StringRedisTemplate来操作 Bitmap
写操作(位修改)
| 方法功能 | 方法 | Redis 原始命令 | 备注 |
|---|---|---|---|
| 设置指定偏移量的位 | setBit(K key, long offset, boolean value) |
SETBIT key offset value |
返回旧值(0 / 1),offset 从 0 开始 |
offset 表示 第几位(bit),不是字节
读操作(位查询)
| 方法功能 | 方法 | Redis 原始命令 | 备注 |
|---|---|---|---|
| 获取指定偏移量的位 | getBit(K key, long offset) |
GETBIT key offset |
返回 0 / 1,不会修改数据 |
批量位操作(位字段 BitField)
-
位字段读/写/自增
| 方法功能 | 方法 | Redis 原始命令 | 备注 |
|---|---|---|---|
| 位字段操作(读/写/自增) | bitField(K key, BitFieldSubCommands subCommands) |
BITFIELD key ... |
原子执行多个子命令 |
-
示例
1 | BITFIELD limits |
1 | // SET u4 0 3 |
Bitmap 常用但 Spring 未直接封装的命令
-
Spring Data Redis 中通常通过 RedisCallback 或 execute 调用这些命令。
| Redis 命令 | 功能 | 说明 |
|---|---|---|
BITCOUNT key [start end] |
统计 bit=1 的数量 | 常用于活跃用户统计 |
BITPOS key bit [start end] |
查找第一个 0/1 的位置 | 常用于分配位 |
BITOP AND/OR/XOR/NOT |
位运算 | 多 bitmap 计算 |
1 | package com.example.demo.bitmap; |