Redis 中跳表是有序集合(ZSET)的核心底层实现之一(还有一个是压缩列表 ziplist,满足条件时触发转换),其设计目标是在有序结构中平衡查找、插入、删除的效率,以 "多层链表 + 随机层高" 的方式规避传统有序链表 O (n) 的查询短板,同时保持实现简单、内存可控的特性。Redis 跳表并非完全照搬经典跳表模型,而是针对缓存场景做了定制化优化,核心遵循 "分层索引 + 有序遍历 + 随机层高" 三大原则,相较于红黑树等平衡树结构,跳表在插入删除时无需复杂的旋转调整,更适配 Redis 单线程、高性能的执行特点。
(1)Redis 跳表的核心结构与分层设计
Redis 跳表由 "跳表节点" 和 "跳表核心结构" 两部分组成,整体通过多层链表嵌套实现分层索引:
跳表节点:是跳表的最小单元,包含四大核心属性:
ele:存储元素的成员,以字符串形式存储,保证唯一性;
score:存储元素的分值(double 类型),跳表按 score 从小到大排序,score 相同则按 member 字典序排序;
backward:指向当前节点的前一个节点(反向指针),仅底层链表(Level 0)有该指针,用于反向遍历(如 ZREVRANGE 命令);
level:节点的层级数组,每个层级包含两个关键属性:
forward:指向当前层级下的下一个节点(正向指针),不同层级的 forward 指针构成不同层级的索引;
span:记录当前节点到 forward 指向节点的 "跨度"(即两个节点之间的元素个数),用于快速计算元素的排名(如 ZRANK 命令)。
zskiplist:跳表核心结构,用于管理跳表的全局状态,包含五大属性:
header:指向跳表的头节点(虚拟节点,不存储实际数据),头节点的层级数等于跳表的最大层级;
tail:指向跳表的尾节点,优化尾节点的快速访问;
length:记录跳表中实际存储的节点数量(不含头节点),用于快速返回有序集合的元素个数(ZCARD 命令);
maxLevel:跳表允许的最大层级(Redis 默认为 32 层),限制跳表的内存占用上限。
(2)Redis 跳表的查询、插入、删除均围绕 "分层遍历 + 路径记录" 展开,不同操作的逻辑如下:
- 查询操作(如 ZSCORE、ZRANK)
跳表查询的核心是 "从高层到低层逐层降维,缩小遍历范围",具体流程:(1)从跳表头节点的最高层级开始,通过 forward 指针遍历,若当前节点的 score 小于目标 score,或 score 相等但 member 字典序更小,则继续向后移动;(2)若当前层级的 forward 节点超出目标范围(score 更大),则降低一层层级,重复步骤(1);(3)直到降至底层(Level 0),此时若找到与目标 score + member 匹配的节点,则返回结果;若未找到,则判定元素不存在。示例流程:
头节点 Level 32 → 检查 forward 节点 score → 大于目标,降至 Level 31,Level 31 forward 节点 score 小于目标 → 移动至该节点......直至逐层降维至 Level 0 → 遍历找到目标节点。
- 插入操作(如 ZADD)
插入是跳表最核心的操作,需兼顾 "层级随机生成" 和 "指针调整",流程如下:
路径记录:从最高层级开始遍历,记录每一层级中 "最后一个 score 小于目标值" 的节点(update 数组),同时记录目标节点的排名(通过 span 累加);
随机层高:调用 Redis 内置的随机数算法(基于幂次分布)生成节点的层级(level),规则为:层级越高,生成概率越低,且最大不超过 maxLevel;
节点创建:新建 zskiplistNode 节点,设置 score、ele 属性,初始化 level 数组;
指针调整:遍历生成的层级,将 update 数组中对应层级节点的 forward 指针指向新节点,新节点的 forward 指针指向原 update 节点的 forward 节点,同时更新各层级的 span 值;
反向指针与全局状态更新:若新节点不是第一个节点,设置其 backward 指针为底层(Level 0)的前一个节点;更新跳表的 level(若新节点层级超过当前最大层级)和 length 属性。
- 删除操作
删除操作是插入的逆过程,核心是 "找到节点并调整指针",流程如下:(1)路径记录:与查询逻辑一致,遍历各层级,记录每一层级中 "最后一个 score 小于目标值" 的节点(update 数组),同时定位到待删除节点;(2)指针调整:遍历待删除节点的所有层级,将 update 数组中对应层级节点的 forward 指针指向待删除节点的 forward 节点,同时更新各层级的 span 值;(3)反向指针与全局状态更新:若待删除节点有后继节点,将后继节点的 backward 指针指向待删除节点的 backward 指针;若待删除节点的层级等于跳表的 level,则更新跳表的 level 为当前最大有效层级;(4)内存释放:释放待删除节点的内存,跳表 length 减 1。
相较于经典跳表,Redis 跳表针对缓存场景做了三大关键优化:
span 跨度属性:经典跳表仅记录指针,Redis 新增 span 属性,可快速计算元素的排名(ZRANK/ZREVRANK),无需遍历底层链表,将排名计算的时间复杂度从 O (n) 降至 O (log n);
反向指针:仅底层链表维护反向指针,支持反向遍历操作(如 ZREVRANGE),兼顾正向查询效率与反向遍历需求;
严格的排序规则:score 相同时按 member 字典序排序,解决了经典跳表 "同分值元素无序" 的问题,适配有序集合的业务场景;
内存限制:通过 maxLevel(默认 32)限制最大层级,避免极端情况下层级过高导致的内存浪费,同时幂次分布的随机层高算法保证了平均层级约为 log n(n 为节点数)。
Redis 跳表的适用于以下几个场景:
1.有序集合(ZSET)的底层实现;
2.需要高效完成 "有序遍历、按分值查询、按排名查询、插入删除" 的场景(如排行榜、延时队列、范围查询);
其性能特点主要有:
1.查找、插入、删除的平均时间复杂度为 O (log n),最坏为 O (n)(极端随机层高导致层级为 1),但实际场景中概率极低;
2.空间复杂度为 O (n),相较于红黑树,虽然跳表的额外内存开销(层级指针)略高,但实现更简单,单线程下执行效率更高;
3.支持范围查询(如 ZRANGE、ZREVRANGE),效率优于红黑树。