quicklist vs ziplist

在一次使用redis list的時候罐栈,使用object encoding命令查看編碼類型烫沙。時出現(xiàn)了quicklist

192.168.99.100:6379> lpush list a b c d
(integer) 4
192.168.99.100:6379> object encoding list
"quicklist"
192.168.99.100:6379>

而我只記得linkedlistziplist搬素。 這次抽了個時間來翻翻源碼绷耍,為了弄清楚:

list對象的linkedlist編碼祈匙、ziplist編碼忽刽、以及編碼轉(zhuǎn)換機(jī)制是否還存在?

List

redis/src/t_list.c(3.0)

# 使用LPUSH命令夺欲,lpushCommand會被調(diào)用
void lpushCommand(redisClient *c) {
    pushGenericCommand(c,REDIS_HEAD);
}

void pushGenericCommand(redisClient *c, int where) {
    int j, waiting = 0, pushed = 0;
    # 如果命令是 LPUSH key value1 value2 
    # 則這里的argv = ["LPUSH", "key", "value1", "value2"]
    robj *lobj = lookupKeyWrite(c->db,c->argv[1]); 

    # 如果key對應(yīng)的類型不是list會報錯并退出
    if (lobj && lobj->type != REDIS_LIST) {
        addReply(c,shared.wrongtypeerr);
        return;
    }

    # 每一次循環(huán)就是在處理LPUSH操作的一個值
    for (j = 2; j < c->argc; j++) {
        # 嘗試將argv[j]轉(zhuǎn)換成合適的編碼:RAW跪帝、EMBSTR、INT
        c->argv[j] = tryObjectEncoding(c->argv[j]);
        # list的自動創(chuàng)建些阅,創(chuàng)建的list默認(rèn)使用Ziplist編碼
        if (!lobj) {
            lobj = createZiplistObject();
            dbAdd(c->db,c->argv[1],lobj);
        }
        # Push的操作在listTypePush
        listTypePush(lobj,c->argv[j],where);
        pushed++;
    }
    # 命令回復(fù)
    addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0));
    if (pushed) {
        char *event = (where == REDIS_HEAD) ? "lpush" : "rpush";
        # 發(fā)送信號:key已修改
        signalModifiedKey(c->db,c->argv[1]);
        # 發(fā)送鍵空間伞剑、鍵時間通知
        notifyKeyspaceEvent(REDIS_NOTIFY_LIST,event,c->argv[1],c->db->id);
    }
    server.dirty += pushed;
}

void listTypePush(robj *subject, robj *value, int where) {
    # 判斷value是否滿足編碼轉(zhuǎn)換條件
    listTypeTryConversion(subject,value);

    # 判斷l(xiāng)ist是否滿足編碼轉(zhuǎn)換條件
    # 如果list長度大于等于server.list_max_ziplist_entries
    # 那么list會從Ziplist轉(zhuǎn)換成Linkedlist編碼
    if (subject->encoding == REDIS_ENCODING_ZIPLIST &&
        ziplistLen(subject->ptr) >= server.list_max_ziplist_entries)
            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
   
    # 針對不同編碼,調(diào)用不同API來增加list節(jié)點
    if (subject->encoding == REDIS_ENCODING_ZIPLIST) {
        int pos = (where == REDIS_HEAD) ? ZIPLIST_HEAD : ZIPLIST_TAIL;
        value = getDecodedObject(value);
        subject->ptr = ziplistPush(subject->ptr,value->ptr,sdslen(value->ptr),pos);
        decrRefCount(value);
    } else if (subject->encoding == REDIS_ENCODING_LINKEDLIST) {
        if (where == REDIS_HEAD) {
            listAddNodeHead(subject->ptr,value);
        } else {
            listAddNodeTail(subject->ptr,value);
        }
        incrRefCount(value);
    } else {
        redisPanic("Unknown list encoding");
    }
}

# 如果添加的value是sds(Simple Dynamic String), sds可以是raw或embstr編碼
# 且sds的長度大于server.list_max_ziplist_value
# 那么list會從Ziplist轉(zhuǎn)換成Linkedlist編碼
void listTypeTryConversion(robj *subject, robj *value) {
    if (subject->encoding != REDIS_ENCODING_ZIPLIST) return;
    if (sdsEncodedObject(value) &&
        sdslen(value->ptr) > server.list_max_ziplist_value)
            listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
}

這是我所熟知的list編碼轉(zhuǎn)換機(jī)制扑眉,而quicklist是redis 3.2新加入的結(jié)構(gòu)纸泄。那我直接去看了redis 4.0的版本赖钞,了解下list編碼轉(zhuǎn)換機(jī)制是否有所改變腰素。

redis/src/t_list.c(4.0)

void pushGenericCommand(client *c, int where) {
    ...
    for (j = 2; j < c->argc; j++) {
        if (!lobj) {
            # list自動創(chuàng)建時,使用的是quicklist
            lobj = createQuicklistObject();
            quicklistSetOptions(lobj->ptr, server.list_max_ziplist_size,
                                server.list_compress_depth);
            dbAdd(c->db,c->argv[1],lobj);
        }
        listTypePush(lobj,c->argv[j],where);
        pushed++;
    }
    ...
}

