Redis Zset(有序集合)底层实现原理解析
在 Redis 中,Zset(Sorted Set,有序集合)是一种结合了哈希表和跳表的数据结构,它不仅支持快速的插入、删除和查找操作,还能高效地实现范围查询和排序操作。本文将详细介绍 Zset 的底层实现原理。
1. Zset 结构概述
Zset 主要由两个数据结构组成:
- 跳表(skiplist):用于高效的范围查询和排序。
- 字典(hash table):用于快速查找特定元素的分数。
这两个数据结构相互配合,使得 Zset 既能快速查找元素分数,又能高效地按分数排序访问数据。
2. 跳表(Skiplist)—— 高效的排序结构
跳表是一种 多层级链表结构,用于有序数据存储,支持 O(log N) 级别的插入、删除、查找 操作。
2.1 跳表的结构
跳表的基本结构如下:
- 多个层级(levels),每一层都是一个升序链表。
- 底层(level 1)包含所有元素,越高层的链表元素越少。
- 元素被随机分配到不同层,插入时按一定概率决定元素的层数(Redis 采用 1/4 的概率)。
2.2 跳表的查找过程
- 从最高层开始,查找比目标值小但最接近的元素。
- 若当前层无更大元素,则下降到下一层继续查找。
- 直到底层链表找到目标值或确认目标值不存在。
2.3 跳表的插入过程
- 先在跳表中查找插入位置。
- 通过随机算法决定新元素的层数。
- 在对应层级插入新元素,并维护链表指针。
2.4 跳表的删除过程
- 先查找目标元素。
- 遍历所有包含该元素的层级,移除其节点。
- 若删除后某些层变为空,则删除该层。
3. 哈希表(Hash Table)—— 快速查找分数
除了跳表,Redis 还使用 哈希表(dict) 存储 Zset 元素与分数的映射关系,使得 通过成员名快速查询分数 变得高效(O(1) 复杂度)。
3.1 哈希表的作用
- 通过 key(成员名) 快速找到 score(分数)。
- 通过 成员名获取分数,然后去跳表中查找排名或范围。
3.2 为什么需要哈希表?
- 跳表按分数排序,但无法快速按 成员名 查找分数。
- 哈希表提供 O(1) 级别的 成员名 -> 分数 查询能力。
4. Zset 如何存储数据?
在 Redis 代码实现中,Zset 主要由 zset 结构体 组织,底层存储分两种方式:
- ziplist(压缩列表):元素少时使用
- skiplist(跳表)+ dict(哈希表):元素多时使用
4.1 Ziplist 模式(小数据量时)
- 适用于 成员数量 < 128 且 每个成员+分数 < 64 字节。
- 使用 连续内存存储,节省空间,但插入、查找性能较差。
4.2 Skiplist + Dict 模式(大数据量时)
- 适用于 大规模数据,当 Zset 变大时自动转换为此结构。
- 跳表 用于 按分数排序,字典 用于 O(1) 级查找。
5. Zset 典型操作及复杂度
操作 | 命令 | 时间复杂度 |
---|---|---|
添加元素 | ZADD key score member | O(log N) |
删除元素 | ZREM key member | O(log N) |
获取元素排名 | ZRANK key member | O(log N) |
获取分数范围元素 | ZRANGEBYSCORE key min max | O(log N + M) |
获取指定排名元素 | ZRANGE key start stop | O(log N + M) |
6. 总结
- 小数据量时,Zset 使用 ziplist(压缩列表) 存储,节省空间但性能较低。
- 大数据量时,Zset 使用 skiplist + dict 组合,保证查询效率。
- 跳表用于排序操作,提供 O(log N) 级效率。
- 哈希表用于成员分数查找,提供 O(1) 级效率。
- Zset 适用于排行榜、权重排序、时间序列等应用场景。