Redis ZSet底层实现

Zset原理

有序集合对象是有序的。与列表使用索引下标作为排序依据不同,有序集合为每个元素设置一个分数(score)作为排序依据

ZSet底层如何实现

一、使用ziplist。
  1. 前提:保存元素数量小于128,并且每个元素长度小于64字节
    (这两个参数可以通过zset-max-ziplist-entries 选项和 zset-max-ziplist-value 进行修改)

  2. ziplist原理:
    压缩列表(ziplist)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值。

    image
image
  • 每个节点组成如图。previous_entry_length保存前一个节点的长度,遍历时可根据定位到前一个节点。encoding存储content的类型和长度。content保存节点的内容
二、使用字典和跳跃表
typedef struct zset{
     //跳跃表
     zskiplist *zsl;
     //字典
     dict *dice;
} zset;

字典的键保存元素的值,字典的值则保存元素的分值;跳跃表节点的 object 属性保存元素的值,跳跃表节点的 score 属性保存元素的分值。

为什么不直接用跳跃表

假如我们单独使用 字典,虽然能以 O(1) 的时间复杂度查找成员的分值,但是因为字典是以无序的方式来保存集合元素,所以每次进行范围操作的时候都要进行排序;假如我们单独使用跳跃表来实现,虽然能执行范围操作,但是查找操作有 O(1)的复杂度变为了O(logN)。因此Redis使用了两种数据结构来共同实现有序集合。

字典

字典中的键是唯一的,可以通过key来查找值

  • 字典底层实现是哈希表,字典有两个哈希表,一个在扩容时使用,哈希表扩容使用渐进式扩容,发送扩容时需要在两个哈希表中进行搜索。

    image
  • 发生哈希冲突时使用链地址法解决

跳跃表

跳跃表(skiplist)是一种有序数据结构,它通过在每个节点中维持多个指向其它节点的指针,从而达到快速访问节点的目的。

image
  1. 性质
  • 由很多层结构组成
  • 每一层都是一个有序的链表,排列顺序为由高层到底层,都至少包含两个链表节点,分别是前面的head节点和后面的nil节
  • 最底层的链表包含了所有的元素
  • 如果一个元素出现在某一层的链表中,那么在该层之下的链表也全都会出现(上一层的元素是当前层的元素的子集)
  • 链表中的每个节点都包含两个指针,一个指向同一层的下一个链表节点,另一个指向下一层的同一个链表节点;
  1. 定义
typedef struct zskiplistNode {
     //层
     struct zskiplistLevel{
           //前进指针
           struct zskiplistNode *forward;
           //跨度
           unsigned int span;
     }level[];

     //后退指针
     struct zskiplistNode *backward;
     //分值
     double score;
     //成员对象
     robj *obj;

} zskiplistNode

typedef struct zskiplist{
     //表头节点和表尾节点
     structz skiplistNode *header, *tail;
     //表中节点的数量
     unsigned long length;
     //表中层数最大的节点的层数
     int level;

}zskiplist;

  1. 操作
  • 搜索:从最高层的链表节点开始,如果比当前节点要大和比当前层的下一个节点要小,那么则往下找,也就是和当前层的下一层的节点的下一个节点进行比较,以此类推,一直找到最底层的最后一个节点,如果找到则返回,反之则返回空。
  • 插入:首先确定插入的层数,有一种方法是假设抛一枚硬币,如果是正面就累加,直到遇见反面为止,最后记录正面的次数作为插入的层数。当确定插入的层数k后,则需要将新元素插入到从底层到k层。
  • 删除:在各个层中找到包含指定值的节点,然后将节点从链表中删除即可,如果删除以后只剩下头尾两个节点,则删除这一层。

转自:https://www.jianshu.com/p/56defc93b024

你可能感兴趣的:(Redis ZSet底层实现)