1. 程式人生 > 其它 >Redis原始碼 - 有序集合

Redis原始碼 - 有序集合

關於有序集合的實現,從zaddGenericCommand函式說起:

從程式碼中我們發現有序集合在儲存時似乎使用了兩種方案:一種Zset,另一種是zsetlistpack。主要是根據zset_max_listpack_entries和zset_max_listpack_value來判斷。這兩個欄位什麼意思呢,我們在redis.conf中找到了這兩個引數的說明:

什麼意思呢?同Hashes、lists相似,有序集合為了節約大量儲存空間進行了特殊的編碼梳理。這種編碼僅在有序集合儲存的元素的長度在符合zset_max_listpack_entries和zset_max_listpack_value的設定時才生效。結合上面的程式碼就是,小資料用zsetlistpack,大資料用zset。或者設定zset_max_listpack_entries強制使用zset。這兩種資料型別究竟是個什麼玩意兒呢?

zsetlistpack就是簡單的listpack結構,說到底最終是一段malloc分配的地址。而zset就相對複雜,它是這樣的:

看過筆者redis原始碼系列的朋友應該知道dict是個什麼樣的資料結構。但zskiplist是什麼鬼呢?上圖中的註釋大致說這是skiplist的一個變種。也就是說這玩意兒的本質還是skiplist。那麼skiplist是什麼呢?skiplist專業術語叫跳躍連結串列(跳錶)。也就是說這玩意兒是一種連結串列。但是跳躍是什麼呢?怎麼跳。我們知道連結串列的訪問是從頭到尾式逐個遍歷,這種方式在資料少的時候沒啥問題,但是如果資料多了,就很耗時間。為了改善這種情況,有大佬就想,能不能不要逐個遍歷,比如跳過緊鄰的直接訪問下一個呢?來討論具體實現可能更容易理解:

在跳錶中,和1節點對比,如果大於1節點則直接到3節點。如果目標值小於3節點則回到2節點,如果大於3節點則跳轉5節點。依次這樣,整個查詢過程中也就減少了遍歷的節點個數,這種思想就是跳錶。基於這種思想我們可以對該結構進行擴充至跳過3個甚至更多節點,這樣也就大大減少了遍歷時間。更多跳躍表的細節這裡就不過多解讀,畢竟能力有限。到這裡key的儲存解決了,具體的值是怎麼儲存的呢?

具體的插入因為有序集合採用了兩種資料結構,因此是兩種插入方式,首先是資料量小時用的listpack:

至於具體的規則是從前往後還是從後往前,需要具體研究lpseek,此處不過多解讀,因為此處是一個類似雙向連結串列的遍歷。

對於跳錶結構,過程類似,只是redis的跳錶中增加了一個隨機層數,也就是前面所說的那個跳躍步長,選擇隨機步長主要用於解決redis在儲存過程中增刪改的具體問題。

名為小兵 不忘初心