Redis复习------跳表

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 跳表的查询、插入、删除均围绕 "分层遍历 + 路径记录" 展开,不同操作的逻辑如下:

  1. 查询操作(如 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 → 遍历找到目标节点。

  1. 插入操作(如 ZADD)

插入是跳表最核心的操作,需兼顾 "层级随机生成" 和 "指针调整",流程如下:

路径记录:从最高层级开始遍历,记录每一层级中 "最后一个 score 小于目标值" 的节点(update 数组),同时记录目标节点的排名(通过 span 累加);

随机层高:调用 Redis 内置的随机数算法(基于幂次分布)生成节点的层级(level),规则为:层级越高,生成概率越低,且最大不超过 maxLevel;

节点创建:新建 zskiplistNode 节点,设置 score、ele 属性,初始化 level 数组;

指针调整:遍历生成的层级,将 update 数组中对应层级节点的 forward 指针指向新节点,新节点的 forward 指针指向原 update 节点的 forward 节点,同时更新各层级的 span 值;

反向指针与全局状态更新:若新节点不是第一个节点,设置其 backward 指针为底层(Level 0)的前一个节点;更新跳表的 level(若新节点层级超过当前最大层级)和 length 属性。

  1. 删除操作

删除操作是插入的逆过程,核心是 "找到节点并调整指针",流程如下:(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),效率优于红黑树。

相关推荐
✿ ༺ ོIT技术༻2 小时前
Redis:Redis背景、特性、客户端及单线程模型
数据库·redis·缓存
程序员阿鹏2 小时前
如何保证写入Redis的数据不重复
java·开发语言·数据结构·数据库·redis·缓存
廋到被风吹走2 小时前
【数据库】【Oracle】事务与约束详解
数据库·oracle
天然玩家2 小时前
【数据库知识】聚簇索引&二级索引
数据库·聚簇索引·回表·二级索引
斯普信专业组2 小时前
Redis Cluster 集群化部署全流程指南:从源码编译到容器化
数据库·redis·缓存
任子菲阳3 小时前
学JavaWeb第五天——MySQL
数据库·mysql
ZePingPingZe3 小时前
MySQL查看事务与锁
数据库·mysql
TDengine (老段)3 小时前
从“被动养护”到“主动预警”,TDengine IDMP 让智慧桥梁靠数据“说话”
大数据·数据库·人工智能·物联网·时序数据库·tdengine·涛思数据
白日做梦Q3 小时前
【MySQL】9.吃透关键SQL语法:从正则表达式、窗口函数、条件函数到结果集合并的实战拆解
数据库·sql·mysql·正则表达式