Redis位图实战:海量数据高效处理

引言

在前面的 Redis 系列中,我们学习了五种基本数据类型和发布订阅。今天要讲的位图(Bitmap) 不是一种独立的数据类型,而是 String 类型的一种特殊用法------把字符串当成二进制位数组来操作。

位图的核心思想是:用一个 bit 表示一个状态(0 或 1)。1 字节 = 8 个 bit,可以表示 8 个独立的状态。这种极致的内存效率,让位图特别适合海量数据的布尔统计场景------用户签到、活跃用户统计、权限判断等。

第一部分:位图的基本原理

一、位图是什么

位图本质就是一个 String 字符串 ,但操作的单位不是字节,而是比特(bit)。Redis 提供了专门操作比特位的命令。

关键概念

概念 说明
offset 位的偏移量,从 0 开始(从左向右计数)
最大值 2³² - 1 = 约 42 亿 bit ≈ 512 MB
底层类型 String,自动扩容

二、为什么用位图

场景 传统做法 位图做法 节省
1 亿用户签到 1 亿个 key 约 12MB 一个 key 数千倍
日活统计 set/hash 约 12MB 数百倍
权限 100 项 hash 存储 约 13 字节 数十倍

第二部分:核心命令

一、设置位:SETBIT

bash 复制代码
# 语法:SETBIT key offset value
# value 只能是 0 或 1

127.0.0.1:6379> SETBIT user:1:sign 0 1   # 第 0 天签到
(integer) 0   # 返回旧值

127.0.0.1:6379> SETBIT user:1:sign 1 1   # 第 1 天签到
(integer) 0

127.0.0.1:6379> SETBIT user:1:sign 5 1   # 第 5 天签到
(integer) 0

127.0.0.1:6379> SETBIT user:1:sign 0 0   # 取消第 0 天签到
(integer) 1   # 返回旧值为 1

注意

  • offset 可以从 0 到 2³²-1(约 42 亿)

  • Redis 会自动扩容字符串以容纳更大的 offset

  • 返回值是设置前的旧值

二、获取位:GETBIT

bash 复制代码
# 语法:GETBIT key offset

127.0.0.1:6379> GETBIT user:1:sign 0
(integer) 0

127.0.0.1:6379> GETBIT user:1:sign 1
(integer) 1

127.0.0.1:6379> GETBIT user:1:sign 2   # 未设置过
(integer) 0   # 默认返回 0

三、统计位数:BITCOUNT

bash 复制代码
# 语法:BITCOUNT key [start end]

# 统计有多少个 1
127.0.0.1:6379> BITCOUNT user:1:sign
(integer) 2   # 有两个 1

# 统计指定字节范围(注意是字节,不是 bit!)
127.0.0.1:6379> BITCOUNT user:1:sign 0 0   # 第 0 个字节
(integer) 1

BITCOUNT 的 start/end 是字节索引,不是 bit 索引

BITCOUNT key 0 0 → 只统计第 0 个字节(bit 0~7)

BITCOUNT key 0 1 → 统计第 0~1 个字节(bit 0~15)

BITCOUNT key 0 -1 → 统计所有字节

四、查找位:BITPOS

bash 复制代码
# 语法:BITPOS key bit [start] [end]
# 查找第一个值为 bit 的位置

127.0.0.1:6379> BITPOS user:1:sign 1   # 第一个 1 的位置
(integer) 1

127.0.0.1:6379> BITPOS user:1:sign 0   # 第一个 0 的位置
(integer) 0

# 在指定字节范围内查找
127.0.0.1:6379> BITPOS user:1:sign 1 1 5   # 第 1~5 字节内找第一个 1

五、位运算:BITOP

bash 复制代码
# 语法:BITOP operation destkey key [key ...]
# 支持:AND(与)、OR(或)、XOR(异或)、NOT(非)

127.0.0.1:6379> SETBIT user:1:day1 0 1
127.0.0.1:6379> SETBIT user:1:day2 0 1
127.0.0.1:6379> SETBIT user:2:day1 0 1

# AND:两天都签到的用户
127.0.0.1:6379> BITOP AND both_days user:1:day1 user:1:day2
(integer) 1

# OR:任意一天签到的用户
127.0.0.1:6379> BITOP OR any_day user:1:day1 user:1:day2
(integer) 1

# XOR:恰好只签了一天的用户
127.0.0.1:6379> BITOP XOR only_one user:1:day1 user:1:day2
(integer) 1

位运算的应用

运算 场景
AND 连续 N 天都签到的用户
OR N 天内任意一天签到的用户
XOR 恰好签了奇数天的用户
NOT 没有签到的用户

第三部分:实战场景

一、用户签到系统

bash 复制代码
# 用户 1001 在第 0 天签到
127.0.0.1:6379> SETBIT sign:2024:06:01 1001 1
(integer) 0

# 用户 1002 签到
127.0.0.1:6379> SETBIT sign:2024:06:01 1002 1
(integer) 0

# 查看用户 1001 是否签到
127.0.0.1:6379> GETBIT sign:2024:06:01 1001
(integer) 1

