Redis 核心数据结构深度解密:从基础命令到源码架构

Redis 之所以被称为"高性能数据结构服务器",是因为它不仅提供了简单的键值对存储,更在于它针对不同的应用场景,在底层设计了多种极度精巧的数据结构。

本文将针对 Redis 的 5 大基础数据结构4 大高级数据结构 进行全方位的拆解。


一、 字符串 (String):Redis 的基石

String 是 Redis 中最简单、也是最常用的类型。它是二进制安全的,意味着你可以存储任何数据(JSON、图片、序列化对象)。

1.1 基础用法

  • 设置与获取SET key value / GET key

  • 批量操作MSET k1 v1 k2 v2 / MGET k1 k2

  • 计数操作INCR key(原子加1)、DECR keyINCRBY key increment

  • 生存时间SETEX key seconds value(设置值的同时设置过期时间)

  • 不存在才设置SETNX key value(分布式锁的核心)

1.2 底层原理:SDS (简单动态字符串)

Redis 没有使用 C 语言传统的字符数组(以 \0 结尾),而是自己实现了 SDS

  • O(1) 获取长度 :内部有 len 属性,不需要像 C 字符串那样遍历。

  • 空间预分配:修改字符串时,Redis 会多分配一些空闲空间,减少内存重分配次数。

  • 二进制安全 :SDS 根据 len 判断结束,而不是 \0,所以可以存二进制图片。

1.3 内存编码

  • int:如果是 8 字节长整型,直接存数字。

  • embstr:长度 ≤ 44 字节。RedisObject 头和 SDS 在一起分配,速度极快。

  • raw:长度 > 44 字节。两次内存分配,适合存大文本。


二、 哈希 (Hash):对象存储的专家

Hash 类似于 Java 的 HashMap,特别适合存储对象,因为它不需要序列化整个对象就能修改其中的某个字段。

2.1 基础用法

  • 设置字段HSET user:100 name "Tom" age 18

  • 获取字段HGET user:100 name

  • 获取全部HGETALL user:100

  • 字段增量HINCRBY user:100 age 1

  • 判断存在HEXISTS user:100 name

