REDIS_ZSET (有序集) 是ZADD 、ZCOUNT 等命令的操做對象, 它使用redis
REDIS_ENCODING_ZIPLIST 和REDIS_ENCODING_SKIPLIST 兩種方式編碼:服務器
編碼的選擇數據結構
在經過ZADD 命令添加第一個元素到空key 時,程序經過檢查輸入的第一個元素來決定該創ide
建什麼編碼的有序集。函數
若是第一個元素符合如下條件的話,就建立一個REDIS_ENCODING_ZIPLIST 編碼的有序集:編碼
服務器屬性server.zset_max_ziplist_entries 的值大於0 (默認爲128 )。spa
元素的member 長度小於服務器屬性server.zset_max_ziplist_value 的值(默認爲64指針
)。server
不然,程序就建立一個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
等命令的關鍵。
經過同時使用字典和跳躍表,有序集能夠高效地實現按成員查找和按順序查找兩種操做。