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),效率优于红黑树。

相关推荐
玄同7658 小时前
SQLite + LLM:大模型应用落地的轻量级数据存储方案
jvm·数据库·人工智能·python·语言模型·sqlite·知识图谱
吾日三省吾码8 小时前
别只会“加索引”了!这 3 个 PostgreSQL 反常识优化,能把性能和成本一起打下来
数据库·postgresql
chian-ocean8 小时前
百万级图文检索实战:`ops-transformer` + 向量数据库构建语义搜索引擎
数据库·搜索引擎·transformer
小Tomkk9 小时前
数据库 变更和版本控制管理工具 --Bytebase 安装部署(linux 安装篇)
linux·运维·数据库·ci/cd·bytebase
qq_12498707539 小时前
基于JavaWeb的大学生房屋租赁系统(源码+论文+部署+安装)
java·数据库·人工智能·spring boot·计算机视觉·毕业设计·计算机毕业设计
倒流时光三十年10 小时前
SpringBoot 数据库同步 Elasticsearch 性能优化
数据库·spring boot·elasticsearch
forestsea10 小时前
深入理解Redisson RLocalCachedMap:本地缓存过期策略全解析
redis·缓存·redisson
码农小卡拉10 小时前
深入解析Spring Boot文件加载顺序与加载方式
java·数据库·spring boot
佛祖让我来巡山10 小时前
Redis 为什么这么快?——「极速快递站」的故事
redis·redis为什么快?
怣5010 小时前
MySQL多表连接:全外连接、交叉连接与结果集合并详解
数据库·sql