第四章 三个进阶数据类型(了解即可)
本章定位:Bitmap、HyperLogLog、Stream 属于「锦上添花」的能力。初学阶段知道它们解决什么问题、大概怎么用即可;真上生产前再查官方文档细抠参数与边界情况。
4.1 Bitmap —— 签到统计、布隆过滤器基础
是什么
Bitmap(位图)不是一种新的「独立类型名」,而是把 String 的值当成一串二进制位 来用:每个 bit 只能是 0 或 1,适合表示「有 / 无」「是 / 否」这类极省空间的标记。
内存里可以想象成一长串开关(每个用户、每一天占 1 bit):
bit 位置: 0 1 2 3 4 5 ...
|--|--|--|--|--|--|...
含义示例: 签 签 未 签 签 未 ...特点简述:
| 优点 | 说明 |
|---|---|
| 省空间 | 大量布尔标记时,比存很多独立 key 更紧凑 |
| 运算快 | 可按位设置、统计、做 AND/OR 等位运算 |
与布隆过滤器:布隆过滤器常用「多个哈希 → 多个 bit 位置」的思路;Redis 里可用 Bitmap 模拟简单布隆或配合业务自己做标记,完整布隆可再了解 RedisBloom 等模块(本章不展开)。
核心命令
| 命令 | 作用 |
|---|---|
SETBIT key offset value | 将 key 在偏移 offset 上的 bit 设为 0 或 1 |
GETBIT key offset | 读取指定偏移上的 bit |
BITCOUNT key [start end] | 统计值为 1 的 bit 个数(可限定字节范围) |
BITOP operation destkey key [key...] | 对多个 key 做 AND / OR / XOR / NOT,结果写入 destkey |
典型场景:用户连续签到统计
思路:按「用户 + 某段时间」用一条位图表示每一天是否签到;offset 可用「一年中的第几天」等规则映射。
# 用户 user:1001 在 2025 年的签到位图(示意:第 1 天签到了)
SETBIT sign:user:1001:2025 0 1
GETBIT sign:user:1001:2025 0
# (integer) 1
# 再签几天(第 2、3 天)
SETBIT sign:user:1001:2025 1 1
SETBIT sign:user:1001:2025 2 1
# 统计这一年(或某段字节范围)一共签了多少天
BITCOUNT sign:user:1001:2025连续签到:可在应用里配合 GETBIT 从某天往前扫,或把「连续天数」另外缓存;复杂规则仍以业务代码为主,Bitmap 负责高密度存 yes/no。
4.2 HyperLogLog —— 海量数据基数统计
是什么
基数(cardinality):集合里不重复元素的个数。例如很多用户 ID 访问过页面,你想知道「大约有多少个不同用户」,而不是把每个 ID 都存下来。
HyperLogLog(HLL)是一种概率型数据结构:用固定很小的内存估算「去重后的数量」,结果有误差(通常可接受),不能列出具体是哪些元素。
精确集合: { A, B, C, A, B } --> 基数 = 3(要存全量或去重结构)
HyperLogLog: 只接收元素流 --> 输出 ≈ 3(只占几 KB 级内存,有误差)适用边界:要的是「大概多少」且数据量巨大;要100% 准确或要枚举所有不同值,请用 Set 等其他结构。
核心命令
| 命令 | 作用 |
|---|---|
PFADD key element [element ...] | 向 HyperLogLog 增加元素(可多个) |
PFCOUNT key [key ...] | 返回估算基数;多个 key 时返回合并后的估算 |
PFMERGE destkey sourcekey [sourcekey ...] | 合并多个 HLL 到 destkey |
典型场景:UV(独立访客)统计
思路:每个页面或全站一个 HLL key,每次访问把用户标识 PFADD 进去,用 PFCOUNT 看近似 UV。
# 今日首页 UV
PFADD uv:home:20250324 user_1001 user_1002 user_1001
PFCOUNT uv:home:20250324
# 返回类似 (integer) 2 (重复 user_1001 只算一次,结果为估算值)
# 多源合并(例如把多个子页面的 UV 合成「板块 UV」)
PFADD uv:sectionA:20250324 u1 u2
PFADD uv:sectionB:20250324 u2 u3
PFMERGE uv:board:20250324 uv:sectionA:20250324 uv:sectionB:20250324
PFCOUNT uv:board:202503244.3 Stream —— Redis 原生消息队列(Redis 5.0+)
是什么
Stream 是 Redis 5.0 起提供的日志型、可持久感知的结构:消息带 ID(时间有序),支持消费者组(同一条消息可被组内一个消费者认领),适合当轻量级消息队列或事件流。
生产者 Stream (按 ID 追加)
| |
+--- XADD ----------------------> [ ID-1 msg1, ID-2 msg2, ... ]
|
消费者组 / XREAD <-----------------------+前提:使用 Stream 前确认 Redis 版本 ≥ 5.0(更完善的组特性在后续版本持续演进)。
核心命令(入门够用版)
| 命令 | 作用 |
|---|---|
XADD key ID field value [field value ...] | 向流追加一条消息;ID 常用 * 由 Redis 自动生成 |
XREAD [COUNT n] [BLOCK ms] STREAMS key ID | 按 ID 读取;BLOCK 可阻塞等待新消息 |
XRANGE key start end [COUNT n] | 按 ID 范围遍历历史消息 |
XGROUP CREATE key groupname id | 创建消费者组;id 常用 0 或 $ |
XREADGROUP GROUP group consumer COUNT n STREAMS key > | 组内消费者拉取未投递给自己组内其他消费者的新消息(> 表示新消息) |
典型场景:轻量级消息队列
# 1) 生产:往订单事件流里写一条
XADD orders * type paid order_id 10001 user_id 200
# 返回类似 "1740000000000-0" 的消息 ID
# 2) 简单消费:从开头读两条(非组模式,入门演示)
XRANGE orders - + COUNT 2
# 3) 阻塞读新消息(0 表示只读新产生的;BLOCK 5000 最多等 5 秒)
XREAD BLOCK 5000 STREAMS orders $
# 4) 消费者组(需先建组,再从组里读)
XGROUP CREATE orders cg1 0 MKSTREAM
XREADGROUP GROUP cg1 worker1 COUNT 10 BLOCK 2000 STREAMS orders >与 List 做队列的对比:
| 对比项 | List(LPUSH + BRPOP 等) | Stream |
|---|---|---|
| 消息标识 | 无统一内置 ID(靠值自己约定) | 内置时间有序 ID |
| 消费语义 | 弹出即「拿走」,需自己 ack 方案 | 原生消费者组、pending、ack(XACK 等) |
| 历史与追溯 | 弹出后默认难追溯 | 可保留日志、按范围查 |
| 复杂度 | 模型简单,够用很多场景 | 概念与命令更多,适合要明确「流 + 组」的场景 |
小结一句:只要「队列入队出队、简单任务」List 往往够用;要 ID、多消费者协作、可回溯 时再考虑 Stream。
本章小结
| 类型 | 一句话 | 核心命令印象 | 典型场景 |
|---|---|---|---|
| Bitmap | String 当位数组,极省空间存 0/1 | SETBIT / GETBIT / BITCOUNT / BITOP | 签到、日活标记、简单位运算统计 |
| HyperLogLog | 极小内存估算「去重个数」,有误差 | PFADD / PFCOUNT / PFMERGE | UV、海量基数估算 |
| Stream | 带 ID 的消息流 + 消费者组 | XADD / XREAD / XRANGE / XGROUP + XREADGROUP | 轻量队列、事件流 |
下一章预告
第五章 将回到日常开发中最常用的「组合拳」:键的过期与内存策略、简单事务与乐观锁思路、以及如何用 Redis 做缓存时避免典型坑(穿透、击穿、雪崩等概念入门)。我们会用短示例把「怎么用」和「要注意什么」对齐,方便你直接对照自己的项目思考。