• Redis skip list結構分析

    1
    MySQL Redis C/C++ Go 12774 次瀏覽

      如何實現一個海量用戶的實時排名系統?或許可以用 mysql 搞一個糾結的方案;但要是選擇了 redis,那絕對是既簡單又優雅。Redis 的 zset 本身就是一種支持排序的集合,而 zset 的實現,則使用了 skip list 數據結構。Skip list 是一種多層次的有序鏈表,通過隨機地選擇層數來實現插入、查找和刪除都是O(logn)的時間復雜度(和平衡樹同樣的效率,但實現比平衡樹簡單很多)。關 于 skip list 的具體介紹可以參見 William Pugh 的論文:Skip Lists: A Probabilistic Alternative to Balanced Trees 。下面我們來分析一下 redis 中 skip list 的實現。

      Redis 中 skip list 主要有 zskiplist 和 zskiplistNode 兩個數據結構:

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

      其中 zskiplistNode 中包含一個 zskiplistLevel 數組,數組的大小根據節點所在的層數(level)決定。backward 指針是為了方便向后遍歷而對 skip list 做的改進。

      主要的 API 有:

      zslCreate            創建一個 zskiplist,并添加一個具有最高層數 ZSKIPLIST_MAXLEVEL(代碼中定義為32)的節點來管理分層的鏈表。

      zslInsert              插入一個節點到 zskiplist,并調整每一個層級的鏈表都是有序的。

      zslDelete            從 zskiplist 刪除一個節點,并調整剩余節點在每個層級都是有序的。

      zslRandomLevel 為新加入的節點隨機產生一個不超過 ZSKIPLIST_MAXLEVEL 的層數。

      zslInsert 和 zslDelete 函數都需要首先查找到合適的位置或節點,查找的代碼很簡單,直接包含在了這兩個函數內:

      x = zsl->header;
    
          for (i = zsl->level-1; i >= 0; i–) {
    
          /* store rank that is crossed to reach the insert position */
    
              rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];
    
              while (x->level[i].forward &&
    
                    (x->level[i].forward->score < score
    
                       (x->level[i].forward->score == score &&
    
                    compareStringObjects (x->level[i].forward->obj,obj) < 0))) {
    
                 rank[i] += x->level[i].span;
    
                 x = x->level[i].forward;
    
             }
    
             update[i] = x;
    
          }

      查找是從 zskiplist 現有的最高層開始向前,并在查找的過程中根據規則轉向低層的鏈表繼續,一直到 skip list 的最低層為止。同時看到 redis 的實現中允許相同的 score 存在(這時按對象的字符串進行比較),但不允許具有相同值的對象并存(集合的特性)。

      下面通過一個例子來說明 skip list 的建立過程。

      按順序執行下列語句:

          zslInsert (zsl, 5, obj1);              //level=1;
    
          zslInsert (zsl, 3, obj2);              //level=2;
    
          zslInsert (zsl, 4, obj3);              //level=1;
    
          zslInsert (zsl, 1, obj4);              //level=3;
    
          zslInsert (zsl, 2, obj5);              //level=1;

      現在的 zsl 結構如下圖所示,其中 level array 的數組下標是為了圖例更直觀,實際不占存儲空間。為了保證圖例的簡潔,backward 的指針沒有畫出,對應 level 0 紅色指針相反方向的指針。

    Redis skip list結構分析

      祝大家玩兒的開心!
          來自: rdc.taobao.com

    相似問題

    相關經驗

    相關資訊

    相關文檔

  • sesese色