本文我们要剖析的基本类型是ZSet,下面我们将深入源码剖析RedisZSet的实现。


ZSet 有序集合


存储类型

深入学习Redis( 六),基本类型【ZSet】剖析

1、sorted set,有序的 set,每个元素有个 score。


2、score 相同时,按照 key 的 ASCII 码排序。

数据结构对比

深入学习Redis( 六),基本类型【ZSet】剖析

操作命令


添加元素



zadd myzset 10 java 20 php 30 ruby 40 cpp 50 python

获取全部元素



zrange myzset 0 -1 withscores


zrevrange myzset 0 -1 withscores

根据分值区间获取元素



zrangebyscore myzset 20 30

移除元素,也可以根据 score rank 删除



zrem myzset php cpp

统计元素个数



zcard myzset

分值递增



zincrby myzset 5 python

根据分值统计个数



zcount myzset 20 60

获取元素 rank



zrank myzset java

获取元素 score



zsocre myzset java

也有倒序的 rev 操作(reverse)


存储( 实现) 原理


同时满足以下条件时使用 ziplist 编码:


1、元素数量小于 128 个


2、所有 member 的长度都小于 64 字节


在 ziplist 的内部,按照 score 排序递增来存储。插入的时候要移动之后的数据。


对应 redis.conf 参数:

深入学习Redis( 六),基本类型【ZSet】剖析

超过阈值之后,使用 skiplist+dict 存储。


问题1、什么是 skiplist?


我们先来看一下有序链表:

深入学习Redis( 六),基本类型【ZSet】剖析

在这样一个链表中,如果我们要查找某个数据,那么需要从头开始逐个进行比较,直到找到包含数据的那个节点,或者找到第一个比给定数据大的节点为止(没找到)。


也就是说,时间复杂度为 O(n)。同样,当我们要插入新数据的时候,也要经历同样的查找过程,从而确定插入位置。


而二分查找法只适用于有序数组,不适用于链表。


假如我们每相邻两个节点增加一个指针(或者理解为有三个元素进入了第二层),让指针指向下下个节点。

深入学习Redis( 六),基本类型【ZSet】剖析

这样所有新增加的指针连成了一个新的链表,但它包含的节点个数只有原来的一半(上图中是 7, 19, 26)。在插入一个数据的时候,决定要放到那一层,取决于一个算法(在 redis 中 t_zset.c 有一个 zslRandomLevel 这个方法)。


现在当我们想查找数据的时候,可以先沿着这个新链表进行查找。当碰到比待查数据大的节点时,再回到原来的链表中的下一层进行查找。比如,我们想查找 23,查找的路径是沿着下图中标红的指针所指向的方向进行的:

深入学习Redis( 六),基本类型【ZSet】剖析

1. 23 首先和 7 比较,再和 19 比较,比它们都大,继续向后比较。

2. 当23 和 26 比较的时候,比 26 要小,因此回到下面的链表(原链表),与 22比较。


3. 23 比 22 要大,沿下面的指针继续向后和 26 比较。23 比 26 小,说明待查数据 23 在原链表中不存在。


在这个查找过程中,由于新增加的指针,我们不再需要与链表中每个节点逐个进行比较了。需要比较的节点数大概只有原来的一半。这就是跳跃表。


为什么不用 AVL 树或者红黑树?因为 skiplist 更加简洁。


源码展示如下:



typedef struct zskiplistNode {
sds ele
; /* zset 的元素 */

double score; /* 分值 */

struct zskiplistNode *backward; /* 后退指针 */

struct zskiplistLevel {

struct zskiplistNode *forward; /* 前进指针, 对应 level 的下一个节点 */

unsigned long span; /* 从当前节点到下一个节点的跨度(跨越的节点数) */

} level[]; /* 层 */

} zskiplistNode;


typedef struct zskiplist {

struct zskiplistNode *header, *tail; /* 指向跳跃表的头结点和尾节点 */

unsigned long length; /* 跳跃表的节点数 */

int level; /* 最大的层数 */

} zskiplist;



typedef struct zset {
dict
*dict;
zskiplist
*zsl;

} zset;

随机获取层数的函数(源码):



int zslRandomLevel(void) {
 
int level = 1;
 
while ((random()&0xFFFF) < (ZSKIPLIST_P * 0xFFFF))
 level
+= 1;
 
return (level<ZSKIPLIST_MAXLEVEL) ? level : ZSKIPLIST_MAXLEVEL;}

应用场景


排行榜


id 为 6001 的新闻点击数加 1:zincrby hotNews:20190926 1 n6001


获取今天点击最多的 15 条:zrevrange hotNews:20190926 0 15 withscores

深入学习Redis( 六),基本类型【ZSet】剖析





相关文章:

  • 2021-12-08
  • 2022-12-23
  • 2021-10-06
  • 2021-08-28
  • 2021-12-03
  • 2021-10-08
  • 2022-12-23
  • 2021-12-07
猜你喜欢
  • 2021-12-11
  • 2021-07-03
  • 2022-12-23
  • 2021-05-25
  • 2022-12-23
  • 2021-12-19
  • 2021-07-19
相关资源
相似解决方案