• Redis存储结构之zskiplist


    zskiplist(跳跃表)

    原理

    zskiplist是一个有序的集合,为了解决其他链表的插入、删除效率低、查询元素需要循环遍历等缺点,redis就采用了一个特殊的数据结构zskiplist(跳跃表)。

    它在性能上跟红黑树差不多, 同时又比红黑树的实现简单。在插入、删除、查询等操作上的时间复杂度为o(logn)。

    分析

    • 它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的;
    • Redis 只在两个地方用到了跳跃表,一个是实现有序集合健,另一个是在集群节点中用做内部数据结构,除此之外,跳跃表在 Redis 里没有其它用途;
    • 一个常规的链表在做元素查找时,只能从头到尾的遍历来得到元素。时间复杂度为 O(n);
    • zskiplist的头结点不是一个有效的节点,它有ZSKIPLIST_MAXLEVEL层(32层),每层的forward指向该层跳跃表的第一个节点,若没有则为null;
    • redis限定了 ZSKIPLIST_P 抛硬币正面的概率为1/4

    zskiplist 图解分析

    在这里插入图片描述

    zskiplist如何查找元素分析

    加入我们想查找 9 这个元素,步骤如下:

    1. 先遍历最高层第四层找到16;
    2. 9<16,找到第三层13;
    3. 9<13,找到二层8;
    4. 9>8,找到8的下一个节点9。

    在这里插入图片描述

    zskiplist插入元素分析

    创建一个zskiplist的时间复杂度为O(1);

    1. 找到合适的位置插入
    2. 调用zslRandomLevel 得到一个插入节点的层数,有 1/4 概率加入上一层;
    3. 调用zslCreateNode创建一个新的节点;
    4. 修改插入位置的前后节点以及插入节点本身的backwardforwardspan等属性

    在这里插入图片描述

    zskiplist删除元素分析

    释放一个节点的内存 时间复杂度O(1);

    释放整个skiplist的内存 时间复杂度O(n);

    从skiplist中删除并释放掉一个节点 时间复杂度O(logn),会涉及到3个步骤:

    • 根据elescore找到节点的位置(代码里变量x即为该节点,update记录每层x的上一个节点)
    • 调动zslDeleteNode把x节点从skiplist逻辑上删除
    • 释放x节点内存

    根据范围删除节点 时间复杂度O(log(n)+m), m是范围内元素的个数;

    zskiplist跟红黑树对比

    优点

    • 比红黑树占用更多的内存,每个节点的大小取决于该节点的层数;
    • 空间局部性较差导致缓存命中率低,感觉上会比红黑树更慢;

    优点:

    • 实现比红黑树简单;
    • 比红黑树更容易扩展,作者之后实现zrank指令时没怎么改动代码;
    • 红黑树插入删除时为了平衡高度需要旋转附近节点,高并发时需要锁。skiplist不需要考虑;
    • 一般用zset的操作都是执行zrange之类的操作,取出一片连续的节点。这些操作的缓存命中率不会比红黑树低。

    源码

    redis/src/server.h

    #define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */
    #define ZSKIPLIST_P 0.25      /* Skiplist P = 1/4 */
    
    • 1
    • 2
    typedef struct zskiplist {
        struct zskiplistNode *header, *tail;  // 跳跃表头尾节点
        unsigned long length;  //节点个数
        int level;  //除头结点外最大的层数
    } zskiplist;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    typedef struct zskiplistNode {
        sds ele;   //存储字符串类型数据 redis3.0版本中使用robj类型表示,但是在redis4.0.1中直接使用sds类型表示
        double score;  //存储排序的分值
        struct zskiplistNode *backward;   //指向上一个节点,用于zrevrange命令
        struct zskiplistLevel { 
            struct zskiplistNode *forward;   //指向下一个节点
            unsigned long span;  //到达后一个节点的跨度(两个相邻节点span为1)
        } level[];  //该节点在各层的信息,柔性数组成员
    } zskiplistNode;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    Ceph块存储的安装部署和使用
    接口自动化框架搭建(九):接入钉钉消息通知
    基于Linux的邮件模拟系统设计与实现
    springboot整合Mongodb
    Vue3 -- Composition API setup函数 reactive ref ...
    MySQL的Undo Log、Redo Log和Binlog
    Django ORM:数据库操作的Python化艺术
    界面控件DevExpress WinForms皮肤编辑器的这个补丁,你了解了吗?
    端口号被占用解决办法(超详细)
    1400*C. No Prime Differences(找规律&数学)
  • 原文地址:https://blog.csdn.net/u011077966/article/details/126833135