Redis底层数据结构之跳表(SkipList)

news2025/6/5 19:25:01

        SkipList是Redis有序结合ZSet底层的数据结构,也是ZSet的灵魂所在。与之相应的,Redis还有一个无序集合Set,这两个在底层的实现是不一样的。

标准的SkipList:

        跳表的本质是一个链表。链表这种结构虽然简单清晰,但是在查询时的效率比较低(O(n)),而在有序集合的场景中,我们希望运用有序这个前提条件,来提高我们增删查的效率。在算法中,有一种针对有序集合的搜索算法叫二分查找,Redis中的ZSet底层在设计时也参考了二分查找的思想,引入了跳表。跳表在链表的基础上,引入了多级索引,通过索引可以一次实现多个节点的跳跃,提高查询性能,下面是跳表的结构图:

        可以看到,图中的某些节点不光只有一层。如果使用普通的节点,那么每一步只能查询后方的一个节点,如果用这种有高层的节点,那么可以实现一步查询后方相隔多个的节点。理论上,层次越高,对应的步长就越大,但是实际上并不像上图一样是绝对均衡的,节点的层高其实是概率随机的,下面会具体分析到,这里我们先这样简单理解。

        为了理解这种结构有什么好处,我们根据上方的图分几个来进行分析。

        (1)查找分数为35的数据。

        如果只有原始链表的话,那需要走4步,如果有图中的二级索引,那么只用走一步。如果要找的值是45呢?其实就是从第一个节点出发,通过二级索引走到节点值为35,再查看二级索引对应的下一个节点值为65,已经超过要查询的节点值,所以在值为35的节点上,二级索引下降到一级索引向后遍历,再走一次就到了值为45的节点。整个查询的过程其实和二分查找十分相似。

        (2)插入一条score为36的数据。

        要插入一个值为36的节点,那么我们首先应该定位到一个值比36大的节点,也就是图中值为45的节点,定位的方式与查询类似。然后构造一个新的值为36的节点,这里我们假设节点的层高随机到3,(具体的随机算法我们后面会讲),最后将各层链表补齐,其实就是在每一层进行链接,完成后效果如下图所示:

        标准的跳表会存在如下两种限制:

        ①score值不能重复,也就是在底层链表中不允许存在两个值相同的节点。

        ②底层链表只有前向指针(next),而没有退回指针(prev)。

Redis中的SkipList:

        在Redis中,跳表是用来支持有序集合的,所以Redis对标准跳表做了一些优化,使得链表中的score值可以重复,并且增加了回退指针,具体的结构如下图所示:

zskiplistNode:

typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned int span;
    } level[];
} zskiplistNode;

        ele:用来存储数据,使用的是SDS数据结构。

        score:用来存储节点的分数,采用double数据类型。

        backword:指向上一个节点的回退指针,支持链表从尾到头的遍历,也就是ZREVRANGE这个命令。

        level[]:level结构体数组是用来保存各个索引层级的forward指针和span信息的。具体来说,level[0]存储的就是第一层索引(最底层索引),level[1]存储的就是第二层索引,以此类推。higher level的forward指针可以指向更远的节点,可以用来快速遍历。span记录索引跨度。通过维护多级索引,跳表可以通过较高层级的索引快速定位到目标节点附近,然后再在底层索引进行线性查找。所以,level数组在跳表中的作用就是记录多级索引的数据,让跳表可以实现更快的查找、遍历、排序等操作。

zskiplist:

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

        *header:指向跳表的头结点(头结点不存储任何数据,并且其level数组的大小为32)。

        *tail:指向跳表的尾节点。

        length:存储跳表中除去头结点后其余节点个数。

        level:存储跳表中除去头结点后其余节点中level数组最大长度。

内存分布:

        Redis中的SkipList内存分布如下图所示:

        

Redis中跳表的Node中的level数组长度怎么决定?

        长度的决定需要比较随机,才能在各个场景中表现出较为平均的性能。Redis使用概率均衡的思路来确定新插入节点的层数:Redis跳表决定每一个节点,是否增加一层的概率为25%,而最大层数限制在Redis5.0时是64层,在Redis7.0时是32层。

Redis使用跳表对性能究竟优化了多少?

        与二分查找的平均时间复杂度是一样的,Redis使用跳表将增删查的时间复杂度从O(n)降低到了O(logn)。

Redis为什么不使用哈希表或者平衡树?

        先来看一看作者是怎么回答这个问题的:

There are a few reasons:

They are not very memory intensive. It's up to you basically. Changing parameters about the probability of a node to have a given number of levels will make then less memory intensive than btrees.
A sorted set is often target of many ZRANGE or ZREVRANGE operations, that is, traversing the skip list as a linked list. With this operation the cache locality of skip lists is at least as good as with other kind of balanced trees.

