700 字
4 分钟
Redis 的 ZSet 为什么使用跳表,而不是平衡树?
  1. 1Redis 系列导读:知识地图、必会主线与复习顺序
  2. 2Redis 中常见的数据类型有哪些?
  3. 3Redis 通常应用于哪些场景?
  4. 4Redis为什么这么快?
  5. 5Redis 常用的过期时间、删除 Key 命令有哪些?
  6. 6Redis 的过期 Key 是如何被删除的?
  7. 7Redis 内存满了怎么办?有哪些淘汰策略?
  8. 8缓存穿透、击穿、雪崩的区别与解决方案
  9. 9Redis 如何保证缓存与数据库的数据一致性?
  10. 10Redis 的持久化机制有哪些?
  11. 11Redis 主从复制的完整流程是什么?
  12. 12Redis 的哨兵机制是什么?
  13. 13Redis Cluster 模式与 Sentinel 模式的区别是什么?
  14. 14Redis 支持事务吗?如何实现?
  15. 15Redis 中如何实现分布式锁?
  16. 16Redis 的 Lua 脚本功能是什么?如何使用?
  17. 17如何解决 Redis 中的热点 Key 问题?
  18. 18Redis BigKey 有什么危害?如何发现和治理?
  19. 19为什么 Redis 设计为单线程?6.0 版本为何引入多线程?
  20. 20Redis 的 LRU 和 LFU 有什么区别?
  21. 21Redis 数据类型和底层编码有什么区别?
  22. 22Redis 中跳表的实现原理是什么?
  23. 23Redis 的 ZSet 为什么使用跳表,而不是平衡树?本文
  24. 24Redis 的 RDB 快照如何生成?为什么会有写时复制?
  25. 25Redis 的 AOF 刷盘和重写有什么区别?
  26. 26Redis 断线后如何通过 PSYNC 部分重同步?
  27. 27Redis 主从切换为什么会丢数据?WAIT 能解决吗?
  28. 28Redis 集群会出现脑裂问题吗?
  29. 29Redisson 看门狗(watch dog)机制了解吗?
  30. 30Redis Cluster:哈希槽、Gossip 与故障转移
  31. 31Redis Pipeline 为什么能提高吞吐?能保证原子性吗?
  32. 32Redis 的 SCAN 和 KEYS 有什么区别?
  33. 33Redis 变慢时如何排查?SLOWLOG 能看到所有耗时吗?
  34. 34Redis 和 MySQL 有什么区别?
  35. 35Redis 为什么使用 SDS,而不是 C 字符串?
  36. 36Redis 的哈希表如何处理冲突和渐进式 rehash?
  37. 37Redis Cluster 的 MOVED 和 ASK 重定向有什么区别?
  38. 38Redis Cluster 为什么会报 CROSSSLOT?Hash Tag 怎么用?
  39. 39Redis 如何实现限流?固定窗口和滑动窗口怎么选?
  40. 40Redis Stream 如何保证消息消费?ACK 和 PEL 是什么?
  41. 41Redis 删除数据后为什么内存不下降?如何理解内存碎片?

Redis 较大的 ZSet 通常用字典与跳表配合。字典支持按成员查分数,跳表支持按分数有序访问与排名。选择跳表可以获得平均 O(log N) 的插入、删除和查找,并且较容易支持顺序遍历、反向遍历和范围查询;不能据此说平衡树无法实现这些操作。

一个排行榜有两种访问需求#

查询“用户 u 的分数是多少”适合成员到分数的哈希映射;查询“分数 80 到 100 的用户”和“前十名”则需要有序结构。如果只用字典,每次排序成本高;如果只有跳表,按成员定位时还需要相应查找路径,因此二者互补。

跳表底层连接所有元素,上层通过随机高度形成加速索引。查找时从高层向右,超过目标前向下,最终定位底层节点。Redis 按 score 排序,同分时按成员字典序形成确定顺序。节点的 backward 指针支持反向遍历,各层 span 记录跨越的节点数,用于排名计算。

与平衡树比较#

平衡树能提供有序检索和范围遍历,并通常有严格的最坏复杂度保证;跳表以随机化取得平均复杂度,实现不需要复杂的旋转与平衡规则,范围结果可沿底层链表输出。两者都需要内存元数据,不能脱离具体实现断言跳表永远更省内存或更快。

小 ZSet 可采用 listpack,避免字典和多层指针的开销。回答“ZSet 底层是什么”时应先说明版本和规模,不能说所有 ZSet 无条件使用跳表。

常见追问#

范围查询复杂度为什么不是单独 O(log N)?#

定位起点平均 O(log N),输出 M 个结果还需要 O(M);典型范围查询整体为 O(log N + M),返回大量结果也会带来网络开销。

score 相同会覆盖成员吗?#

不会因为同分覆盖不同成员。ZSet 的成员唯一,同一成员再次 ZADD 通常更新分数,不同成员可以拥有同一分数。

ZSet 排名能否直接保存超大整数?#

score 使用双精度浮点表示,整数精度有边界;超出可精确表示范围时需要重新设计分值与排序规则,避免把所有 64 位整数都当成精确分数。

为什么删除时要维护字典和跳表?#

它们表示同一逻辑集合的两条索引路径。只删除一边会造成查分数与有序查询结果不一致,Redis 在命令执行中维护二者关系。

Redis 的 ZSet 为什么使用跳表,而不是平衡树?
https://blog.xqcherry.top/posts/interview/redis/redis-zset-skiplist-vs-tree/
作者
xqcherry
发布于
2026-10-12
许可协议
CC BY-NC-SA 4.0