Redis的跳跃表

在Redis中,有一种高效的数据结构叫做有序集合(zset)

它是一种集合,其中每个成员(member)都会关联一个分数(score)。
zset既可以快速地通过member找到该成员对应的分数,又可以按照分数的大小进行范围查询,这对于实现排行榜等功能非常有用。那么zset是如何实现这样的功能的呢?答案是跳跃表(skiplist)。

1. zset的底层实现

思考这么几个问题:

  • zset是如何快速的根据成员取得对应的分数?
  • zset是如何快速的取得指定排行区间的所有成员和分数?
  • zsetzrevrange(逆序)又是如何实现的?
  • zset如何计算一个成员的排位?
  • ...
    第一个问题我们很容易想到使用哈希表,实际上zset的底层实现,就是一个哈希表+跳跃表,看看结构:(对于元素数量少的情况,zset会使用另一种实现方式,我们这里不作讨论)
typedef struct zset {
    dict *dict;
    zskiplist *zsl;
} zset;

哈希表(dict)大部分人都了解,那么对于这个跳跃表(zskiplist),又是怎样的结构呢?

2.跳跃表的结构和特性

跳跃表是一种有序链表+多层索引的结构,使得包含n个元素的有序序列的查找和插入操作的平均时间复杂度都是 O(\log{n})
跳跃表是按层建造的,最底层是一个普通的有序链表。
众所周知,如果是一个有序的数组,我们可以通过二分查找,很快的找到其中的某个元素。那么对于一个有序的链表,我们有没有办法也使用二分查找呢?
跳跃表的想法就是用空间换时间:在最底层的原始链表上面,加多一层索引,元素数量为原始链表的一半,如图所示:

skiplist-level-2.png

这样查找的时候,原本要比较n个元素,通过使用索引,减少到 \frac{n}{2}
但是这样还不够,所以我们在上面再多加几层索引,如图所示:

skiplist-level-n.png

理论上,对于元素数量为n的链表,我们可以建立 \log_{2}{n} 层索引。
这样,我们就能通过从最高层开始,通过二分查找的方式,快速的查找某个数值在链表中的位置。

3.实现细节

Redis的跳跃表实现主要有两个结构:zskiplistNodezskiplist,其中zskiplistNode是跳跃表的结点,而zskiplist则记录整个表的一些信息,如结点的数量等。

skiplist.png

上图展示了一个跳跃表的实例。左边代表着跳跃表的整体结构:zskiplist

zskiplist的代码如下:

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

headertail:分别指向跳跃表头结点和尾结点,其中头结点在zskiplist创建时生成,并不实际存储数据。
length:跳跃表的长度,通过这个变量,可以在O(1)时间内获取长度。
level:当前跳跃表中最高的层,不包括头结点。

每次创建新结点的时候,redis根据幂次定律(越大的数出现概率越小)随机生成一个高度(介于1和32),作为当前结点的level数组的长度,也就是这个结点的高度。

结点zskiplistNode的代码如下:

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

ele:每个结点的成员,sdsredis内置的简单动态字符串结构(Simple Dynamic String)
score:每个结点的分数,是double类型
backward:回退指针,用于从表尾向表头遍历。用来实现zrevrange
level:每个层级上的索引。

注意level中的每一层,除了指向下一个结点的指针以外,还有一个span表示跨度,即下一个结点和当前结点的距离。(示例图中,箭头上面的数字)
这个跨度用来计算排行,由于redis的跳跃表并不是严格的按照每层数量减半的实现,所以在高层,每一次跨越几个原始结点,只能存储在这个span中,而在遍历的时候,通过计算跨度的和,就可以知道当前排在第几位了。

上图中红色和绿色都是zskiplistNode结构,其中headerbackward/ele/score都是没用的,所以省略了没画出来

4 与其他结构的对比

和红黑树这类平衡树结构相比,跳跃表的算法复杂度一样,而主要优势在于实现简单,且在并发环境下更加高效;
与哈希表相比,跳跃表支持有序数据的快速检索和范围查询,但牺牲了一些插入和删除操作的性能。
总的来说,跳跃表是一种空间换时间,以及用随机化处理平衡的高效数据结构。

参考资料:

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • Redis里面使用skiplist是为了实现sorted set这种对外的数据结构。sorted set提供的操作...
    tracy_668阅读 780评论 0 8
  • 1. 跳跃表的原理 传统有序链表查询节点需要遍历,复杂度为O(n)。《Redis5设计语言与源码分析》 上图可理解...
    Oliver_Li阅读 135评论 0 0
  • 点赞再看,养成习惯,公众号搜一搜【一角钱技术[https://p3-juejin.byteimg.com/tos-...
    一角钱技术阅读 600评论 0 15
  • 跳跃表(skiplist)是一种有序数据结构,通过每个节点中维持多个指向其他节点的指针,达到快速访问节点的目的,被...
    jianshu_9527阅读 143评论 0 0
  • 跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快捷访问节点的...
    wenmingxing阅读 1,770评论 0 3