【愚公系列】2021年11月 C#版 数据结构与算法解析 Redis有序集合zset实现原理(跳表)

简介: 【愚公系列】2021年11月 C#版 数据结构与算法解析 Redis有序集合zset实现原理(跳表)

Redis有序集合中的元素的编码可以是 ziplist 或者 skiplist。ziplist和skiplist编码选择的标准在于Redis里的元素的数量以及元素成员的长度。当满足以下2个条件时,元素编码为ziplist:


有序集合保存的元素数量小于128个

有序集合保存的所有元素成员的长度小于64字节

ziplist:

ziplist编码的有序集合对象使用压缩列表作为底层实现。每个集合使用2个紧挨在一起的压缩列表节点来保存,第一个保存元素的成员,第二个保存元素的分值。压缩列表内的集合按分值从小到大排序,分值较小的元素被放置在靠近表头的位置,分值较大的元素在靠近表尾的位置。


skiplist:

跳跃表是一种有序的数据结构,它通过在每个节点上维持多个指向其它节点的指针来达到快速访问的目的。跳跃表在插入、删除和查找操作上的平均复杂度为O(logN),最坏为O(N),可以和红黑树相媲美,但是在实现起来,比红黑树简单很多。在Redis中的zset就是使用了跳跃表的实现有序集合。

具有如下性质:


由很多层结构组成;

每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节点;

最底层的链表包含了所有的元素;

如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集);

链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;

image.png

如上图所示,有一个链表保存着若干有序的数字,如果我们想找到 <16,23,25,31>这几个数字的话,简单的方法就是遍历一遍,时间复杂度为O(N)。数据量小的话还好,但是数据量一旦十分庞大的话就非常耗时了。这个时候就是需要我们去优化的了,找一个线性的顺序表,我们一般使用二分法查找,时间复杂度为O(LogN)。但是链表不适用二分法去查找(链表没有线性表那种连续下标,无法确定中间位置)。于是跳表产生了,它是一种随机化的数据结构,类似二叉搜索树,我们把一些节点提取出来作为索引。如下:>这几个数字的话,简单的方法就是遍历一遍,时间复杂度为O(N)。数据量小的话还好,但是数据量一旦十分庞大的话就非常耗时了。这个时候就是需要我们去优化的了,找一个线性的顺序表,我们一般使用二分法查找,时间复杂度为O(LogN)。但是链表不适用二分法去查找(链表没有线性表那种连续下标,无法确定中间位置)。于是跳表产生了,它是一种随机化的数据结构,类似二叉搜索树,我们把一些节点提取出来作为索引。如下:

image.png

我们提取了<13,19,25,34>作为一级索引,这样可以减少比较的次数。同样的,有一级索引就可以建立二级索引,如下,在海量的数据情况下这种方式无疑会大大减少查询的时间O(logN)

image.png

Redis中跳跃表的结构

在上面简单了解了跳跃表的基本含义及实现思想。在redis中,跳跃表的定义在 server.h 中,包含2个结构体,分别表示跳跃表节点的zskiplistNode和跳跃表zskilist。zskiplistNode 结构用于表示跳跃表节点,zskiplist结构用来保存跳跃表节点的相关信息。源码如下:

typedef struct zskiplistNode {
    //节点对应成员的对象,一般是SDS
    robj *obj;
    //分数,跳跃表的顺序按照分值进行排列
    double score;
    //存储后继节点的指针
    struct zskiplistNode *backward;
    // 层级
    struct zskiplistLevel {
        // 存储前驱节点的指针
        struct zskiplistNode *forward;
        // 跨度
        unsigned int span;
    } level[];
} zskiplistNode;
typedef struct zskiplist {
    // 跳跃表的表头节点和表尾节点
    struct zskiplistNode *header, *tail;
    // 跳跃表中节点的数量
    unsigned long length;
    // 表中层数最大的节点层数
    int level;
} zskiplist;

level[] 是跳跃表中非常重要的属性,在初始化一个跳跃表节点的时候会为其随机生成一个层级大小,这个大小通常在1-32之间(不需要太大,假设极端平均的情况下2^32已经是很大了42亿+),作为level数组的大小。数组中的每个元素包含一个指向其它节点的前进指针,通过它来加速访问其它节点。如下图所示,这个层级是随机产生的,所以每个节点拥有的层级是不一定的,但是至少是1级。假设链表 <13,16,19,34>

