redis 源碼分析--跳躍表

簡介

跳躍表(skiplist)是一種有序的數據結構,它通過在每個節點中維持多個指向其他節點的指針,從而達到快速訪問節點的目的。跳躍表支持平均O(logN)、最壞O(N)複雜度的節點查找,還可以通過順序性操作來批量處理節點。

Redis只在兩個地方用到了跳躍表,一個是實現有序集合鍵,另一個是在集羣節點中用作內部數據結構。

定義

typedef struct redisObject {

    // 類型
    unsigned type:4;

    // 編碼
    unsigned encoding:4;

    // 對象最後一次被訪問的時間
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 引用計數
    int refcount;

    // 指向實際值的指針
    void *ptr;

} robj;

/*
 * 跳躍表節點
 */
typedef struct zskiplistNode {

    // 成員對象指向一個字符串對象,而字符串對象則保存着一個SDS值,各個節點保存的成員對象必須唯一
    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節點,層數爲5的跳躍表。L1、L2、L3...代表層數,箭頭上的數字代表節點之間的距離即span,BW表示backward 指針,數字1.0、2.0、3.0代表的是分值。o1、o2、o3則是成員對象。

主要API

#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^32 elements */

/*
 * 創建並返回一個新的跳躍表
 *
 * T = O(1)
 */
zskiplist *zslCreate(void) {
    int j;
    zskiplist *zsl;

    // 分配空間
    zsl = zmalloc(sizeof(*zsl));

    // 設置高度和起始層數
    zsl->level = 1;
    zsl->length = 0;

    // 初始化表頭節點
    // T = O(1)
    zsl->header = zslCreateNode(ZSKIPLIST_MAXLEVEL,0,NULL);
    for (j = 0; j < ZSKIPLIST_MAXLEVEL; j++) {
        zsl->header->level[j].forward = NULL;
        zsl->header->level[j].span = 0;
    }
    zsl->header->backward = NULL;

    // 設置表尾
    zsl->tail = NULL;

    return zsl;
}

/*
 * 創建一個層數爲 level 的跳躍表節點,
 * 並將節點的成員對象設置爲 obj ,分值設置爲 score 。
 *
 * 返回值爲新創建的跳躍表節點
 *
 * T = O(1)
 */
zskiplistNode *zslCreateNode(int level, double score, robj *obj) {
    
    // 分配空間
    zskiplistNode *zn = zmalloc(sizeof(*zn)+level*sizeof(struct zskiplistLevel));

    // 設置屬性
    zn->score = score;
    zn->obj = obj;

    return zn;
}


/*
 * 釋放給定的跳躍表節點
 *
 * T = O(1)
 */
void zslFreeNode(zskiplistNode *node) {

    decrRefCount(node->obj);

    zfree(node);
}

/*
 * 釋放給定跳躍表,以及表中的所有節點
 *
 * T = O(N)
 */
void zslFree(zskiplist *zsl) {

    zskiplistNode *node = zsl->header->level[0].forward, *next;

    // 釋放表頭
    zfree(zsl->header);

    // 釋放表中所有節點
    // T = O(N)
    while(node) {

        next = node->level[0].forward;

        zslFreeNode(node);

        node = next;
    }
    
    // 釋放跳躍表結構
    zfree(zsl);
}

/*
 * 創建一個成員爲 obj ,分值爲 score 的新節點,
 * 並將這個新節點插入到跳躍表 zsl 中。
 * 
 * 函數的返回值爲新節點。
 *
 * T_wrost = O(N^2), T_avg = O(N log N)
 */
zskiplistNode *zslInsert(zskiplist *zsl, double score, robj *obj) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    unsigned int rank[ZSKIPLIST_MAXLEVEL];
    int i, level;

    redisAssert(!isnan(score));

    // 在各個層查找節點的插入位置
    // T_wrost = O(N^2), T_avg = O(N log N)
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {

        /* store rank that is crossed to reach the insert position */
        // 如果 i 不是 zsl->level-1 層
        // 那麼 i 層的起始 rank 值爲 i+1 層的 rank 值
        // 各個層的 rank 值一層層累積
        // 最終 rank[0] 的值加一就是新節點的前置節點的排位
        // rank[0] 會在後面成爲計算 span 值和 rank 值的基礎
        rank[i] = i == (zsl->level-1) ? 0 : rank[i+1];

        // 沿着前進指針遍歷跳躍表
        // T_wrost = O(N^2), T_avg = O(N log N)
        while (x->level[i].forward &&
            (x->level[i].forward->score < score ||
                // 比對分值
                (x->level[i].forward->score == score &&
                // 比對成員, T = O(N)
                compareStringObjects(x->level[i].forward->obj,obj) < 0))) {

            // 記錄沿途跨越了多少個節點
            rank[i] += x->level[i].span;

            // 移動至下一指針
            x = x->level[i].forward;
        }
        // 記錄將要和新節點相連接的節點
        update[i] = x;
    }

    /* we assume the key is not already inside, since we allow duplicated
     * scores, and the re-insertion of score and redis object should never
     * happen since the caller of zslInsert() should test in the hash table
     * if the element is already inside or not. 
     *
     * zslInsert() 的調用者會確保同分值且同成員的元素不會出現,
     * 所以這裏不需要進一步進行檢查,可以直接創建新元素。
     */

    // 獲取一個隨機值作爲新節點的層數
    // T = O(N)
    level = zslRandomLevel();

    // 如果新節點的層數比表中其他節點的層數都要大
    // 那麼初始化表頭節點中未使用的層,並將它們記錄到 update 數組中
    // 將來也指向新節點
    if (level > zsl->level) {

        // 初始化未使用層
        // T = O(1)
        for (i = zsl->level; i < level; i++) {
            rank[i] = 0;
            update[i] = zsl->header;
            update[i]->level[i].span = zsl->length;
        }

        // 更新表中節點最大層數
        zsl->level = level;
    }

    // 創建新節點
    x = zslCreateNode(level,score,obj);

    // 將前面記錄的指針指向新節點,並做相應的設置
    // T = O(1)
    for (i = 0; i < level; i++) {
        
        // 設置新節點的 forward 指針
        x->level[i].forward = update[i]->level[i].forward;
        
        // 將沿途記錄的各個節點的 forward 指針指向新節點
        update[i]->level[i].forward = x;

        /* update span covered by update[i] as x is inserted here */
        // 計算新節點跨越的節點數量
        x->level[i].span = update[i]->level[i].span - (rank[0] - rank[i]);

        // 更新新節點插入之後,沿途節點的 span 值
        // 其中的 +1 計算的是新節點
        update[i]->level[i].span = (rank[0] - rank[i]) + 1;
    }

    /* increment span for untouched levels */
    // 未接觸的節點的 span 值也需要增一,這些節點直接從表頭指向新節點
    // T = O(1)
    for (i = level; i < zsl->level; i++) {
        update[i]->level[i].span++;
    }

    // 設置新節點的後退指針
    x->backward = (update[0] == zsl->header) ? NULL : update[0];
    if (x->level[0].forward)
        x->level[0].forward->backward = x;
    else
        zsl->tail = x;

    // 跳躍表的節點計數增一
    zsl->length++;

    return x;
}