They are simpler to implement, debug, and so forth. For instance thanks to the skip list simplicity I received a patch (already in Redis master) with augmented skip lists implementing ZRANK in O(log(N)). It required little changes to the code.

About the Append Only durability & speed, I don't think it is a good idea to optimize Redis at cost of more code and more complexity for a use case that IMHO should be rare for the Redis target (fsync() at every command). Almost no one is using this feature even with ACID SQL databases, as the performance hint is big anyway.
About threads: our experience shows that Redis is mostly I/O bound. I'm using threads to serve things from Virtual Memory. The long term solution to exploit all the cores, assuming your link is so fast that you can saturate a single core, is running multiple instances of Redis (no locks, almost fully scalable linearly with number of cores), and using the "Redis Cluster" solution that I plan to develop in the future.

        说人话就是跳表的实现更加简单,并且达到了类似的效果。

        首先回答一下为什么不使用哈希表。最大的原因就是哈希表的存储是无序的,因此哈希表更适合解决对单个键的查询工作,而不适合解决范围查找问题(查找哪些值在两个数之间的节点的这类问题)。

        接着回答为什么不使用平衡树。平衡树可以解决范围查找的问题,但是与SkipList相比,平衡树在进行范围查找时的操作更加复杂,当我们在平衡树上找到指定范围的小值时,还需要以中序遍历的顺序继续寻找其他不超过指定范围的大值的节点,而SkipList的操作就很简单了,只需要在找到小值之后,进行若干步的遍历即可。平衡树在进行插入和删除操作时可能引发树的自平衡调整,逻辑复杂,而SkipList在进行指定节点的插入和删除时,只需要修改相邻节点的指针就可以了,逻辑和操作都很简单。

        所以,Redis在实现复杂度和性能上,最终选择的是SkipList数据结构。

        Redis底层数据结构篇文章到这里就迎来一个小结了,有什么问题或者勘误欢迎评论区留言,笔者看到都会回复的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2398670.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Ubuntu安装Docker命令清单(以20.04为例)

在你虚拟机上完成Ubuntu的下载后打开终端!!! Ubuntu安装Docker终极命令清单(以20.04为例) # 1. 卸载旧版本(全新系统可跳过) sudo apt-get remove docker docker-engine docker.io containerd …

HarmonyOS Next 弹窗系列教程(2)

HarmonyOS Next 弹窗系列教程(2) 上一章节我们讲了自定义弹出框 (openCustomDialog),那对于一些简单的业务场景,不一定需要都是自定义,也可以使用 HarmonyOS Next 内置的一些弹窗效果。比如: 名称描述不依…

中小企业搭建网站选择虚拟主机还是云服务器?华为云有话说

这是一个很常见的问题,许多小企业在搭建网站时都会面临这个选择。虚拟主机和云服务器都有各自的优缺点,需要根据自己的需求和预算来决定。 虚拟主机是指将一台物理服务器分割成多个虚拟空间,每个空间都可以运行一个网站。虚拟主机的优点是价格…

使用 HTML + JavaScript 在高德地图上实现物流轨迹跟踪系统

在电商行业蓬勃发展的今天,物流信息查询已成为人们日常生活中的重要需求。本文将详细介绍如何基于高德地图 API 利用 HTML JavaScript 实现物流轨迹跟踪系统的开发。 效果演示 项目概述 本项目主要包含以下核心功能: 地图初始化与展示运单号查询功能…

19-项目部署(Linux)

Linux是一套免费使用和自由传播的操作系统。说到操作系统,大家比较熟知的应该就是Windows和MacOS操作系统,我们今天所学习的Linux也是一款操作系统。 我们作为javaEE开发工程师,将来在企业中开发时会涉及到很多的数据库、中间件等技术&#…

html基础01:前端基础知识学习

html基础01&#xff1a;前端基础知识学习 1.个人建立打造 -- 之前知识的小总结1.1个人简历展示1.2简历信息填写页面 1.个人建立打造 – 之前知识的小总结 1.1个人简历展示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8&qu…

【RoadRunner】自动驾驶模拟3D场景构建 | 软件简介与视角控制

&#x1f4af; 欢迎光临清流君的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落 &#x1f4af; &#x1f525; 个人主页:【清流君】&#x1f525; &#x1f4da; 系列专栏: 运动控制 | 决策规划 | 机器人数值优化 &#x1f4da; &#x1f31f;始终保持好奇心&…

基于RK3576+FPGA芯片构建的CODESYS软PLC Linux实时系统方案,支持6T AI算力

基于RK3576芯片构建的CODESYS软PLC Linux实时系统方案&#xff0c;结合了异构计算架构与工业实时控制技术&#xff0c;主要特点如下&#xff1a; 一、硬件架构设计 ‌异构多核协同‌ ‌Cortex-A72四核‌&#xff08;2.3GHz&#xff09;&#xff1a;处理运动轨迹规划、AI视觉等…