image.png

它的跳跃表结构可能如下(注意这里说的是可能,因为每个节点的层级Level是随机的):

image.png

相关文章
|
3月前
|
存储 监控 算法
局域网监控其他电脑的设备信息管理 Node.js 跳表算法
跳表通过分层索引实现O(logn)的高效查询、插入与删除,适配局域网监控中设备动态接入、IP映射及范围筛选等需求,相比传统结构更高效稳定,适用于Node.js环境下的实时设备管理。
158 9
|
存储 缓存 NoSQL
Redis 服务器全方位介绍:从入门到核心原理
Redis是一款高性能内存键值数据库,支持字符串、哈希、列表等多种数据结构,广泛用于缓存、会话存储、排行榜及消息队列。其单线程事件循环架构保障高并发与低延迟,结合RDB和AOF持久化机制兼顾性能与数据安全。通过主从复制、哨兵及集群模式实现高可用与横向扩展,适用于现代应用的多样化场景。合理配置与优化可显著提升系统性能与稳定性。
564 0
|
5月前
|
存储 缓存 NoSQL
【📕分布式锁通关指南 12】源码剖析redisson如何利用Redis数据结构实现Semaphore和CountDownLatch
本文解析 Redisson 如何通过 Redis 实现分布式信号量(RSemaphore)与倒数闩(RCountDownLatch),利用 Lua 脚本与原子操作保障分布式环境下的同步控制,帮助开发者更好地理解其原理与应用。
374 6
|
4月前
|
消息中间件 缓存 NoSQL
Redis各类数据结构详细介绍及其在Go语言Gin框架下实践应用
这只是利用Go语言和Gin框架与Redis交互最基础部分展示;根据具体业务需求可能需要更复杂查询、事务处理或订阅发布功能实现更多高级特性应用场景。
332 86
|
6月前
|
存储 缓存 NoSQL
Redis核心数据结构与分布式锁实现详解
Redis 是高性能键值数据库,支持多种数据结构,如字符串、列表、集合、哈希、有序集合等,广泛用于缓存、消息队列和实时数据处理。本文详解其核心数据结构及分布式锁实现,帮助开发者提升系统性能与并发控制能力。
|
6月前
|
存储 运维 监控
基于跳表数据结构的局域网上网记录监控时序查询优化算法研究与 Python 实现
本文探讨跳表(Skip List)在局域网上网记录监控中的应用,分析其在快速范围查询、去重与异常检测中的优势,并提供 Python 实现示例,为高效处理海量时序数据提供参考。
121 0
|
3月前
|
存储 监控 算法
基于 Go 语言跳表结构的局域网控制桌面软件进程管理算法研究
针对企业局域网控制桌面软件对海量进程实时监控的需求,本文提出基于跳表的高效管理方案。通过多级索引实现O(log n)的查询、插入与删除性能,结合Go语言实现并发安全的跳表结构,显著提升进程状态处理效率,适用于千级进程的毫秒级响应场景。
188 15
|
4月前
|
存储 消息中间件 NoSQL
Redis数据结构:别小看这5把“瑞士军刀”,用好了性能飙升!
Redis提供5种基础数据结构及多种高级结构,如String、Hash、List、Set、ZSet,底层通过SDS、跳表等实现高效操作。灵活运用可解决缓存、计数、消息队列、排行榜等问题,结合Bitmap、HyperLogLog、GEO更可应对签到、UV统计、地理位置等场景,是高性能应用的核心利器。
|
4月前
|
存储 监控 算法
企业电脑监控系统中基于 Go 语言的跳表结构设备数据索引算法研究
本文介绍基于Go语言的跳表算法在企业电脑监控系统中的应用,通过多层索引结构将数据查询、插入、删除操作优化至O(log n),显著提升海量设备数据管理效率,解决传统链表查询延迟问题,实现高效设备状态定位与异常筛选。
149 3
|
4月前
|
存储 缓存 NoSQL
Redis基础命令与数据结构概览
Redis是一个功能强大的键值存储系统,提供了丰富的数据结构以及相应的操作命令来满足现代应用程序对于高速读写和灵活数据处理的需求。通过掌握这些基础命令,开发者能够高效地对Redis进行操作,实现数据存储和管理的高性能方案。
148 12

推荐镜像

更多
  • DNS