2.2 底层原理

  • Listpack (新版) / ZipList (旧版):当 Hash 的字段较少且值较小时,Redis 使用紧凑的连续内存存储,极其省内存。

  • HashTable (哈希表):当数据量变大(默认超过 512 个字段或单值超过 64 字节)时,转为真正的哈希表,查询效率为 O(1。

2.3 实战场景:购物车

以用户 ID 为 Key,商品 ID 为 Field,商品数量为 Value。这比用 String 存储整个序列化对象效率高得多。


三、 列表 (List):高性能双端队列

List 是简单的字符串列表,按照插入顺序排序,支持从两端插入或弹出。

3.1 基础用法

  • 两端插入LPUSH key value(左插)、RPUSH key value(右插)

  • 两端弹出LPOP keyRPOP key

  • 范围查看LRANGE key start stop(例如 LRANGE key 0 -1 查看全部)

  • 阻塞弹出BLPOP key timeout(队列为空时阻塞,直到有数据,消息队列神技)

  • 剪裁列表LTRIM key start stop(只保留指定区间,常用于保留最新动态)

3.2 底层原理:QuickList

现在的 Redis List 统一采用 QuickList 结构。

  • 它是一个双向链表 ,但链表中的每个节点都是一个 Listpack(紧凑列表)。

  • 设计精髓:单纯的链表指针开销大(每个节点左右指针占 16 字节),且容易产生内存碎片;QuickList 结合了链表的灵活性和压缩列表的内存高效性。


四、 集合 (Set):无序且唯一的去重专家

Set 存储不重复的元素,并提供了极其强大的集合运算(交、并、差)。

4.1 基础用法

  • 添加/删除SADD key member / SREM key member

  • 判断存在SISMEMBER key member

  • 获取所有SMEMBERS key

  • 随机抽取SRANDMEMBER key count(不删除)、SPOP key(删除)

  • 集合运算SINTER k1 k2(交集)、SUNION k1 k2(并集)、SDIFF k1 k2(差集)

4.2 底层原理

  • IntSet:如果元素全是整数且数量少,Redis 使用有序数组存储,查询用二分查找。

  • HashTable:一旦有字符串或元素多,转为 Dict,Value 设为 NULL。

4.3 实战场景:共同好友

利用 SINTER 命令,可以瞬间算出两个用户的共同好友;利用 SADD 可以轻松实现抽奖逻辑。


五、 有序集合 (ZSet):Redis 的性能巅峰

ZSet 在 Set 的基础上增加了一个 score(分数),使得元素可以按分数排序。

5.1 基础用法

  • 添加元素ZADD rank 100 "Tom" 90 "Jerry"

  • 获取排名ZRANGE rank 0 -1(从小到大)、ZREVRANGE rank 0 -1(从大到小)

  • 按分数取ZRANGEBYSCORE rank 80 100

  • 增加分数ZINCRBY rank 10 "Tom"

  • 统计数量ZCARD rank

5.2 核心底层:SkipList (跳跃表)

ZSet 的核心是跳表

  • 为什么快?:跳表通过在链表上建立多级索引,实现了类似"二分查找"的效果。

  • 复杂度:插入、删除、查询的时间复杂度均为 O(log N)。

  • 对比红黑树 :跳表实现更简单,且在处理 ZRANGE 这种范围查询时,跳表通过底层的双向链表横向遍历,效率远超红黑树。


六、 高级数据结构:应对特殊场景

6.1 Bitmaps (位图)

  • 用法SETBIT key offset value / GETBIT key offset

  • 原理:利用 String 的 bit 位存储 0 或 1。

  • 场景:1 亿用户每天的签到状态,仅需 12.5MB 空间。

6.2 HyperLogLog (基数统计)

  • 用法PFADD key element / PFCOUNT key

  • 原理:基于概率算法。

  • 场景:统计网站 UV(独立访客)。无论数据量多大,只占 12KB,误差仅 0.81%。

6.3 Geospatial (地理位置)

  • 用法GEOADD city 116.40 39.90 "beijing" / GEODIST 计算距离 / GEORADIUS 附近的人。

  • 原理:将经纬度转为 ZSet 的 Score。

6.4 Streams (流)

  • 用法XADD / XREAD / XGROUP

  • 场景:Redis 5.0 后提供的专业消息队列。支持多播、消费组、ACK 确认机制,完美解决了 List 做消息队列时无法多播和数据丢失风险的问题。


七、 总结:如何选型?

需求场景 推荐结构 理由
单值缓存/计数器 String 简单、原子性高
存储对象信息 Hash 节省内存,灵活修改字段
消息队列/时间轴 List 双端操作,有序
去重/社交关系 Set 集合运算强大
排行榜/延迟任务 ZSet 自动排序,范围查询快
海量状态统计 Bitmap 极致节省空间

博主寄语:

选择 Redis 数据结构时,不仅要看它的基础用法是否满足业务功能,更要关注其底层编码和时间复杂度。在大数据量下,一个 O(N) 命令(如 HGETALL 或 SMEMBERS)可能会导致整个 Redis 实例阻塞。

希望这篇深度解析能帮你在面试和实战中游刃有余!如果你有任何疑问,欢迎在评论区留言讨论。

相关推荐
wadesir2 小时前
掌握Rust并发数据结构(从零开始构建线程安全的多线程应用)
数据结构·安全·rust
信创天地2 小时前
信创国产化数据库的厂商有哪些?分别用在哪个领域?
数据库·python·网络安全·系统架构·系统安全·运维开发
JIngJaneIL2 小时前
基于java + vue校园跑腿便利平台系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot
周胡杰2 小时前
鸿蒙preferences单多例使用,本地存储类
缓存·华为·harmonyos·preferences·鸿蒙本地存储
瀚高PG实验室2 小时前
highgo DB中数据库对象,模式,用户,权限之间的关系
数据库·瀚高数据库
飞舞花下2 小时前
MAVEN私有仓库配置-Nexus私有仓库
xml·java·maven
越来越无动于衷2 小时前
odbc链接oracle数据源
数据库·oracle
毕设源码-赖学姐3 小时前
【开题答辩全过程】以 基于SpringBoot的健身房管理系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
李迟3 小时前
Golang实践录:使用sqlx操作sqlite3数据库
数据库·golang·sqlite