首页 > 数据库 >Redis数据结构5:REDIS_SKIPLIST

Redis数据结构5:REDIS_SKIPLIST

时间:2023-12-14 15:32:32浏览次数:46  
标签:level REDIS Redis 节点 查找 跳表 zsl SKIPLIST

REDIS_SKIPLIST

skipList,即:跳表,或者叫跳跃表。skiplist的优势是能支持平均 O(logN) 复杂度的节点查找。

用一句话来说:skiplist就是一个有着索引的list。

编码结构

简单理解

简单来说,skipList有多层“索引”以加快查找速度:

9.png

其中L1、L2和L3都是一个list。

当查找8时,从L3查找到5,再从L2从5开始查找,查找到7,再L3中从7开始查找,最终查找到8。

这样,原本需要8次查找的操作直接简化到了4次。

从源码理解

typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;
typedef struct zskiplistNode {
    sds ele;
    double score;
    struct zskiplistNode *backward;
    struct zskiplistLevel {
        struct zskiplistNode *forward;
        unsigned long span;
    } level[];
} zskiplistNode;

Redis 只有 Zset 对象的底层实现用到了跳表。

Zset 需要你填入元素和权重,其中权重就是跳表用于排序的根源,即listNode中的score

zskipList中的level即为跳表的层级, Zset的元素存储在listNode中的ele中,以sds形式存储。

SkipList的层级设计

前文提到,SkipList相邻两层的节点数量的比例会影响跳表的查询性能。

当设计不合理时,跳表的查询性能可能会降低到 O(n)

**跳表的相邻两层的节点数量最理想的比例是 2:1,查找复杂度可以降低到 O(logN)**。

但Redis所提出的解决方案是:跳表在创建节点的时候,随机生成每个节点的层数,并没有严格维持相邻两层的节点数量比例为 2 : 1 的情况。

skipList在创建节点时候,会生成范围为[0-1]的一个随机数,如果这个随机数小于 0.25(相当于概率 25%),那么层数就增加 1 层,然后继续生成下一个随机数,直到随机数的结果大于 0.25 结束,最终确定该节点的层数

// 创建一个空表头的跳表
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;
    // 尝试分配内存空间
    zsl = zmalloc(sizeof(*zsl));
    // 初始化level和length
    zsl->level = 1;
    zsl->length = 0;
    // 调用下面的方法zslCreateNode, 传入的参数有数组长度。
    // 分数0,对象值NuLL
    // 这一步就是创建管理所有节点的数组
    // 并且设置表头的头头指针为此对象的地址
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
    // 为这32个数组赋值前指针forward和跨度span
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    // 设置尾指针
    zsl->header->backward = NULL;
    zsl->tail = NULL;
    // 返回对象
    return zsl;
}

zskiplistNode *zslCreateNode(int level, double score, sds ele) {
    zskiplistNode *zn =
        zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));
    zn->score = score;
    zn->ele = ele;
    return zn;
}

ZSKIPLIST_MAXLEVEL 定义的是最高的层数,Redis 7.0 定义为 32,Redis 5.0 定义为 64,Redis 3.0 定义为 32。

为什么要用跳表而不用平衡树?

对于这个问题,Redis作者@antirez如是回答:

There are a few reasons:

  1. 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.
  2. 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.
  3. 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. 简单来说:

1.Btree和SkipList不是内存密集型数据模型,用什么取决于你,只不过改变节点级数的时候SkipList可以比树用更少的内存。

2.Redis经常需要执行ZRANGEZREVRANGE命令,即作为链表遍历跳表。通过此操作,跳表的缓存局部性至少与其他类型的平衡树一样好。

3.SkipList实现简单,社区维护起来方便。比如社区提交来一个用O(logN)ZRANK实现,只需要改动少量代码就能完成。

详细来说:

  • 从内存占用上来比较,跳表比平衡树更灵活。平衡树每个Node固定包含 2 个指针(指向左右子树),而跳表每个节点包含的指针数目平均为 1/(1-p),具体取决于参数 p 的大小。如果像 Redis里的实现一样,取 p=1/4,那么平均每个节点包含 1.33 个指针,比树占用的内存更少。
  • 在做范围查找的时候,跳表比平衡树操作要简单。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对树进行改造,中序遍历并不容易实现。而在跳表上进行范围查找就非常简单,只需要在找到小值之后,对第 1 层链表进行遍历就可以实现。
  • 从算法实现难度上来比较,跳表比平衡树要简单得多。平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而跳表的插入和删除只需要修改相邻节点的指针,操作简单快速。

