Redis 的核心设计思路是:为不同的基本类型提供 “多态” 的底层实现—— 会根据数据量、数据类型的不同,自动切换更高效的底层结构(优先用内存紧凑的结构,数据量大了再切换到性能更优的结构)。
1. 字符串(String):Redis 最基础的类型
用途
存字符串(如 "hello")、数字(如 123)、二进制数据(如图片、序列化对象),是 Redis 最常用的类型。
底层实现:SDS(简单动态字符串)
Redis 没有直接用 C 语言的字符串(char*),而是自己造了个叫SDS的结构,因为 C 字符串有 3 个致命缺点:
- 获取长度要从头遍历(O (n));
- 不能存空字符(\0),无法兼容二进制数据;
- 修改时容易内存溢出。
SDS 的结构(通俗版)
+--------+--------+-----------+ | len | free | buf[] | | 已用长度 | 空闲长度 | 字符数组 | +--------+--------+-----------+比如存 "redis",SDS 的结构是:
- len=5("redis" 占 5 个字节);
- free=0(暂时没空闲);
- buf=['r','e','d','i','s','\0'](最后加 \0 是为了兼容 C 函数)。
额外优化
如果 String 存的是整数(且在 long 型范围内),Redis 不会真的用 SDS 存,而是直接把它当作整数存储(比如存 100,底层就是数字 100,不是字符串 "100"),节省内存且方便计算(比如 INCR 命令)。
2. 哈希(Hash):键值对的 “嵌套”
用途
存结构化数据,比如用户信息(user:1 → {name: 张三,age:20, city: 北京})。
底层实现:二选一(自动切换)
| 底层结构 | 适用场景 | 特点 |
|---|---|---|
| ziplist(压缩列表) | 元素少(默认 < 512 个)+ 键 / 值长度短(默认 < 64 字节) | 连续内存块,紧凑省内存,修改稍慢 |
| hashtable(字典) | 元素多 / 键值长 | 哈希表结构,增删改查 O (1),内存稍多 |
通俗解释
- ziplist:把所有键值对 “打包” 在一块连续的内存里,比如存 {name: 张三,age:20},内存里是 “name→张三→age→20” 连在一起,没有内存碎片,特别省空间。但如果要插入 / 删除中间的元素,需要重新分配整块内存,数据多了就慢。
- hashtable:Redis 的字典结构,核心是 “数组 + 链表”(解决哈希冲突)。比如数组下标 0 存 “name”,下标 1 存 “age”;如果两个键哈希后落到同一个下标,就用链表串起来。另外,Redis 的哈希表会 “渐进式 rehash”(通俗说就是 “慢慢搬家”):当数据量变化需要扩容 / 缩容时,不会一次性把所有数据移到新表,而是分多次搬,避免 Redis 卡死。
3. 列表(List):有序的字符串队列
用途
两端操作的有序列表,比如消息队列、最新评论列表。
底层实现:二选一(Redis 3.2 后核心是 quicklist)
| 底层结构 | 适用场景 | 特点 |
|---|---|---|
| ziplist(压缩列表) | 元素少 + 长度短 | 紧凑省内存 |
| quicklist(快速列表) | 元素多 / 长度长 | 兼顾内存和性能(Redis 3.2 后默认) |
通俗解释
- 早期 Redis 用的是 linkedlist(双向链表),但链表的问题是 “内存碎片多”(每个节点都要存指针);而 ziplist 的问题是 “修改慢”。
- quicklist:相当于 “把多个 ziplist 用双向链表串起来”—— 既解决了链表的内存碎片问题,又解决了单个 ziplist 修改慢的问题。比如一个 List 有 1000 个元素,Redis 会把它拆成 10 个 ziplist(每个 100 个元素),用链表连接这 10 个 ziplist,增删元素时只动其中一个 ziplist,效率高还省内存。
4. 集合(Set):无序、唯一的字符串集合
用途
去重、随机取值、交集 / 并集 / 差集(比如抽奖、好友共同关注)。
底层实现:二选一(自动切换)
| 底层结构 | 适用场景 | 特点 |
|---|---|---|
| intset(整数集合) | 所有元素都是整数 + 个数少(默认 < 512 个) | 紧凑的整数数组,查询快(二分查找) |
| hashtable(字典) | 有非整数元素 / 元素个数多 | 利用哈希表的唯一性,增删查 O (1) |
通俗解释
- intset:比如存 {1,3,5},底层是一个升序排列的整数数组 [1,3,5],查询时用二分查找(O (logn)),特别省内存。如果往里面加一个字符串(比如 "abc"),或者元素超过 512 个,就会自动转成 hashtable。
- hashtable:存的时候 “键是集合元素,值是 NULL”(比如键 1→NULL,键 3→NULL),利用哈希表 “键唯一” 的特性保证 Set 的唯一性,增删查都是 O (1)。
5. 有序集合(ZSet):带分数的有序集合
用途
排行榜、带权重的队列(比如用户积分排名、热门商品排序)。
底层实现:二选一(自动切换)
| 底层结构 | 适用场景 | 特点 |
|---|---|---|
| ziplist(压缩列表) | 元素少(默认 < 128 个)+ 元素长度短(默认 < 64 字节) | 紧凑省内存,按分数升序排列 |
| skiplist+hashtable | 元素多 / 长度长 | 兼顾排序和快速查询 |
通俗解释
- ziplist:元素按分数升序排列,每个元素先存 “成员” 再存 “分数”(比如 “张三→95,李四→88”),紧凑存储,适合小数据量的 ZSet。
- skiplist+hashtable(核心组合):
- skiplist(跳表):可以理解成 “多层楼梯”—— 底层是完整的有序链表,上层是少量 “捷径节点”。比如查分数 95 的元素,先从最高层找,快速定位到底层的大致位置,再遍历,平均复杂度 O (logn),支持快速的范围查询(比如查分数 80-100 的元素)和排序。
- hashtable:映射 “成员→分数”,比如查 “张三” 的分数,直接通过哈希表 O (1) 拿到,不用遍历跳表。两者结合,既保证了按分数排序的效率,又保证了查询单个元素分数的效率。
总结
- Redis 的基本类型都采用 “按需切换” 的底层实现:小数据量用紧凑结构(ziplist/intset)省内存,大数据量用高性能结构(hashtable/quicklist/skiplist)保速度。
- 核心优化思路:平衡 “内存占用” 和 “操作性能”,避免单一结构的缺点(比如链表省性能但费内存,压缩列表省内存但改得慢)。
- 关键底层结构:SDS(String)、ziplist(小数据通用)、quicklist(List)、intset(Set 整数场景)、hashtable(Hash/Set/ZSet 通用)、skiplist(ZSet 核心)。