一.編碼
? ? ? ? 有序集合的編碼可以是ziplist或者skiplist。
? ? ? ? ziplist編碼的有序集合對象使用壓縮列表作為底層實現,每一個集合元素使用緊挨在一起的兩個壓縮列表節點來保存。第一個節點保存元素的成員(member),而第二個元素則保存元素的分值(score)。
127.0.0.1:6379> zadd price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3
- 使用ziplist壓縮列表編碼?
如果price鍵的值對象使用的是ziplist編碼,那么這個值的對象和壓縮列表如下圖:
注意:Redis5.0版本后使用listpack替代了ziplist?Redis哈希對象(listpack介紹)-CSDN博客
- 使用skiplist編碼?
? ? ? ? ?skiplist編碼的有序集合對象使用zset結構作為底層實現,一個zset結構同時包含一個字典和一個跳躍表。字典和跳躍表都會使用到。
typedef struct zset {dict *dict;zskiplist *zsl;
} zset;
? ? ? ? zset結構中的zsl跳躍表按分值從小到大保存了所有集合元素,每一個跳躍表節點保存了一個集合元素:跳躍表的節點的object屬性保存了元素的成員,而跳躍表節點的score屬性則保存了元素的分值。通過這個跳躍表,程序程序可以對有序集合進行范圍查詢操作,比如:zrank,zrange等命令就是基于跳躍表的API實現的。
? ? ? ? 而zset結構中的dict字典為有序集合創建了一個成員到分值的映射,字典中的每一個鍵值對都保存了一個集合元素:字典中的鍵保存了元素成員,而字典中的值保存了元素的分值。通過這個字典,程序可以通過O(1)復雜度查找給定成員的分值,zscore命令就是根據這一特性實現的。而很多其他有序集合的命令都是通過這一特性實現的。
? ? ? ? 有序集合每一個成員都是一個字符串對象,而每一個元素的分值都是一個double類型的浮點數。
? ? ? ? 雖然zset結構同時使用跳躍表和字典來保存有序集合元素,但這兩種數據結構都通過指針來共享相同元素的成員和分值,所以同時使用跳躍表和字典老保存有序集合元素不會產生任何重復成員和分值,也不會因此而浪費額外的內存。
為什么有序集合需要同時使用跳躍表和字典來實現?
? ? ? ? 在理論上,有序集合可以單獨使用字典或者跳躍表來實現。但是,無論是單獨使用跳躍表還是字典,在性能上會比同時使用字典和跳躍表有所降低。
? ? ? ? 如果只使用字典來實現有序集合,雖然可以在O(1)時間復雜度內找到對應成員的分值。但是,因為字典是無序的方式來保存元素。所以在內存執行范圍型操作——比如:zrank,zrange等命令時,需要先將字典中的元素按照分值進行排序,完成排序至少需要O(NlogN)時間復雜度,以及額外的O(N)內存空間來保存排序好的元素。
? ? ? ? 如果只使用跳躍表來實現有序集合,那么跳躍表執行范圍型操作的所有優點會保存下來,但是,根據成員查找分值的操作,會從O(1)的時間時間復雜度提高到O(logN)。
? ? ? ? 所以為了提高效率,有序集合同時使用了跳躍表和字典兩種數據結構了實現。
? ? ? ? 如果上面price鍵創建使用的時skiplist編碼的有序集合對象,那么這個有序結合對象和zset將會如下圖所示:
? ? ? ? 注意:下圖為了展示清楚,重復展示了各個成員和分值,但是實際中,字典和跳躍表會共享元素和分值。
二.編碼轉換
?????????當有序集合同時滿足下面兩個條件時,對象使用ziplist編碼,redis5.0之后使用listpack編碼。
- 有序集合保存的元素個數小于128個。
- 有序集合保存的所有元素成員的長度小于64字節。
當面兩個的上限值可以通過配置,zset-max-ziplist-entries選項和zset-max-ziplist-value選項來修改。