深入了解Redis中Zset底层结构

603 阅读2分钟
  • 存储类型与操作命令
类型(Type key)存储编码(OBJECT encoding key)实例命令
zsetziplistzadd myzset 10 java 20 php 30 ruby 40 cpp 50 python
zsetskiplistzadd myzset 20 xxxx(value超过64个字节)

问题:Redis是如何决定存储ziplist还是skiplist?

答:在Redis配置文件有两个属性zset-max-ziplist-entries 128 ;zset-max-ziplist-value 64(字节),当集合内元素小于128,所有元素长度小于64字节使用ziplist,若无法满足ziplist条件时skiplist作为内部实现

问题:若存储的内分值相同Redis如何处理?

答:所有节点的分值是按从小到大的方式排序的,当有序集合的成员分值相同时,节点 会按ele(见下)的字典序进行排序

存储原理

  • skiplist
typedef struct zskiplistNode {
  sds ele;
  double score;
  struct zskiplistNode *backward;
  struct zskiplistLevel {
    struct zskiplistNode *forward;
    unsigned int span;
  } level[];
} zskiplistNode;

/**
 * 跳跃表结构体
 */
typedef struct zskiplist {
    struct zskiplistNode *header, *tail;
    unsigned long length;
    int level;
} zskiplist;

/**
 * 有序集合结构体
 */
typedef struct zset {
    /*
     * Redis 会将跳跃表中所有的元素和分值组成 
     * key-value 的形式保存在字典中
     * todo:注意:该字典并不是 Redis DB 中的字典,只属于有序集合
     */
    dict *dict;
    /*
     * 底层指向的跳跃表的指针
     */
    zskiplist *zsl;
} zset;

属性说明
ele用于存储字符串类型的数据
score用于存储排序的分值
backward后退指针,只能指向当前节点最底层的前一个节 点,头节点和第一个节点——backward指向NULL,从后向前遍历跳 跃表时使用
level为柔性数组。每个节点的数组长度不一样,在生成跳跃 表节点时,随机生成一个1~64的值,值越大出现的概率越低
forward指向本层下一个节点,尾节点的forward指向NULL
spanforward指向的节点与本节点之间的元素个数。span值越 大,跳过的节点个数越多;跨度实际上是用来计算元素排名(rank)的,在查找某个节点的过程中,将沿途访过的所有层的跨度累积起来,得到的结果就是目标节点在跳跃表中的排位
  • 结构示意图

skiplist结构图.png

跳表具有如下性质: (1) 由很多层结构组成 (2) 每一层都是一个有序的链表 (3) 最底层(Level 1)的链表包含所有元素 (4) 如果一个元素出现在 Level i 的链表中,则它在 Level i 之下的链表也都会出现。 (5) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

应用场景

  • 排行榜