引言
Redis 是一个高效、灵活的键值存储系统,广泛应用于缓存、消息队列、实时分析等场景。Redis 提供了丰富的数据结构,每一种数据结构都适用于特定的业务需求。理解这些底层数据结构,不仅能帮助你更好地使用 Redis,也能加深你对 Redis 内部实现的理解。
本文将详细介绍 Redis 的核心数据结构,包括:
- String
- Hash
- List
- Set
- Sorted Set (Zset)
- Bitmap
- HyperLogLog
- Geo
- Stream
此外,我们还将深入解析 Zset 的底层实现,特别是 跳表 和 Redis 7.0 引入的 ListPack,并探讨 Redis 为什么选择跳表而非 B+ 树。
一、Redis 的数据结构概览
Redis 支持的多种数据结构使它能够应对各种复杂的场景。下面是对 Redis 常见数据结构的简要介绍:
String(字符串)
-
存储值:简单的字符串(例如,数字、文本、二进制数据)
-
读写能力:支持快速的读取、写入、修改操作。
-
应用场景:
-
缓存:简单的缓存键值对
-
计数器:实现页访问计数、秒杀商品库存等功能
-
Hash(哈希)
-
存储值:键值对集合(类似于字典或映射)
-
读写能力:适合存储对象,支持按字段读取和修改。
-
应用场景:
-
用户信息:存储用户的基本资料(用户名、邮箱、年龄等)
-
配置管理:存储配置信息并能快速修改
-
List(列表)
-
存储值:有序字符串集合
-
读写能力 :支持两端插入、删除(
LPUSH、RPUSH),以及索引访问。 -
应用场景:
-
消息队列:使用
LPUSH和RPOP实现生产者消费者模式 -
实时数据流:处理顺序数据流,如日志收集
-
Set(集合)
-
存储值:无序字符串集合,支持唯一性
-
读写能力:支持快速插入、删除、查找操作,支持求交集、并集和差集等集合运算。
-
应用场景:
-
唯一元素集合:如用户的标签、访问过的商品 ID
-
社交关系:如用户的好友关系、关注列表
-
Sorted Set(有序集合,Zset)
-
存储值:每个元素都包含一个分数(score)和一个值,元素会根据分数排序
-
读写能力:支持按分数区间或排名范围获取元素,支持快速插入和删除
-
应用场景:
-
排行榜:存储用户积分、游戏得分等
-
时间序列:如股票价格、传感器数据等
-
Bitmap(位图)
-
存储值:用于对 bit 位进行操作的数组
-
读写能力:可以对单个位进行高效操作,适用于计数、标记等。
-
应用场景:
-
用户签到:记录每个用户的每日签到情况
-
活跃用户统计:使用
SETBIT记录某个时间段内是否活跃
-
HyperLogLog
-
存储值:用于统计唯一元素的基数估算,适合大数据量场景
-
读写能力:基于概率算法,支持高效估算唯一值数量
-
应用场景:
- 唯一用户统计:统计活跃用户数,避免直接存储大量数据
Geo(地理空间)
-
存储值:基于经纬度坐标的数据
-
读写能力:支持按位置范围查询、计算两点之间的距离等
-
应用场景:
-
实时位置追踪:如共享单车、打车服务中的用户位置
-
最近餐馆、商店查询等
-
Stream(流)
-
存储值:消息流,支持高效的生产和消费操作
-
读写能力:类似于日志,支持高并发的生产和消费
-
应用场景:
-
实时日志:系统日志收集、监控数据流
-
消息队列:提供持久化、顺序消费的消息队列
-
二、Zset(有序集合)的底层实现
Redis Zset 数据结构概述
Redis 中的有序集合(Zset)是一个按照分数排序的字符串集合,支持以下操作:
-
插入、删除元素
-
查询指定范围的元素(按分数排序)
-
获取某个分数区间内的元素
Zset 的底层数据结构
最初,Redis 使用 压缩列表(Ziplist) 和 跳表(Skiplist) 来实现 Zset 的存储。
(1)压缩列表(Ziplist)
-
优点:内存节省,适用于元素数量小、范围不大的场景
-
缺点:插入删除操作的性能较低
(2)跳表(Skiplist)
-
Redis 选择 跳表 作为 Zset 的默认数据结构。
-
跳表通过多层次的索引结构来加速查找,具有对数时间复杂度
O(log N)。
Redis 7.0:ListPack 实现 Zset
-
从 Redis 7.0 开始,Zset 数据结构由 ListPack 取代了之前的压缩列表和跳表实现。
-
ListPack 是一种新的高效的内存编码方式,支持更低的内存消耗,并且支持更高效的查询和插入。
三、跳表(Skiplist)详解
跳表的基本概念
跳表是一种通过多级索引来加速查找的概率性数据结构。它可以在有序的链表上建立多级索引,每一级的元素个数是上一级的部分元素。
跳表的结构
-
底层:一个普通的有序链表
-
上层:多个索引层,元素的数量逐层减少
跳表的层高如何设置?
跳表的层高是动态确定的,通过 概率性算法 来决定:
-
每个元素有一定概率升到上层
-
理论上,跳表的层高通常是对数级别,平均层高约为
log N
为什么 Redis 选择跳表而非 B+ 树?
B+ 树 vs 跳表
-
B+ 树:结构更复杂,性能受限于树的平衡和节点管理
-
跳表 :实现简单,且由于概率机制,跳表具有良好的 内存局部性 ,适合 内存存储 这种快速查找需求
Redis 选择 跳表 的原因:
-
内存效率:跳表在内存中的实现比 B+ 树简单,且性能更好
-
实现简洁:跳表的实现不需要复杂的树的平衡机制,增加了 Redis 的易用性和可维护性
-
高并发:跳表适用于高并发场景,能高效处理大量数据的插入和查询
总结
- Redis 提供了多种高效的数据结构,能够满足不同业务需求。
- Zset 的底层实现,从最初的压缩列表到跳表,再到 Redis 7.0 的 ListPack,逐步提高了性能与内存效率。
- Redis 选择 跳表 而非 B+ 树,因为跳表在内存中的实现更简单、高效,适合快速查找。
Redis 通过这些高效的数据结构,保证了它在高并发、高性能场景中的优势。