REDIS_ZSET (有序集) 是ZADD 、ZCOUNT 等命令的操作對象, 它使用
REDIS_ENCODING_ZIPLIST 和REDIS_ENCODING_SKIPLIST 兩種方式編碼:
編碼的選擇
在通過ZADD 命令添加第一個元素到空key 時,程序通過檢查輸入的第一個元素來決定該創
建什麼編碼的有序集。
如果第一個元素符合以下條件的話,就創建一個REDIS_ENCODING_ZIPLIST 編碼的有序集:
服務器屬性server.zset_max_ziplist_entries 的值大於0 (默認爲128 )。
元素的member 長度小於服務器屬性server.zset_max_ziplist_value 的值(默認爲64
)。
否則,程序就創建一個REDIS_ENCODING_SKIPLIST 編碼的有序集。
編碼的轉換
對於一個REDIS_ENCODING_ZIPLIST 編碼的有序集,只要滿足以下任一條件,就將它轉換爲
REDIS_ENCODING_SKIPLIST 編碼:
ziplist 所保存的元素數量超過服務器屬性server.zset_max_ziplist_entries 的值
(默認值爲128 )
新添加元素的member 的長度大於服務器屬性server.zset_max_ziplist_value 的值
(默認值爲64 )
ZIPLIST 編碼的有序集
當使用REDIS_ENCODING_ZIPLIST 編碼時,有序集將元素保存到ziplist 數據結構裏面。
其中,每個有序集元素以兩個相鄰的ziplist 節點表示,第一個節點保存元素的member 域,
第二個元素保存元素的score 域。
多個元素之間按score 值從小到大排序,如果兩個元素的score 相同,那麼按字典序對member
進行對比,決定那個元素排在前面,那個元素排在後面。
雖然元素是按score 域有序排序的,但對ziplist 的節點指針只能線性地移動,所以在
REDIS_ENCODING_ZIPLIST 編碼的有序集中,查找某個給定元素的複雜度爲O(N) 。
每次執行添加/刪除/更新操作都需要執行一次查找元素的操作,因此這些函數的複雜度都不低
於O(N) ,至於這些操作的實際複雜度,取決於它們底層所執行的ziplist 操作。
SKIPLIST 編碼的有序集
當使用REDIS_ENCODING_SKIPLIST 編碼時,有序集元素由redis.h/zset 結構來保存:
/* * 有序集 */ typedef struct zset { // 字典 dict *dict; // 跳躍表 zskiplist *zsl; } zset;
zset 同時使用字典和跳躍表兩個數據結構來保存有序集元素。
其中,元素的成員由一個redisObject 結構表示,而元素的score 則是一個double 類型的浮
點數,字典和跳躍表兩個結構通過將指針共同指向這兩個值來節約空間(不用每個元素都複製
兩份)。
下圖展示了一個REDIS_ENCODING_SKIPLIST 編碼的有序集:
通過使用字典結構,並將member 作爲鍵,score 作爲值,有序集可以在O(1) 複雜度內:
檢查給定member 是否存在於有序集(被很多底層函數使用);
取出member 對應的score 值(實現ZSCORE 命令)。
另一方面,通過使用跳躍表,可以讓有序集支持以下兩種操作:
在O(logN) 期望時間、O(N) 最壞時間內根據score 對member 進行定位(被很多底層
函數使用);
範圍性查找和處理操作,這是(高效地)實現ZRANGE 、ZRANK 和ZINTERSTORE
等命令的關鍵。
通過同時使用字典和跳躍表,有序集可以高效地實現按成員查找和按順序查找兩種操作。