Skip to content

第四章 三个进阶数据类型(了解即可)

本章定位: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 valuekey 在偏移 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 可用「一年中的第几天」等规则映射。

text
# 用户 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。

text
# 今日首页 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:20250324

4.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 >组内消费者拉取未投递给自己组内其他消费者的新消息(> 表示新消息)

典型场景:轻量级消息队列

text
# 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。


本章小结

类型一句话核心命令印象典型场景
BitmapString 当位数组,极省空间存 0/1SETBIT / GETBIT / BITCOUNT / BITOP签到、日活标记、简单位运算统计
HyperLogLog极小内存估算「去重个数」,有误差PFADD / PFCOUNT / PFMERGEUV、海量基数估算
Stream带 ID 的消息流 + 消费者组XADD / XREAD / XRANGE / XGROUP + XREADGROUP轻量队列、事件流

下一章预告

第五章 将回到日常开发中最常用的「组合拳」:键的过期与内存策略、简单事务与乐观锁思路、以及如何用 Redis 做缓存时避免典型坑(穿透、击穿、雪崩等概念入门)。我们会用短示例把「怎么用」和「要注意什么」对齐,方便你直接对照自己的项目思考。

坚持是一种品格