很多人第一次接触 Redis 时,都会把它简单理解成一个 Key-Value 缓存工具 。
但随着使用的深入就会发现,Redis 真正强大的地方,其实是 丰富的数据结构设计。
Redis 不仅提供了多种数据结构,而且针对每一种结构都设计了不同的底层实现,使得很多业务场景可以非常高效地完成。
今天我们主要从三个角度整理 Redis 的核心数据结构:
-
Redis 对外提供的数据结构类型
-
每种数据结构的底层实现方式
-
实际开发中常见的应用场景
最后也会总结一些面试中经常会被问到的问题,算是对 Redis 数据结构的一次系统复习。
一、为什么 Redis 会设计这么多数据结构?
刚开始学习 Redis 的时候,我其实一直有个疑问:
Redis 不就是一个缓存吗?
直接存 Key-Value 不就够了吗?
后来在看一些实际业务系统的时候才慢慢发现,很多需求其实并不是简单的 KV 存储。
比如这些场景:
-
记录文章点赞用户
-
实现排行榜
-
存储用户对象信息
-
实现任务队列
-
统计访问次数
-
做用户标签系统
如果全部只用字符串来实现,其实会非常麻烦。
Redis 的思路其实很简单:
把常见的数据结构直接内置进去,让开发者可以开箱即用。
所以 Redis 更像是一个:
内存中的高性能数据结构服务器。
很多业务逻辑,如果用数据库实现会比较复杂,但用 Redis 可能几条命令就可以完成。
二、Redis 的主要数据结构类型
Redis 最核心的五种数据结构:
| 类型 | 描述 |
|---|---|
| String | 字符串 |
| List | 列表 |
| Hash | 哈希 |
| Set | 集合 |
| Sorted Set(ZSet) | 有序集合 |
除此之外 Redis 还提供了一些扩展结构:
-
Bitmap
-
HyperLogLog
-
Geo
-
Stream
不过在绝大多数业务系统中,前五种已经覆盖了绝大多数需求。
下面我们逐个来看。
三、String(字符串)
String 是 Redis 中 最基础、使用频率最高 的数据结构。
很多时候我们说 Redis 是 Key-Value 存储,其实大多数 Key 的 Value 就是 String。
Redis 的 String 最大可以存 512MB 的数据。
它不仅可以存字符串,还可以存:
-
数字
-
JSON
-
图片二进制数据
-
序列化对象
常见操作:
SET key value
GET key
INCR key
DECR key
1.String 的底层实现
Redis 的 String 底层使用的是 SDS(Simple Dynamic String)。
SDS 相比传统 C 语言字符串做了很多优化。
(1)O(1) 获取字符串长度
在 C 语言中,如果想获取字符串长度,需要遍历整个字符串:
strlen()
时间复杂度是 O(N)。
而 SDS 在结构体中会直接记录字符串长度,因此获取长度只需要:O(1)。
(2)自动扩容避免缓冲区溢出
在传统字符串操作中,如果拼接字符串时空间不够,很容易发生 buffer overflow。
SDS 在修改字符串时会自动扩容,因此不会出现这种问题。
(3)支持二进制安全
Redis 的 String 可以存储:
-
图片
-
protobuf 数据
-
Java 序列化对象
因为它并不会像 C 字符串那样依赖 \0 作为结束符。
2.String 常见使用场景
(1)计数器
String 最常见的用途之一其实就是 计数器。
比如一些非常常见的数据统计:
-
文章阅读量
-
帖子点赞数
-
商品库存
-
网站访问次数
如果这些操作全部通过数据库实现,每次都需要:
select
update
在高并发系统中,这种操作会给数据库带来很大压力。
而 Redis 可以直接这样做:
INCR article:1001:view
每访问一次文章,就让这个 key 自增一次。
Redis 的操作是 原子性的,所以即使在高并发情况下也不会出现数据问题。
(2)缓存对象
另一个非常常见的用途是 缓存对象。
例如用户信息。
传统流程可能是:
数据库 -> 查询用户信息
如果接口访问频率很高,每次都查数据库其实挺浪费的。
很多系统会直接把用户信息缓存到 Redis:
user:1001 -> JSON
访问流程变成:
先查 Redis -> 没有再查数据库
这样数据库压力会小很多。
(3)分布式锁
Redis String 也经常被用来实现 分布式锁。
经典写法是:
SET lock_key value NX PX 30000
含义:
-
NX:只有 key 不存在时才设置
-
PX:设置过期时间
这个模式在很多分布式系统中都被广泛使用。
3.面试常见问题
Redis String 最大可以存多大?
512MB。
Redis String 底层结构是什么?
SDS(简单动态字符串)。
SDS 相比 C 字符串有哪些优势?
-
O(1) 获取长度
-
防止缓冲区溢出
-
支持二进制安全
四、List(列表)
List 可以理解为一个 有序字符串列表。
它类似一个 双端队列,支持从两端插入和删除。
常见命令:
LPUSH
RPUSH
LPOP
RPOP
LRANGE
1.List 的底层实现
在较新的 Redis 版本中,List 的底层结构是:
quicklist
quicklist 可以理解为:
双向链表 + 压缩列表
每个链表节点内部是一个压缩列表。
这种设计的好处是:
-
链表保证插入删除效率
-
压缩列表节省内存
在 性能和空间之间做了平衡。
2.List 常见使用场景
(1)简单消息队列
很多小系统会直接用 Redis List 来实现消息队列。
生产者:
LPUSH queue msg
消费者:
RPOP queue
这样就形成了一个简单的 FIFO 队列。
不过如果是大型系统,一般还是会使用专业消息队列,例如:
-
Kafka
-
RabbitMQ
因为它们支持:
-
消息确认
-
重试机制
-
持久化
(2)任务队列
List 也很适合用来做任务队列,例如:
-
异步任务处理
-
日志处理
-
批量数据处理
生产者把任务写入队列,消费者不断消费即可。
五、Hash(哈希)
Hash 很适合用来存储 对象结构数据。
例如用户信息:
user:1001
字段:
name -> 张三
age -> 20
city -> 西安
1.常见操作
HSET
HGET
HMGET
HGETALL
2.Hash 的底层实现
Redis Hash 的底层结构有两种:
ziplist
hashtable
当数据量比较小的时候:
使用 ziplist(压缩列表)
当数据量变大之后:
自动转换为 hashtable
这种设计主要是为了:
在节省内存和保证查询效率之间取得平衡。
3.Hash 常见使用场景
(1)存储对象
例如用户对象:
user:1001
字段:
name
age
email
如果只修改一个字段,只需要更新对应 field,而不需要重新存整个对象。
(2)购物车
购物车也很适合用 Hash 实现。
例如:
cart:user1001
字段:
productId -> count
六、Set(集合)
Set 是一个 无序集合。
特点:
-
元素不允许重复
-
支持集合运算
常见命令:
SADD
SMEMBERS
SISMEMBER
1.Set 的底层实现
Set 的底层结构可能是:
intset
hashtable
如果集合中的元素都是整数,并且数量较少:
使用 intset。
否则使用:
hashtable。
2.Set 常见使用场景
(1)标签系统
例如:
user:1001:tags
内容可能是:
java
redis
backend
(2)好友关系
例如:
user:1001:friends
(3)共同好友
Redis 可以直接做集合运算:
SINTER
可以快速计算两个集合的交集。
七、Sorted Set(ZSet)
ZSet 可以理解为 带分数的集合。
每个元素除了 value,还会有一个 score。
结构类似:
member + score
1.ZSet 的底层实现
ZSet 使用两种结构组合实现:
skiplist + hashtable
原因是:
-
hashtable:负责快速查找元素
-
skiplist:负责维护有序结构
跳表(skiplist)是一种 多层链表结构。
时间复杂度大约是:
O(logN)
2.ZSet 常见使用场景
(1)排行榜
这是 ZSet 最经典的使用场景。
例如:
-
游戏排行榜
-
点赞排行榜
-
热度榜
score 可以直接表示排名分数。
(2)延迟队列
可以把时间戳作为 score。
定期扫描最小 score,就可以实现延迟任务。
(3)限流
一些滑动窗口限流算法也会使用 ZSet 实现。
八、总结
Redis 的核心数据结构可以整理成一张表:
| 类型 | 底层结构 | 常见用途 |
|---|---|---|
| String | SDS | 缓存、计数器、分布式锁 |
| List | quicklist | 队列、任务列表 |
| Hash | ziplist / hashtable | 对象存储 |
| Set | intset / hashtable | 标签、好友关系 |
| ZSet | skiplist + hashtable | 排行榜 |
九、面试高频问题
Redis 有哪些数据结构?
String、List、Hash、Set、ZSet。
Redis String 底层是什么?
SDS。
ZSet 为什么使用跳表?
因为:
-
实现简单
-
插入删除效率高
-
支持范围查询
十、最后
很多人刚开始接触 Redis 时,会把它当作一个简单的缓存工具。
但真正深入之后会发现:
Redis 更像是一套高性能的数据结构工具箱。
很多业务逻辑,如果直接用数据库实现可能会比较复杂,但使用 Redis 的数据结构往往几条命令就能实现。
理解 Redis 的数据结构,其实也是理解 Redis 为什么这么强大的一个过程。
