Redis系列(七)底層數據結構之跳躍表

前言

Redis 已是你們耳熟能詳的東西了,平常工做也都在使用,面試中也是高頻的會涉及到,那麼咱們對它究竟瞭解有多深入呢?java

我讀了幾本 Redis 相關的書籍,嘗試去了解它的具體實現,將一些底層的數據結構及實現原理記錄下來。面試

本文將介紹 Redis 中底層的 skiplist(跳躍表) 的實現方法。 它是 Redis 中有序集合鍵底層實現之一。編程

2020-01-06-22-16-38

能夠看到圖中,當我在zsetkey中放入了兩個簡單的值時,編碼爲 ziplist, 而當我插入一個較長的值,zset 的編程方式成爲了 skiplist.後端

對於跳躍表這個數據結構,其底層實現原理及代碼實現,本文就不細講了,若是不太清楚的讀者能夠看一下這個文章 跳錶的原理%E7%9A%84%E5%8E%9F%E7%90%86%E5%8F%8AConcurrentSkipListMap%E7%9A%84%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0/#%E6%A6%82%E8%BF%B0), 或者自行 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

更多學習筆記見我的博客或關注微信公衆號 < 呼延十 >------>呼延十

相關文章
相關標籤/搜索