# 统计今日签到人数
127.0.0.1:6379> BITCOUNT sign:2024:06:01
(integer) 2

# 统计本月全勤用户(6月1日~30日都签到)
127.0.0.1:6379> BITOP AND full_attendance sign:2024:06:01 sign:2024:06:02 ... sign:2024:06:30
127.0.0.1:6379> BITCOUNT full_attendance

二、统计在线活跃用户

bash 复制代码
# 记录每个用户的活跃状态(user_id 作为 offset)
# 2024-06-01 活跃用户
127.0.0.1:6379> SETBIT active:2024-06-01 1001 1
127.0.0.1:6379> SETBIT active:2024-06-01 1002 1
127.0.0.1:6379> SETBIT active:2024-06-01 1005 1

# 2024-06-02 活跃用户
127.0.0.1:6379> SETBIT active:2024-06-02 1001 1
127.0.0.1:6379> SETBIT active:2024-06-02 1003 1

# 两天都活跃的用户
127.0.0.1:6379> BITOP AND active_both active:2024-06-01 active:2024-06-02
127.0.0.1:6379> BITCOUNT active_both
(integer) 1   # 只有用户 1001 两天都活跃

三、权限管理

bash 复制代码
# 100 个权限位,每位代表一个权限
# bit 0: 读权限  bit 1: 写权限  bit 2: 删除权限  bit 3: 管理权限 ...

# 管理员拥有所有权限(前 10 位都设为 1)
127.0.0.1:6379> SETBIT user:admin:permissions 0 1
127.0.0.1:6379> SETBIT user:admin:permissions 1 1
...
127.0.0.1:6379> SETBIT user:admin:permissions 9 1

# 普通用户只有读权限
127.0.0.1:6379> SETBIT user:normal:permissions 0 1

# 检查权限
127.0.0.1:6379> GETBIT user:normal:permissions 1
(integer) 0   # 没有写权限

第四部分:C 语言操作位图

cpp 复制代码
#include <stdio.h>
#include <hiredis/hiredis.h>

int main() {
    redisContext *c = redisConnect("127.0.0.1", 6379);
    if (c == NULL || c->err) {
        printf("连接失败: %s\n", c->errstr);
        return -1;
    }

    // 设置签到
    redisReply *reply = redisCommand(c, "SETBIT sign:2024:06:01 1001 1");
    printf("设置结果(旧值): %lld\n", reply->integer);
    freeReplyObject(reply);

    // 检查是否签到
    reply = redisCommand(c, "GETBIT sign:2024:06:01 1001");
    printf("签到状态: %lld\n", reply->integer);
    freeReplyObject(reply);

    // 统计签到人数
    reply = redisCommand(c, "BITCOUNT sign:2024:06:01");
    printf("签到人数: %lld\n", reply->integer);
    freeReplyObject(reply);

    redisFree(c);
    return 0;
}

第五部分:位图的局限性

局限 说明 解决方案
offset 映射 user_id 需要是连续整数 维护 user_id → 整数映射表
稀疏数据浪费 如果 offset 很大但只有几个 1 用集合或布隆过滤器
不可直接遍历 1 BITCOUNT 只统计数量 BITPOS 逐个查找
字符串最大 512MB offset 最大约 42 亿 分片存储

总结

一、核心命令速查

命令 作用
SETBIT key offset 0/1 设置某位的值
GETBIT key offset 获取某位的值
BITCOUNT key [start end] 统计 1 的数量(字节范围)
BITPOS key bit [start end] 查找第一个 bit 的位置
BITOP AND/OR/XOR/NOT dest key1 key2 位运算

二、一句话记忆

Redis 位图用 String 类型的每个 bit 表示一个布尔状态,1 bit 存一个信息。用 SETBIT/GETBIT 操作单个位,用 BITCOUNT 统计数量,用 BITOP 做集合运算。适合签到、活跃用户、权限等海量布尔统计场景,空间效率是普通 key-value 的数百倍。

相关推荐
头歌实践平台1 小时前
头歌数据库 触发器
数据库
比企谷八幡1 小时前
数据库 Page 内部是什么样:Page Header、Slot 和 Line Pointer
数据库·c++·postgresql·数据库架构
小二·1 小时前
Redis 7 实战:缓存/消息队列/分布式锁生产级实现
redis·分布式·缓存
日取其半万世不竭1 小时前
密码管理工具私有化部署,Vaultwarden 备份恢复怎么做?
数据库·docker·容器
海市公约1 小时前
Redis 哨兵模式底层原理与自动故障转移全流程
redis·sentinel·redis哨兵·高可用架构·主观下线·客观下线·leader选举
abigale031 小时前
LangChain 实践4: 7个人AI助手全栈项目:完整拆解+分阶段开发指南
缓存·langchain·prompt·token·rag·lcel
填满你的记忆1 小时前
《为什么 MySQL 不适合做 AI 检索?》
数据库·人工智能·mysql·ai·向量数据库
map1e_zjc1 小时前
Redis入门笔记
数据库·redis·缓存
辞忧九千七1 小时前
Redis高可用基石:从发布订阅到主从复制再到哨兵模式
redis