Zset 编码的选择
在通过 ZADD 命令添加第一个元素到空 key 时, Redis 会通过检查输入的第一个元素来决定使用何种编码。
如果第一个元素符合以下条件的话, 就创建一个 REDIS_ENCODING_ZIPLIST
编码的 Zset:
- 服务器属性
server.zset_max_ziplist_entries
的值大于默认值 (默认为128
)。 - 元素的
member
长度小于服务器属性server.zset_max_ziplist_value
的值(默认为64
)。
否则,创建一个 REDIS_ENCODING_SKIPLIST
编码的 Zset。
对于一个 REDIS_ENCODING_ZIPLIST
编码的 Zset, 只要满足以下任一条件, 则会被转换为 REDIS_ENCODING_SKIPLIST
编码:
ziplist
所保存的元素数量超过服务器属性server.zset_max_ziplist_entries
的值(默认值为128
)- 新添加元素的
member
的长度大于服务器属性server.zset_max_ziplist_value
的值(默认值为64
)
ziplist原理
ziplist 编码的 Zset 使用紧挨在一起的压缩列表节点来保存,第一个节点保存 member,第二个保存 score。ziplist 内的集合元素按 score 从小到大排序,其实质是一个双向链表。虽然元素是按 score
有序排序的, 但对 ziplist
的节点指针只能线性地移动,所以在 REDIS_ENCODING_ZIPLIST
编码的 Zset 中, 查找某个给定元素的复杂度为 O(N)。
skiplist原理
skiplist 编码的 Zset 底层为一个被称为 zset 的结构体,这个结构体中包含一个字典和一个跳跃表。跳跃表按 score 从小到大保存所有集合元素,查找时间复杂度为平均 O(logN),最坏 O(N)。
字典则保存着从 member 到 score 的映射,这样就可以用 O(1) 的复杂度来查找 member 对应的 score 值。虽然同时使用两种结构,但它们会通过指针来共享相同元素的 member 和 score,因此不会浪费额外的内存。
c++
/* zset结构体 */
typedef struct zset {
// 字典,维护元素值和分值的映射关系
dict *dict;
// 按分值对元素值排序序,支持O(logN)数量级的查找操作
zskiplist *zsl;
} zset;