1. 程式人生 > >redis-zset數據結構探索

redis-zset數據結構探索

結果 AC ddr spa ict tel dict 排序 ntb

redis用的人比較多,其中zset大家都熟悉,主要用於排名場景。
zset數據結構,分成兩部分,一部分是用於排序,一部分用於緩存鍵值。
先看看結構:

typedef struct zset {
    dict *dict;         //緩存
    zskiplist *zsl;     //排序結構
} zset;

上面,跳躍表用於排序結構,可以按照名次,積分查找對應鍵, 時間復雜度: log(n)。
按照名次,積分範圍查找一系列鍵時, 先查詢滿足條件的第一個鍵,然後當前鍵查找後續鍵, 時間復雜度: log(n) + o(m), n=總鍵數, m=查詢結果鍵數。

跳躍表結構:

typedef struct
zskiplist { struct zskiplistNode *header, *tail; //結點頭:用於順序查詢,常用方式; 結點尾:用於倒序簡單查詢。 unsigned long length; //結點數 int level; //跳躍層級 } zskiplist;

結點結構:

typedef struct zskiplistNode {
    robj *obj;                                  //
double score; //積分 struct zskiplistNode *backward; //前一個結點, 和level[0]可看作雙鏈表 struct zskiplistLevel { //跳躍層關系, 每層都是單鏈表 struct zskiplistNode *forward; //此層下一個結點 unsigned int span; //此層下一個結點和當前結點距離(兩者隔了多少結點)
} level[]; //最多32層 } zskiplistNode;


查詢:
根據名次範圍查詢

void zrangeGenericCommand(client *c, int reverse) {
        ......

        zset *zs = zobj->ptr;           //zset結構變量
        zskiplist *zsl = zs->zsl;       //跳躍表
        zskiplistNode *ln;
        robj *ele;

        /* Check if starting point is trivial, before doing log(N) lookup. */
        if (reverse) {                  //是否倒序查詢
            ln = zsl->tail;             //默認取尾結點
            if (start > 0)
                ln = zslGetElementByRank(zsl,llen-start);       //如果start>0, 則取對應結點
        } else {
            ln = zsl->header->level[0].forward; //默認取第一個結點
            if (start > 0)
                ln = zslGetElementByRank(zsl,start+1);
        }

        while(rangelen--) {             //取rangelen個結點
            serverAssertWithInfo(c,zobj,ln != NULL);
            ele = ln->obj;
            addReplyBulk(c,ele);        //響應鍵名
            if (withscores)
                addReplyDouble(c,ln->score);    //響應鍵值
            ln = reverse ? ln->backward : ln->level[0].forward; //設置下一個結點
        }

        ......
}

/* Finds an element by its rank. The rank argument needs to be 1-based. */
zskiplistNode* zslGetElementByRank(zskiplist *zsl, unsigned long rank) {
    zskiplistNode *x;
    unsigned long traversed = 0;                //當前名次
    int i;

    x = zsl->header;                            //頭結點, 從頭結點的下一個結點遍歷
    for (i = zsl->level-1; i >= 0; i--) {       //從高層到低層鏈表遍歷
        while (x->level[i].forward && (traversed + x->level[i].span) <= rank)   //如果有下一個結點,且下一個結點的名次<=rank
        {
            traversed += x->level[i].span;
            x = x->level[i].forward;
        }
        if (traversed == rank) {                //找到對應名次的結點
            return x;
        }
    }
    return NULL;
}

zslGetElementByRank()時間復雜度理想值 = log(n)

...

redis-zset數據結構探索