🧠 一、ZSet 是什么?
ZSet(Sorted Set)= 有序集合
-
元素 不重复(唯一 key),但可以有相同的分值(score)。
-
元素按照 score 从小到大排序。
-
支持按 score 范围 / 排名区间 查询,非常高效。
语法示例:
🧩 二、ZSet 的底层原理(重点考点)
ZSet 是 Redis 内部比较复杂的结构之一 —— 实际上是由 两种数据结构组合 实现的:
| 组件 | 作用 | 复杂度 |
|---|---|---|
| dict(哈希表) | 存放成员到分值的映射(member → score) | O(1) |
| skiplist(跳表) | 存放分值到成员的排序关系(score → member) | O(logN) |
Redis 会同时维护这两个结构以保证:
通过成员快速查找分值(dict)
通过分值范围快速排序和遍历(skiplist)
✅ 跳表(skiplist)原理简述
-
跳表是一种多层链表结构,通过“多级索引”加速查找,类似于二分查找。
-
时间复杂度:O(logN)
-
插入、删除、范围扫描都非常高效。
跳表示意:
⚙️ 三、常用命令与时间复杂度
| 操作 | 命令 | 时间复杂度 | 说明 |
|---|---|---|---|
| 插入/更新 | ZADD key score member |
O(logN) | 若成员存在则更新分值 |
| 删除 | ZREM key member |
O(logN) | 同时从 dict 与 skiplist 删除 |
| 按分值范围查 | ZRANGEBYSCORE key min max |
O(logN + M) | M 为返回数量 |
| 按排名范围查 | ZRANGE key start stop |
O(logN + M) | 支持逆序 |
| 查成员分值 | ZSCORE key member |
O(1) | 从 dict 查 |
| 查排名 | ZRANK key member |
O(logN) | 从 skiplist 查 |
| 增加分值 | ZINCRBY key increment member |
O(logN) | 自动更新位置 |
| 删除区间 | ZREMRANGEBYSCORE key min max |
O(logN + M) | 范围删除 |
| 集合并集 | ZUNIONSTORE / ZINTERSTORE |
O(N logN) | 分值加权合并 |
🚀 四、使用场景(高频面试题)
✅ 1. 排行榜系统(最经典)
场景: 游戏积分榜、电商热度榜、内容点赞排行等
设计:
优势:
-
实时更新分值。
-
可快速取前 N 名、后 N 名、某区间段。
✅ 2. 延迟队列(基于 score 存时间戳)
场景: 定时任务、重试队列、延迟通知等
思路:
-
score = 任务执行时间戳 -
定时轮询当前时间 ≤ score 的任务
优点:
-
不依赖外部消息系统
-
可控制延迟精度(秒级)
✅ 3. 热点数据 Top-N
场景: 内容推荐、最热文章、热门搜索词
思路:
-
每次访问调用
ZINCRBY累加热度 -
定时取前 N 名显示
✅ 4. 权重队列 / 任务优先级
场景: 风控任务、风控规则优先级处理
-
score = 权重(越高优先级越高)
-
用
ZPOPMAX每次取最高优先级任务处理
✅ 5. 用户活跃度 / 打分系统
-
用户行为打分、信用评分、综合指标
-
可存历史分数,定期做 decay(衰减)
💡 五、ZSet 的优缺点
| 优势 | 说明 |
|---|---|
| 有序、可快速范围查询 | 按分值排序,支持分页、区间 |
| 插入、删除、查找效率高 | 跳表 O(logN) |
| 支持原子操作 | 不需要事务就能安全并发修改 |
| 数据量大也能稳定性能 | 比 list 链表结构高效 |
| 缺点 | 说明 |
|---|---|
| 内存占用较高 | 维护 skiplist + dict |
| 无法去重按值排序(只能按 score) | 需自行定义 score 规则 |
| 不支持按 value 排序 | 必须以 score 为序 |
🧮 六、与其他数据结构对比(面试延伸题)
| 类型 | 是否排序 | 是否唯一 | 查询复杂度 | 典型场景 |
|---|---|---|---|---|
| String | 否 | 是(键唯一) | O(1) | KV 存储 |
| List | 按插入顺序 | 可重复 | O(N) | 消息队列 |
| Set | 无序 | 唯一 | O(1) | 去重集合 |
| ZSet | ✅ 有序 | ✅ 唯一 | O(logN) | 排行榜、延迟队列、TopN |
🧠 七、面试高频问法(及标准答法)
① ZSet 底层实现是什么?
跳表(skiplist)+ 哈希表(dict)。
跳表用于按分值排序,哈希表用于 O(1) 查找分值。
② 插入和查询的复杂度是多少?
O(logN) 插入,O(1) 查找,O(logN+M) 范围查。
③ 为什么不用红黑树?
Redis 选择跳表主要因为:
插入/删除更简单;
实现更直观;
更适合范围遍历;
占用内存少,性能稳定。
④ 怎么实现延迟队列?
score 存未来执行时间戳,定期
ZRANGEBYSCORE拉取可执行任务。
⑤ 怎么实现排行榜 TopN?
score 存积分,
ZREVRANGE取前 N 名。
⑥ 如果有 1 亿个用户,取前 100 名怎么优化?
用 ZSet 存局部排行榜 + 分片聚合;
或者采用 Top-K 算法(如 Min-Heap)+ 定期同步 Redis。
🧰 八、实战优化建议
-
⚙️ pipeline 批量写入:避免多次网络往返。
-
🧩 热榜分层缓存:TopN 存本地内存,底层数据异步更新。
-
🔄 分布式排行榜:按业务分片多个 ZSet,合并时在应用层聚合。
-
⏳ 数据过期策略:结合 TTL 或周期性清理。
-
💾 持久化:RDB/AOF 支持 ZSet,重启不丢失。
✅ 总结记忆卡
| 模块 | 内容 |
|---|---|
| 底层结构 | dict + skiplist |
| 复杂度 | 插入/删除 O(logN),查找 O(1) |
| 常用命令 | ZADD, ZRANGE, ZREVRANK, ZINCRBY, ZREMRANGEBYSCORE |
| 典型场景 | 排行榜、延迟队列、TopN、权重队列 |
| 优势 | 有序、高性能、原子操作 |
| 缺点 | 内存占用较高,按 value 排序不支持 |