void listTypePush(robj *subject, robj *value, int where) {
    # 添加元素時雪营,如果不是quicklist編碼就會報錯
    # 那很大程度上意味著list只有一種編碼了
    if (subject->encoding == OBJ_ENCODING_QUICKLIST) {
        int pos = (where == LIST_HEAD) ? QUICKLIST_HEAD : QUICKLIST_TAIL;
        value = getDecodedObject(value); # 確保value是一個字符串對象弓千,而不是INT
        size_t len = sdslen(value->ptr);
        # 直接調(diào)用的quicklist API
        quicklistPush(subject->ptr, value->ptr, len, pos);
        decrRefCount(value);  # 引用計數(shù)器
    } else {
        serverPanic("Unknown list encoding");
    }
}

3.2版本的listTypePush會根據(jù)不同的編碼調(diào)用不同數(shù)據(jù)結(jié)構(gòu)的API,而4.0直接調(diào)用了quicklist的API献起,以前的編碼轉(zhuǎn)換的機(jī)制已經(jīng)不存在了洋访。

Quicklist

這次的目的已達(dá)到,但還是來了解下quicklist
quicklist - A doubly linked list of ziplists

typedef struct quicklist {
    quicklistNode *head;
    quicklistNode *tail;
    unsigned long count;        /* total count of all entries in all ziplists */
    unsigned long len;          /* number of quicklistNodes */
    int fill : 16;              /* fill factor for individual nodes */
    unsigned int compress : 16; /* depth of end nodes not to compress;0=off */
} quicklist;

typedef struct quicklistNode {
    struct quicklistNode *prev;
    struct quicklistNode *next;
    unsigned char *zl;
    unsigned int sz;             /* ziplist size in bytes */
    unsigned int count : 16;     /* count of items in ziplist */
    unsigned int encoding : 2;   /* RAW==1 or LZF==2 */
    unsigned int container : 2;  /* NONE==1 or ZIPLIST==2 */
    unsigned int recompress : 1; /* was this node previous compressed? */
    unsigned int attempted_compress : 1; /* node can't compress; too small */
    unsigned int extra : 10; /* more bits to steal for future usage */
} quicklistNode;

typedef struct quicklistEntry {
    const quicklist *quicklist;
    quicklistNode *node;
    unsigned char *zi;
    unsigned char *value;
    long long longval;
    unsigned int sz;
    int offset;
} quicklistEntry;
  • quicklist 是一個雙向鏈表谴餐,head姻政、tail分別指向頭尾節(jié)點
  • quicklistNode 是雙向鏈表的節(jié)點,prev岂嗓、next分別指向前驅(qū)汁展、后繼結(jié)點
  • quicklistNode.zl 指向一個ziplist(或者quicklistLZF結(jié)構(gòu))
  • quicklistEntry 包裹著list的每一個值,作為ziplist的一個節(jié)點

可以想象得到厌殉,當(dāng)一個空的quicklist加入一個值value時食绿,會有以下操作(不一定以這個順序):

  1. 使用Entry包裹value
  2. 創(chuàng)建一個ziplist,把Entry加入到ziplist中
  3. 創(chuàng)建一個Node公罕,Node.zl指向ziplist
  4. 創(chuàng)建quicklist器紧,將Node加入quicklist中

原來版本的list直接使用的一個ziplist,而現(xiàn)在版本的list可以看成使用多個ziplist楼眷,然后通過雙向鏈表連接起來铲汪。如果知道ziplist的特性熊尉,那這樣的優(yōu)點就很明顯了。

由于ziplist的連鎖更新掌腰。ziplist的插入刪除操作在最壞情況下的復(fù)雜度為O(n^2)帽揪,雖然導(dǎo)致整條ziplist連鎖更新的概率很低。將原來的ziplist劃分成多條ziplist后辅斟,插入刪除一個元素转晰,即使情況再極端也只會引起一段ziplist的連鎖更新。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末士飒,一起剝皮案震驚了整個濱河市查邢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酵幕,老刑警劉巖扰藕,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芳撒,居然都是意外死亡邓深,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門笔刹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芥备,“玉大人,你說我怎么就攤上這事舌菜∶瓤牵” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵日月,是天一觀的道長袱瓮。 經(jīng)常有香客問我,道長爱咬,這世上最難降的妖魔是什么尺借? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮精拟,結(jié)果婚禮上燎斩,老公的妹妹穿的比我還像新娘。我一直安慰自己串前,他們只是感情好瘫里,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著荡碾,像睡著了一般谨读。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上坛吁,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天劳殖,我揣著相機(jī)與錄音铐尚,去河邊找鬼。 笑死哆姻,一個胖子當(dāng)著我的面吹牛宣增,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播矛缨,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼爹脾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了箕昭?” 一聲冷哼從身側(cè)響起灵妨,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎落竹,沒想到半個月后泌霍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡述召,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年朱转,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片积暖。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡藤为,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出呀酸,到底是詐尸還是另有隱情凉蜂,我是刑警寧澤琼梆,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布性誉,位于F島的核電站,受9級特大地震影響茎杂,放射性物質(zhì)發(fā)生泄漏错览。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一煌往、第九天 我趴在偏房一處隱蔽的房頂上張望倾哺。 院中可真熱鬧,春花似錦刽脖、人聲如沸羞海。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽却邓。三九已至,卻和暖如春院水,著一層夾襖步出監(jiān)牢的瞬間腊徙,已是汗流浹背简十。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留撬腾,地道東北人螟蝙。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像民傻,于是被迫代替她去往敵國和親胰默。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容