标签:level,REDIS,Redis,节点,查找,跳表,zsl,SKIPLIST
From: https://blog.51cto.com/ErickRen/8821043

相关文章

  • redis 使用主从机制复制数据
    查看主从情况127.0.0.1:6379>inforeplication#Replicationrole:masterconnected_slaves:0master_repl_offset:12539repl_backlog_active:0repl_backlog_size:1048576repl_backlog_first_byte_offset:0repl_backlog_histlen:0建立主从(在备机操作)注意,如果让有数据的......
  • .net core 分布式锁 之 基于 Redis 的 RedLock
    使用场景分布式锁的业务场景涉及到并发控制、任务调度、缓存更新、分布式事务和防止重复操作等方面,能够保证分布式系统的数据一致性和正确性。并发控制:当多个线程或进程同时访问共享资源时,使用分布式锁可以确保只有一个线程或进程能够访问该资源,避免数据竞争和并发冲突。分......
  • 关于Redis
    1、Redis事务不支持回滚即使事务执行过程中,有其中一条命令出错了,那么只有该条命令不会被执行,其前后的命令仍然会被执行;只有在执行事务之前的组队阶段发生错误,才会回滚2、Redis事务只是保证在事务中的命令在执行的过程中不会被打断3、Redis是基于单线程的,每个命令都能保证其原......
  • redis stream的所有方法以及用处和使用场景
    目录一、用途:将消息添加到Stream中。二、用途:按范围获取消息。三、用途:阻塞读取消息,支持多个Stream。四、用途:创建消费者组。五、用途:阻塞读取消息并将其分配给消费者组中的消费者。六、用途:确认消息已被消费。七、用途:获取待处理的消息列表。八、用途:删除消息。九、用......
  • Redis数据结构4:REDIS_ZIPLIST
    REDIS_ZIPLISTzipList(压缩列表)是一种紧凑型的数据结构,占用一片连续的内存,本质上是一个字节数组。能提高CPU缓存的利用效率,并且针对不同数据结构进行不同编码,节省内存开销。编码结构zipList的字节数组主要由5个部分组成:zlbytes、zltail、zllen、zltail和entry。zlbytes记录......
  • Redis实战篇
    Redis实战篇开篇导读短信登录这一块我们会使用redis共享session来实现商户查询缓存通过本章节,我们会理解缓存击穿,缓存穿透,缓存雪崩等问题,让小伙伴的对于这些概念的理解不仅仅是停留在概念上,更是能在代码中看到对应的内容优惠卷秒杀通过本章节,我们可以学会Redis的计数......
  • 2023最新中级难度Redis面试题,包含答案。刷题必备!记录一下。
    好记性不如烂笔头内容来自面试宝典-中级难度Redis面试题合集问:请解释Redis中的持久化机制RDB和AOF的区别,并谈谈你在实际应用中的选择。Redis的两种持久化机制分别为RDB和AOF:RDB(RedisDatabase)是Redis默认的持久化方式,会在指定的时间间隔内将内存中的数据集快照写入磁盘......
  • 2023最新初级难度Redis面试题,包含答案。刷题必备!记录一下。
    好记性不如烂笔头内容来自面试宝典-初级难度Redis面试题合集问:请简单介绍一下Redis,以及它主要用于解决什么问题?Redis是一款键值存储系统,也被称为“内存数据库”,其主要特点是在内存中高速存储数据。它的优点在于其极高的读写速度和较低的延迟,因此常被用来作为缓存、队列......
  • Redis
    入门Redis是一种基于Key-Value键值对的在内存数据库。版本号第二位是奇数则是非稳定版本,偶数则为稳定版本。常用命令命令作用redis-server/myredis/redis7.conf启动Redisredis-cli-a159123zxc-p6379连接Redisquit退出Redis界面,此时Redis仍在运行shu......
  • Redis内存分析工具-RDBtools安装&使用
    目录是什么安装安装Python(已安装忽略,低版本需要卸载重安)安装GCC(已安装忽略)安装rdbtools和python-lzf安装成功页面基础命令常用示例查找大key与处理导出CVS文件直连Redis服务查询单个key详情生成HTML图表更多用法见Help是什么Rdbtools提供了一组工具,可以帮助用户分析、导入和转换......