Redis系列(七)底层数据结构之跳跃表

前言

Redis 已是你们耳熟能详的东西了,平常工做也都在使用,面试中也是高频的会涉及到,那么咱们对它究竟了解有多深入呢?java

我读了几本 Redis 相关的书籍,尝试去了解它的具体实现,将一些底层的数据结构及实现原理记录下来。面试

本文将介绍 Redis 中底层的 skiplist(跳跃表) 的实现方法。 它是 Redis 中有序集合键底层实现之一。编程

2020-01-06-22-16-38

能够看到图中,当我在zsetkey中放入了两个简单的值时,编码为** ziplist**, 而当我插入一个较长的值,zset 的编程方式成为了 skiplist.后端

对于跳跃表这个数据结构,其底层实现原理及代码实现,本文就不细讲了,若是不太清楚的读者能够看一下这个文章 跳表的原理, 或者自行 google 了解。数组

本文仅对 Redis 中跳跃表的实现作一个学习。服务器

定义

首先让咱们来看一下,skiplist 的定义:微信

typedef struct zskiplist{
    // 表头结点和尾节点
    struct zskiplistNode *header, *tail;
    // 表中节点的数量
    unsigned int length;
    // 表中层数最大的节点的层数
    int level;
} zskiplist;
复制代码

这几个属性比较简单,其中header, tail能够在 O(1) 的时间复杂度内定位到跳跃表的头部和尾部,length能够在 O(1) 时间复杂度内获得跳跃表的长度。level能够知道当前跳跃表最高的层,从而开始从高向低进行查找。数据结构

其中 skiplistNode 的节点的定义为:性能

typedef struct zskiplistNode{
    struct zskiplistLevel{
        // 前进指针
        struct zskiplistNode *forward;
        // 跨度
    } level[];
    // 后退指针
    struct zskiplistNode *backward;
    // 分值
    double score;
    // 成员对象
    robj *obj;
} zskiplistNode;
复制代码

这个节点的定义有点东西的。学习

若是了解 Java 中的ConcurrentSkipListMap的实现,或者看了上面个人那篇文章的话,就会知道,在 Java 中,一个 所谓的 节点(或者叫索引) 是有两个指针的,一个指向右侧的下一个索引,一个指向本身的下一层索引。

可是 Redis 不是这么实现的,在上面的定义中,能够看到zskiplistLevel这个结构是一个数组,用一个数组来保存,本节点,以及本节点在全部层的索引.

每一个索引中,有两个属性,

  • forward

指向右侧的指针,能够在当前层,继续向右走。

  • 跨度

这个属性设计的很巧妙,能够用它来计算当前节点在 跳跃表中的一个排名,这就 zset 提供了查看排名的功能。

  • backward

后退的指针,若是在高层索引向右走的太多了,能够用后退指针来向后退。

  • score and obj

这两个属性用来保存当前节点的真正值以及分值。

层级问题

在 Java 中的ConcurrentSkipListMap的实现中,索引每一次向上升级或者不升级,都是随机的,所以:

  1. 一个节点是不是一级索引的几率是 50%.
  2. 是不是二级索引的几率是 25%. ...

而在 Redis 中,新添加一个节点时,会给该节点随机一个索引层数,并且几率是 25%. 以后将该节点的各层索引与左右的索引相连接。

因为几率是 25%, 所以 Redis 的跳跃表相对于 Java 中的跳跃表,结构更加扁平一些,在查找的时候,在同级索引上可能须要多查询几个。

也是由于结构扁平,所以索引的数量并非彻底的等同于节点数,额外的内存占用只有 50%. 能够为 Redis 服务器节省一点内存。

顺序问题

咱们知道,在 zset 中,是能够存储分数同样的值的,此时内部如何存储?直接进行无序存储吗?

若是是这样,当一个 zset 中,全部元素的分值都同样,跳跃表表的性能就会退化成链表的性能吗?

不是这样的,Redis 除了按照分值排序以外,还会按照字符串的字典序来存储。

排名问题

前面提到了 跨度 这个属性,当咱们须要查找某个元素的排名时,跳跃表首先开始一次查询过程,找到该节点时,也能够找到从顶层索引找到该节点的 查找路径, 将 路径上的全部节点的 跨度 值相加就是该节点的排名。

总结

Redis 的跳跃表,和 其余语言实现的跳跃表,整体思路同样,在实现方式上有一些本身的小技巧。

跳跃表示 有序集合键 的底层实现之一,表中元素按照 score 大小进行排序,当 score 相同时,元素按照字符串的字典大小进行排序。

相比于 Java 的跳跃表,Redis 的跳跃表的索引层级更加扁平,能够节省一些内存。

参考文章

《Redis 的设计与实现(第二版)》

《Redis 深度历险:核心原理和应用实践》

完。

联系我

最后,欢迎关注个人我的公众号【 呼延十 】,会不按期更新不少后端工程师的学习笔记。 也欢迎直接公众号私信或者邮箱联系我,必定知无不言,言无不尽。


以上皆为我的所思所得,若有错误欢迎评论区指正。

欢迎转载,烦请署名并保留原文连接。

联系邮箱:huyanshi2580@gmail.com

更多学习笔记见我的博客或关注微信公众号 < 呼延十 >------>呼延十

相关文章
相关标签/搜索