Redis 有序集合对象

有序集合的编码可以是 ziplist 或者 skiplist

ziplist 编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个挨在一起的压缩列表节点来保存,第一个节点保存元素成员,第二个节点保存元素分值

压缩列表内的集合元素按分值从小到大进行排序

ziplist 编码的有序集合对象
有序集合元素在压缩列表中按分值从小到大排序

skiplist 编码的有序集合对象使用 zset 结构作为底层实现,一个 zset 结构同时包含一个字典和一个跳跃表:

typedef struct zset {
    
    zskiplist *zsl;

    dict *dict;

} zset;

zset 结构中的 zsl 跳跃表按分值从小到大保存了所有集合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的 object 属性保存了元素的成员,而跳跃表节点的 score 属性则保存了元素的分值。通过跳跃表,程序可以对有序集合进行范围型操作,比如 ZRANK、ZRANGE 等

除此之外,zset 结构中的 dict 字典为有序集合创建了一个从成员到分值的映射,字典中每个键值对都保存了一个集合元素:字典的键保存了元素的成员,字典的值则保存了元素的分值。通过字典,程序可以 O(1) 时间内查找给定成员的分值(如 ZSCORE)

有序集合每个元素成员都是一个字符串对象,而每个元素的分值都是一个 double 类型的浮点数

skiplist 编码的有序集合对象
有序集合对象元素同时被保存在字典和跳跃表中

虽然 zset 结构同时使用跳跃表和字典来保存有序集合元素,但这两种数据结构会通过指针来共享元素的成员和分值,所以不会造成数据重复,也不会因此浪费内存

为什么同时使用两种数据结构

理论上单独使用一种数据结构也可以实现,但是无论单独使用哪种数据结构,在性能上对比起同时使用字典和跳跃表都会有所降低

如果只使用字典实现,那么虽然以 O(1) 时间查找成员分值这一特性会被保留,但是因为字典以无需的方式来保存集合元素,所以每次执行范围型操作时,程序都需要对字典保存的所有元素进行排序,完成这种排序需要至少 O(NlogN) 时间,以及额外 O(N) 空间(因为要创建一个数组来保存排序后的元素)

如果只使用跳跃表实现,那么跳跃表执行范围型操作的所有优点都会被保留,但因为没有字典,根据成员查找分值这一操作的复杂度将从 O(1) 上升为 O(logN)

为了让有序集合的查找和范围型操作都尽快执行,Redis 选择同时使用字典和跳跃表两种数据结构来实现有序集合

编码的转换

当有序集合对象可以同时满足以下两个条件时,对象使用 ziplist 编码:

  • 有序集合保存的元素数量小于 128 个
  • 有序集合保存的所有元素成员长度都小于 64 字节

不能满足以上两个条件的有序结合对象将使用 skiplist 编码

以上两个条件的上限值可以被修改,由 zset-max-ziplist-entries 和 zset-max-ziplist-value 控制

你可能感兴趣的:(Redis 有序集合对象)