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 key、INCRBY 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 key、RPOP 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 实例阻塞。
希望这篇深度解析能帮你在面试和实战中游刃有余!如果你有任何疑问,欢迎在评论区留言讨论。