你肯定不來了解下 Redis 跳躍表的原理嗎

前言

本章將介紹 Redis中 set 和 zset的基本使用和內部原理.由於這兩種數據結構有不少類似的地方因此把他們放到一章中介紹.而且重點介紹zset 內部一個很重要的數據結構:跳躍表.node

基本介紹

set

先來看看 set 算法

Redis 中 set 集合很像Java 中 HashSet,鍵值對無序、惟1、不爲空.數組

> sadd books Java
(integer 1)
> sadd books Java
(integer 0)            # value 值重複
> sadd books Go
(integer 1)
> smembers books        # 無序
1) "Go"
2) "Java"

zset

zset 是 Redis 中最特別的基礎數據結構,其餘幾個都能和 Java 大體對應上.它基本上仍是一個 set 可是添加了一個 score 屬性去保證有序性.其內部實現爲跳躍表稍後將會着重介紹.數據結構

> zadd books 1 Java
(integer) 1
> zadd books 2 Go
(integer) 1
> zadd books 3 Python
(integer) 1
> zrange books 0 -1     #按 score 有序取出
1) "Java"
2) "Go"
3) "Python"

在 zset 中 score 的類型爲 double 因此有時會出現小數點精度問題.post

當 zset 中最後一個 value 被刪除後,這個和 zset 就會被自動刪除,內存被回收.spa

內部原理

Redis 的 zset 是個複合結構,是由一個 hash 和 skiplist 組成的,其中 hash 用來保存 value 和 score 對應關係.skiplist 用來給 score 排序.關於hash 的內部實現請參閱以前的一篇文章:《你肯定不來了解一下Redis中 Hash的原理嗎》,在這裏咱們着重介紹 skiplist 的實現.指針

skiplist 跳躍表

由於zset須要高效的插入和刪除,因此底層不適合使用數組實現,須要使用鏈表的結構.當插入新元素時須要根據 score插入到鏈表合適的位置,保證鏈表的有序性.高效的辦法是經過二分查找去找到插入點.code

那麼問題就來了,二分查找的對象必須是有序數組,只有數組支持快速定位,鏈表作不到該怎麼辦呢?這時,就該跳躍表出場了.對象

如圖所示,跳躍表在鏈表的基礎上加入了層級L0~L3的概念,Redis 的跳躍表共有 64 層,可容納 $2^{64}$ 個元素.每一個元素的層級是隨機分配的,分配 L0 的機率是 100%,就是說每一個元素至少會有一層.分配L1 的機率是 50%,分配 L2 的機率是 25%,往上以此類推.排序

每一個 kv 對應的結構爲zslnode.kv 之間使用指針造成有序的雙向鏈表.同一層的 kv 會使用指針串起來.每層元素的遍歷都是從跳躍表的頭指針 kv header 出發.

header 的結構也是 zslnode,當中 value 爲 null,score 爲 Double.MIN_VALUE排在最前面.

struct zslnode{
    string value;
    double score;
    zslnode*[] forwards;    //多層鏈接的指針
    zslnode* backward;        //回溯指針
}

struct zsl{
    zslnode* header;            //跳躍表頭指針
    int maxLevel;                //當前節點的最高層
    map<String,zslnode*> ht;    //hash 中的鍵值對
}

查找

介紹完 skiplist的數據結構後,咱們來具體看下skiplist 是怎樣快速定位元素的.

在上圖中,假設咱們要查找 3 這個節點.skiplist 會從 header 的頂層出發遍歷搜索找到第一個比目標元素小的開始降一層,直到降到最底層找到 3這個節點,搜索路徑爲:

  1. L3:header -> 4 -> header
  2. L2:header -> 2 -> 4 -> 2
  3. L1: 2 -> 4 ->2
  4. L0: 2 -> 3

說明:

  1. L3 層header找到 43大,回退到 header 同時降一層
  2. L2層header 找到 23 小,繼續遍歷找到 4,回退到 2 同時降一層
  3. L1 層 2 找到 43 大,回退到 2 降一層
  4. L0 層 2 找到 3 指望節點

整個查找過程算法的時間複雜度爲$O(lg(n))$.

相關文章
相關標籤/搜索