【Redis#9】其他数据结构
引言
Redis 除了我们最常用的 String、Hash、List、Set、ZSet(Sorted Set) 这五种基本数据结构外,还提供了很多高级或特殊用途的数据结构/类型 ,它们可以满足更复杂的业务需求。
✅ Redis 的“五大基本数据结构”回顾
类型 | 特点 |
---|---|
String | 字符串,可以是文本、数字、二进制等 |
Hash | 键值对集合,适合存储对象 |
List | 有序的字符串列表(底层为链表) |
Set | 无序且不重复的字符串集合 |
ZSet (Sorted Set) | 带有分数排序的集合 |
一、Stream(流)
Redis 5.0 引入,是一个日志型数据结构 ,支持发布订阅、持久化、消费者组 等功能。
可以把 Redis Stream 想象成一个:持久化的、可查询的、支持消费者组的消息队列系统。 基于 Radix Tree + Listpacks(或称 ziplist) 实现的,具有高效写入和读取的能力。
1. Stream 的核心概念
概念 | 说明 |
---|---|
Stream | 存储事件记录的有序队列,每个记录都有唯一 ID |
Entry / Message | 一条消息,包含多个字段(field-value 对) |
Consumer Group | 消费者组,类似 Kafka 的 consumer group,用于多消费者协作消费 |
Consumer | 消费者,属于某个消费者组 |
Pending Entries List (PEL) | 记录已发给消费者但尚未确认的消息 |
Claim | 把未确认的消息重新分配给其他消费者 |
比如:我们可以用 Stream 实现一个事件,如下:消防员 + 着火事件
事件 | 操作 |
---|---|
着火事件 | Producer 使用XADD 写入一条事件消息 |
消防员 | Consumer 属于某个消费者组,监听这个 Stream |
发现火情 | Consumer 通过XREADGROUP 接收到事件 |
使用干粉灭火器 | Consumer 执行业务逻辑(如写数据库、发通知) |
确认火已灭 | Consumer 调用XACK 确认事件处理完成 |
没火时等待 | 使用BLOCK 参数阻塞等待新事件到来 |
2. Stream 支持的功能📦
功能 | 描述 |
---|---|
📥 消息写入 | 使用XADD 命令添加新消息 |
📤 消息读取 | 使用XRANGE ,XREAD ,XREADGROUP 等命令读取消息 |
🔄 消费者组 | 支持多个消费者组并行消费,避免重复消费 |
🧾 自动偏移量管理 | 消费者组会自动维护消费进度(offset) |
⏱️ 阻塞读取 | 类似 BLPOP,使用XREAD 或XREADGROUP COUNT ... BLOCK |
🔁 消息重试机制 | 可通过XCLAIM 将失败的消息重新分发给其他消费者 |
🗑️ 消息过期 | 可设置最大条数限制(MAXLEN )实现自动清理 |
🔐 持久化支持 | 消息写入后会持久化到 AOF 和 RDB 中(如果启用) |
3. 常用命令一览🛠️
命令 | 用途 |
---|---|
XADD key [MAXLEN ~ count] * field value [field value ...] | 添加消息 |
XRANGE key start end [COUNT count] | 查询指定范围内的消息 |
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...] | 从指定位置读取消息 |
XREADGROUP GROUP group consumer [COUNT count] [BLOCK ms] STREAMS key [key ...] IDLE [min-idle-time] | 以消费者组方式读取消息 |
XACK key group id [id ...] | 确认消息已被成功处理 |
XCLAIM key group consumer min-idle-time id [id ...] | 抢占未被确认的消息 |
XDEL key id [id ...] | 删除指定消息 |
XTRIM key MAXLEN ~ count | 控制 stream 的长度(保留最近的 count 条消息) |
XGROUP CREATE key group-name $ | 创建消费者组 |
XGROUP SETID key group new-id | 设置消费者组的起始读取位置 |
XGROUP DELGROUP key group | 删除消费者组 |
XINFO STREAM key | 查看 stream 的详细信息 |
XINFO GROUPS key | 查看所有消费者组 |
XINFO CONSUMERS key group | 查看某组下的所有消费者 |
4. 示例操作🔁
1️⃣ 添加消息
XADD mystream * name Alice age 30
# 输出
"1717986912345-0"
- 每条消息都会有一个自动生成的 ID:
<时间戳>-<序列号>
2️⃣ 查询所有消息
XRANGE mystream - +
# 输出
1) 1) "1717986912345-0"2) 1) "name"2) "Alice"3) "age"4) "30"
-
表示最小 ID,+
表示最大 ID
3️⃣ 创建消费者组
XGROUP CREATE mystream mygroup $
$
表示从最后一条消息之后开始消费
4️⃣ 以消费者组方式消费消息
XREADGROUP GROUP mygroup consumer1 COUNT 1 STREAMS mystream >
>
表示只读取未被该组消费过的消息
5️⃣ 确认消息已处理
XACK mystream mygroup 1717986912345-0
6️⃣ 查看未确认的消息
XPENDING mystream mygroup
5. 使用场景
场景 | 描述 |
---|---|
📬 消息队列 | 替代 RabbitMQ、Kafka,适用于轻量级消息队列系统 |
📝 日志收集 | 收集分布式系统的日志、事件、监控数据 |
📲 事件溯源(Event Sourcing) | 所有状态变更都作为事件流存储 |
🎮 游戏排行榜更新 | 记录玩家得分变化事件 |
📊 实时数据分析 | 接收实时数据流,进行聚合、分析 |
🚦 分布式任务调度 | 多个 worker 协作处理任务流 |
二、Geospatial(地理位置)
Redis 3.2 引入,用于存储地理位置信息,并支持 距离计算、范围查询 等操作。
Geospatial(地理空间) 是指与地球表面位置相关的信息,通常包括:地理位置(经纬度)、地理对象(点、线、面)、空间关系(距离、包含、交集等)、时间维度(时空变化)
Geospatial 数据可以用于描述任何具有地理属性的事物,比如:城市的位置、道路网络、气象数据、移动设备轨迹、商店分布、用户当前位置
1. Geospatial 技术的核心组成部分🧭
组成部分 | 描述 |
---|---|
GIS(Geographic Information System) | 地理信息系统,用于采集、存储、分析和展示地理空间数据 |
GPS(Global Positioning System) | 全球定位系统,用于获取精确的地理位置坐标 |
RS(Remote Sensing) | 遥感技术,通过卫星或无人机获取地表信息 |
GEOJSON / KML / Shapefile | 常见的地理空间数据格式 |
地图服务(如 Google Maps、高德地图) | 提供可视化、导航、搜索等功能 |
2. Geospatial 应用场景
应用领域 | 示例 |
---|---|
物流配送 | 实时追踪包裹位置、规划最优路径 |
共享出行 | 显示附近车辆、计算距离、预估到达时间 |
智慧城市 | 监控交通流量、管理公共设施、应急调度 |
零售行业 | 分析门店周边人流、优化选址策略 |
农业 | 精准施肥、病虫害监测、产量预测 |
环境监测 | 追踪污染源、分析气候变化趋势 |
社交媒体 | 发布带地理位置的内容、查找附近好友 |
游戏开发 | 构建基于真实世界的 AR 游戏(如 Pokémon GO) |
3. 在数据库中支持 Geospatial 的方式🔧
很多数据库都提供了对地理空间数据的支持,下面是一些常见的数据库及其功能:
3.1 Redis GEO
Redis 从 3.2 版本开始支持 GEO 功能 ,可以用来存储地理位置,并进行距离计算和范围查询。
示例:
# 添加位置
GEOADD cities 116.4074 39.9042 "北京"
GEOADD cities 121.4737 31.2304 "上海"# 获取某城市的经纬度
GEOPOS cities 北京# 计算两地之间的距离
GEODIST cities 北京 上海 km# 查找附近的城市(500km 内)
GEORADIUS cities 116.4074 39.9042 500 km
- Redis GEO 是基于 Sorted Set + GeoHash 实现的,适合轻量级 LBS(基于位置的服务)应用。
3.2 MySQL Spatial Data Types
MySQL 支持 POINT
, LINESTRING
, POLYGON
等空间类型,以及空间索引。
示例:
CREATE TABLE locations (id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(100),coord POINT SRID 4326
);INSERT INTO locations (name, coord)
VALUES ('北京', POINT(116.4074, 39.9042));-- 查询距离北京 500 公里以内的城市
SELECT name, ST_Distance(coord, POINT(116.4074, 39.9042)) AS distance
FROM locations
HAVING distance <= 500;
Geospatial(地理空间)是指与地球位置相关的数据和技术,广泛应用于地图服务、LBS、智慧交通、环境监测等多个领域。主流数据库(如 Redis、MySQL、PostgreSQL、MongoDB)都提供了对地理空间数据的支持,帮助开发者轻松实现位置查询、距离计算、区域划分等功能
三、HyperLogLog
HyperLogLog 是 Redis 提供的一种 概率数据结构(Probabilistic Data Structure) ,用于 高效估算一个集合中不重复元素的数量(基数,Cardinality) 。
✅ 它是用来做什么的? ==> 场景:你有一个巨大的数据集,想要知道其中有多少个唯一元素(如独立访问用户、IP 数量等),但又不想用 Set 存储所有元素。 【HyperLogLog 就是为此而生的!】
1. 核心特点🔍
特性 | 描述 |
---|---|
用途 | 基数统计(去重计数) |
空间效率极高 | 最多占用 12KB 内存,可统计上亿个唯一元素 |
误差率可控 | 默认误差小于 1% |
不可获取具体元素 | 只能统计数量,不能列出具体内容 |
支持合并操作 | 多个 HLL 可以合并,进行全局统计 |
2. Redis 中 HyperLogLog 的常用命令📦
命令 | 说明 |
---|---|
PFADD key element [element ...] | 向指定的 HyperLogLog 中添加元素 |
PFCOUNT key [key ...] | 返回一个或多个 HyperLogLog 的基数估算值 |
PFMERGE dest source [source ...] | 将多个 HyperLogLog 合并为一个 |
3. 示例操作🔁
# 添加元素
PFADD visitors user1 user2 user3 user4 user5# 查看估计的独立访客数
PFCOUNT visitors
(integer) 5# 再添加一些新用户
PFADD visitors user6 user7 user3 # user3 已存在# 再次查看,user3 不会重复计数
PFCOUNT visitors
(integer) 7# 创建另一个 HyperLogLog
PFADD mobile_visitors user8 user9 user10# 合并两个 HyperLogLog
PFMERGE all_visitors visitors mobile_visitors# 查看合并后的总访问人数
PFCOUNT all_visitors
(integer) 10
4. 使用场景
场景 | 描述 |
---|---|
📊 网站 UV 统计 | 每日/每月独立访客数统计(比使用 Set 节省内存百倍) |
🌐 IP 去重计数 | 统计攻击源 IP、访问来源 IP 的数量 |
👥 用户行为分析 | 如“每日活跃用户数”、“不同设备登录数”等 |
📡 日志聚合 | 在大数据平台中做轻量级实时统计 |
🧮 数据预处理 | 预估去重数据规模,决定是否需要更精确的计算方式 |
5. 空间效率对比💾
数据结构 | 存储1万个字符串所需内存 | 存储100万个字符串所需内存 |
---|---|---|
Set | ~几 MB | 几百 MB 到 1GB+ |
HyperLogLog | 最多 12KB | 始终是 12KB |
Redis 的 HLL 实现使用了稀疏和密集两种存储格式,最终统一压缩为最多 12KB。
6. 注意事项⚠️
⚠️ 注意:它只是一个 近似算法 ,不能获取精确值。
问题 | 建议 |
---|---|
是否精确? | ❌ 否,是一个近似算法(误差 < 1%) |
是否能查具体元素? | ❌ 否,只能统计数量 |
是否适合小数据集? | ✅ 也适合,但如果你要完全精确,还是用 Set 更好 |
是否支持合并? | ✅ 支持,这是其一大优势 |
是否支持持久化? | ✅ 是,写入 AOF 和 RDB |
小结:HyperLogLog 是 Redis 提供的一个高效估算唯一元素数量的概率数据结构,非常适合用于大规模去重统计(如 UV、IP 数量等),仅需极小内存即可完成超大体量的数据估算,是大数据统计分析中的利器
四、Bitmaps(位图)
Redis 的 Bitmaps 并不是一种独立的数据结构 ,而是基于 String 类型 实现的一种高效操作二进制位(bit)的方式。
它非常适合用于:用户签到系统、日活统计(DAU)、布尔状态记录(如是否已读、是否登录等)、紧凑的去重计数
1. 核心特点
特性 | 描述 |
---|---|
存储方式 | 基于 String,每个字符是 8 bit |
操作粒度 | 每个 bit 可单独设置、获取、统计 |
节省内存 | 1 字节可表示 8 个布尔值,极大节省空间 |
支持位运算 | AND、OR、XOR、NOT 等 |
适用场景 | 状态标记、签到、访问统计 |
2. 常用命令📦
命令 | 说明 |
---|---|
SETBIT key offset value | 设置某个 bit 位的值(0 或 1) |
GETBIT key offset | 获取某个 bit 位的值 |
BITCOUNT key [start end] | 统计被设为 1 的 bit 数量 |
BITPOS key bit [start] [end] | 查找第一个值为指定 bit 的位置 |
BITOP operation destkey key [key ...] | 对多个 bitmap 做位运算(AND/OR/XOR/NOT) |
3. 示例操作🔁
3.1 用户签到系统(一个月)
# 用户 ID 为 1001,表示他在第 0 天(1号)和第 7 天(8号)签到了
SETBIT user:1001:sign_in 0 1
SETBIT user:1001:sign_in 7 1# 查询某天是否签到
GETBIT user:1001:sign_in 0 # 返回 (integer) 1
GETBIT user:1001:sign_in 3 # 返回 (integer) 0# 统计总共签到几天
BITCOUNT user:1001:sign_in # 返回 (integer) 2
3.2 统计日活跃用户(DAU)
假设你有 100,000 个用户,ID 从 0 到 99999。每天记录一个用户是否登录:
# 今天是 2025-04-05
SETBIT dau:20250405 1001 1 # 用户 1001 登录了
SETBIT dau:20250405 2002 1 # 用户 2002 登录了# 查询总登录人数
BITCOUNT dau:20250405 # 返回 (integer) 2
3.3 多日签到合并统计(BitOp)
你想知道用户 1001 在连续三天内的签到情况:
# 第一天签到
SETBIT user:1001:day1 0 1
SETBIT user:1001:day1 2 1# 第二天签到
SETBIT user:1001:day2 1 1
SETBIT user:1001:day2 2 1# 合并两天签到情况(按 OR 运算)
BITOP OR user:1001:total_sign user:1001:day1 user:1001:day2# 查看合并后的签到总数
BITCOUNT user:1001:total_sign # 返回 (integer) 3 (0,1,2 都为 1)
4. 使用场景
场景 | 描述 |
---|---|
📅 用户签到系统 | 每个 bit 表示一天是否签到 |
👥 日活跃用户统计(DAU) | 每个 bit 表示一个用户当天是否活跃 |
📮 已读消息标记 | 记录用户是否阅读过某条消息 |
🎮 游戏成就系统 | 每个 bit 表示一项成就是否完成 |
🧮 紧凑布尔状态存储 | 替代多个 key,减少内存开销 |
📊 数据压缩 | 用最少的空间表达大量布尔状态 |
5. 内存占用对比💾
数据类型 | 1w 个布尔值所需内存 | 100w 个布尔值所需内存 |
---|---|---|
Hash / Set | 几 MB ~ 几十 MB | 几百 MB ~ 1GB+ |
Bitmaps | 1250 Bytes | ~124KB |
💡 一个 BitMap 可以用 12KB 内存来表示超过 10 万个布尔值 !
⚠️ 注意事项
问题 | 建议 |
---|---|
是否精确 | ✅ 是(无误差) |
是否支持并发 | ✅ 是(Redis 是单线程原子操作) |
是否适合小数据集 | ✅ 是 |
是否能查询具体哪些 bit 为 1 | ❌ 否(只能统计数量或查找位置) |
是否支持压缩 | ✅ 是(Redis 内部做了优化) |
小结:Redis Bitmaps 是一种基于 String 的高效位操作机制,非常适合用来做签到系统、日活统计、布尔状态管理等场景,仅需极小内存即可处理上百万级别的布尔状态,是 Redis 中非常实用的“隐藏神器”。
五、Bitfields
Bitfields
是 Redis 提供的一种高级数据结构,它允许你在 Redis 的 String 类型中操作二进制位(bit)的多个字段(field) 。它是对BITFIELD
命令的支持,从 Redis 3.2 版本开始引入。
1. Bitfields 是什么?🧠
Redis 中的 String 是二进制安全的字节数组 ,一个 String 最多可以存储 512MB 的数据。而 BITFIELD
命令允许我们在这些字节中定义多个 “位字段”(bitfield) ,每个字段可以是:
- 指定长度的 有符号整数(signed integer)
- 或者无符号整数(unsigned integer)
我们可以对这些字段进行 读取、写入、增减等操作 ,非常适合用于紧凑的数据表示和高效的状态管理。
2. 使用场景
📅 用户签到系统 | 用 1 bit 表示一天是否签到,365 天只需 46 字节 |
🎮 游戏角色状态 | 存储角色属性、技能等级、成就等信息 |
📊 高频计数器 | 精确控制字段大小,节省内存空间 |
🧮 位标志集合 | 代替多个布尔值,减少 key 数量 |
3. BITFIELD 常用命令🛠️
3.1 定义并操作多个字段
BITFIELD key [GET type pos] [SET type pos val] [INCRBY type pos delta]
参数说明:
- type:字段类型,如:
u4
表示 4 位无符号整数(0 ~ 15)i8
表示 8 位有符号整数(-128 ~ 127)
pos
:字段起始的 bit 位置(从 0 开始)val
:要设置的值delta
:增量值
3.2 示例操作🔁
① 记录用户每月签到情况(31天)
# 设置第0天为已签到(1),第1天未签到(0)
BITFIELD user:1001:sign_in SET u1 0 1 SET u1 1 0# 查询第0天和第1天签到状态
BITFIELD user:1001:sign_in GET u1 0 GET u1 1# 输出
1) (integer) 1
2) (integer) 0
② 使用带符号整数操作计数器
# 设置第0位为 4 位有符号整数,初始值为 5
BITFIELD user:points SET i4 0 5# 查询该字段的值
BITFIELD user:points GET i4 0
(integer) 5# 增加 2
BITFIELD user:points INCRBY i4 0 2
(integer) 7# 减少 5
BITFIELD user:points INCRBY i4 0 -5
(integer) 2
③ 紧凑存储个状态字段
比如你想存储一个游戏角色的状态:
字段 | 类型 | 占用位数 | 取值范围 |
---|---|---|---|
HP(生命值) | 无符号 | 10 bits | 0 ~ 1023 |
MP(魔法值) | 无符号 | 8 bits | 0 ~ 255 |
状态标志 | 有符号 | 2 bits | -2 ~ 1 |
# 初始化 HP=500, MP=200, 状态=-1
BITFIELD player:101 SET u10 0 500 SET u8 10 200 SET i2 18 -1# 查询所有字段
BITFIELD player:101 GET u10 0 GET u8 10 GET i2 18# 输出
1) (integer) 500
2) (integer) 200
3) (integer) -1
4. Bitfields 的优势
优点 | 说明 |
---|---|
💾 内存占用极低 | 比多个 key 更节省内存 |
⚡ 高效访问 | 所有操作都在一个 key 中完成 |
🧮 支持多种数据格式 | 支持有符号/无符号整数 |
🔄 原子性操作 | 支持原子增减,适合并发计数 |
🧩 结构灵活 | 可自定义字段大小和偏移量 |
5. 注意事项⚠️
- 所有操作都是基于 bit 级别 ,需要自己计算偏移位置。
- 不支持直接删除某个字段,只能重置整个 key。
- 如果字段超出范围,会自动截断(wrap around)或溢出。
- 适用于内部状态存储,不适合频繁查询的业务逻辑。
结论:Redis 的 Bitfields 是一种强大的二进制字段操作工具,它允许你在字符串中按位定义多个字段,并对其进行读写、增减等操作,非常适合用来做状态管理、签到系统、紧凑计数器等高性能、低内存消耗的场景
六、Module 扩展模块
Redis 支持通过加载模块来扩展新的数据结构。例如:
📌 RedisJSON(JSON 数据结构)
- 存储 JSON 格式数据
- 支持 JSONPath 查询和修改字段
JSON.SET user:1001 $ '{"name":"Tom","age":25}'
JSON.GET user:1001 $.name
📌 RedisTimeSeries(时间序列数据库)
- 高效存储和查询时间序列数据(如监控指标、传感器数据)
TS.CREATE temperature:1 LABELS sensor type temp
TS.ADD temperature:1 * 25.5
TS.RANGE temperature:1 0 -1
📌 其他常见模块:
- RedisGraph(图数据库)
- RedisSearch(全文搜索 + 聚合)
- RedisAI(机器学习模型部署)