/* Internal function used by zslDelete, zslDeleteByScore and zslDeleteByRank 
 * 
 * 內部刪除函數,
 * 被 zslDelete 、 zslDeleteRangeByScore 和 zslDeleteByRank 等函數調用。
 *
 * T = O(1)
 */
void zslDeleteNode(zskiplist *zsl, zskiplistNode *x, zskiplistNode **update) {
    int i;

    // 更新所有和被刪除節點 x 有關的節點的指針,解除它們之間的關係
    // T = O(1)
    for (i = 0; i < zsl->level; i++) {
        if (update[i]->level[i].forward == x) {
            update[i]->level[i].span += x->level[i].span - 1;
            update[i]->level[i].forward = x->level[i].forward;
        } else {
            update[i]->level[i].span -= 1;
        }
    }

    // 更新被刪除節點 x 的前進和後退指針
    if (x->level[0].forward) {
        x->level[0].forward->backward = x->backward;
    } else {
        zsl->tail = x->backward;
    }

    // 更新跳躍表最大層數(只在被刪除節點是跳躍表中最高的節點時才執行)
    // T = O(1)
    while(zsl->level > 1 && zsl->header->level[zsl->level-1].forward == NULL)
        zsl->level--;

    // 跳躍表節點計數器減一
    zsl->length--;
}

/* Delete an element with matching score/object from the skiplist. 
 *
 * 從跳躍表 zsl 中刪除包含給定節點 score 並且帶有指定對象 obj 的節點。
 *
 * T_wrost = O(N^2), T_avg = O(N log N)
 */
int zslDelete(zskiplist *zsl, double score, robj *obj) {
    zskiplistNode *update[ZSKIPLIST_MAXLEVEL], *x;
    int i;

    // 遍歷跳躍表,查找目標節點,並記錄所有沿途節點
    // T_wrost = O(N^2), T_avg = O(N log N)
    x = zsl->header;
    for (i = zsl->level-1; i >= 0; i--) {

        // 遍歷跳躍表的複雜度爲 T_wrost = O(N), T_avg = O(log N)
        while (x->level[i].forward &&
            (x->level[i].forward->score < score ||
                // 比對分值
                (x->level[i].forward->score == score &&
                // 比對對象,T = O(N)
                compareStringObjects(x->level[i].forward->obj,obj) < 0)))

            // 沿着前進指針移動
            x = x->level[i].forward;

        // 記錄沿途節點
        update[i] = x;
    }

    /* We may have multiple elements with the same score, what we need
     * is to find the element with both the right score and object. 
     *
     * 檢查找到的元素 x ,只有在它的分值和對象都相同時,纔將它刪除。
     */
    x = x->level[0].forward;
    if (x && score == x->score && equalStringObjects(x->obj,obj)) {
        // T = O(1)
        zslDeleteNode(zsl, x, update);
        // T = O(1)
        zslFreeNode(x);
        return 1;
    } else {
        return 0; /* not found */
    }

    return 0; /* not found */
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章