适配器模式:让不兼容接口协同工作

文章目录 1. 适配器模式概述2. 适配器模式的分类2.1 类适配器2.2 对象适配器 3. 适配器模式的结构4. C#实现适配器模式4.1 对象适配器实现4.2 类适配器实现 5. 适配器模式的实际应用场景5.1 第三方库集成5.2 遗留系统集成5.3 系统重构与升级5.4 跨平台开发 6. 类适配器与对象适…

DDP与FSDP:分布式训练技术全解析

DDP与FSDP:分布式训练技术全解析 DDP(Distributed Data Parallel)和 FSDP(Fully Sharded Data Parallel)均为用于深度学习模型训练的分布式训练技术,二者借助多 GPU 或多节点来提升训练速度。 1. DDP(Distributed Data Parallel) 实现原理 数据并行:把相同的模型复…

【Spring AI 1.0.0】Spring AI 1.0.0框架快速入门(1)——Chat Client API

Spring AI框架快速入门 一、前言二、前期准备2.1 运行环境2.2 maven配置2.3 api-key申请 三、Chat Client API3.1 导入pom依赖3.2 配置application.properties文件3.3 创建 ChatClient3.3.1 使用自动配置的 ChatClient.Builder3.3.2 使用多个聊天模型 3.4 ChatClient请求3.5 Ch…

【笔记】在 MSYS2(MINGW64)中正确安装 Rust

#工作记录 1. 环境信息 Windows系统: MSYS2 MINGW64当前时间: 2025年6月1日Rust 版本: rustc 1.87.0 (17067e9ac 2025-05-09) (Rev2, Built by MSYS2 project) 2. 安装步骤 步骤 1: 更新系统包数据库并升级已安装的包 首先&#xff0c;确保我们的 MSYS2 系统是最新状态。打…

从汇编的角度揭秘C++引用,豁然开朗

C中的引用是指已有对象的别名&#xff0c;可以通过该别名访问并修改被引用的对象。那么其背后的原理是什么呢&#xff1f;引用是否会带来额外的开销呢&#xff1f;我们从一段代码入手&#xff0c;来分析一下引用的本质。 #include <stdio.h> int main() {int a 10;int …

聊聊Tomato Architecture

序 本文主要研究一下Tomato Architecture Clean/Onion/Hexagonal/Ports&Adapters Architectures Clean Architecture clean architecture定义了四层结构&#xff0c;最内层是entities(enterprise business rules)&#xff0c;再往外是use cases(application business ru…

小白的进阶之路系列之十二----人工智能从初步到精通pytorch综合运用的讲解第五部分

在本笔记本中,我们将针对Fashion-MNIST数据集训练LeNet-5的变体。Fashion-MNIST是一组描绘各种服装的图像瓦片,有十个类别标签表明所描绘的服装类型。 # PyTorch model and training necessities import torch import torch.nn as nn import torch.nn.functional as F impor…

2025年06月03日Github流行趋势

项目名称&#xff1a;onlook 项目地址url&#xff1a;https://github.com/onlook-dev/onlook项目语言&#xff1a;TypeScript历史star数&#xff1a;12871今日star数&#xff1a;624项目维护者&#xff1a;Kitenite, drfarrell, spartan-vutrannguyen, apps/devin-ai-integrati…

【数据分析】基于Cox模型的R语言实现生存分析与生物标志物风险评估

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据数据预处理生存分析画图输出图片其他标记物的分析总结系统信息介绍 分析生存数据与多种生物标志物之间的关系。它通过Cox比例风险模型来评估不同生物标志物…

使用nginx配置反向代理,负载均衡

首先啥叫反向代理 咋配置呢&#xff0c;那当然是在nginx目录下改conf文件了 具体咋改呢&#xff0c;那就新增一个新的server配置&#xff0c;然后在location里新增你想代理的服务器 实际上负载均衡也就是根据反向代理的思路来的&#xff0c;如下所示 配置的话实际上也与上…

从 iPhone 备份照片: 保存iPhone图片的5种方法

随着智能手机越来越融入我们的生活&#xff0c;我们的照片已成为我们设备上最有价值的数据形式之一。然而&#xff0c;iPhone内部存储空间仍然有限&#xff0c;因此我们需要将iPhone中的照片备份到另一个地方&#xff0c;以释放空间并确保珍贵的图像记忆的安全。阅读本指南&…

Spring Ai 从Demo到搭建套壳项目(一)初识与实现与deepseek对话模式

前言 为什么说Java长青&#xff0c;主要是因为其生态圈完善&#xff0c;Spring又做了一款脚手架&#xff0c;把对接各个LLM厂商的sdk做了一遍&#xff0c;形成一系列的spring-ai-starter-** 的依赖。 目前为止版本去到1.0.0.M6&#xff0c;golang跟不上了吧&#xff0c; Make …