redis命令 - zset有序集合对象

作者: Redis开发者

有序集合对象

当数据较少时,sorted set是由一个ziplist来实现的。

当数据多的时候,sorted set是由一个dict + 一个skiplist来实现的。简单来讲,dict用来查询数据到分数的对应关系,而skiplist用来根据分数查询数据(可能是范围查找)。

编码

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

ziplist

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

压缩列表内的集合元素按分值从小到大进行排序, 分值较小的元素被放置在靠近表头的方向, 而分值较大的元素则被放置在靠近表尾的方向。

redis> ZADD price 8.5 apple 5.0 banana 6.0 cherry
(integer) 3

如果 price 键的值对象使用的是 ziplist 编码, 那么这个值对象将会是

1565948335114

1565948349178

dict+skiplist

typedef struct zset {

    zskiplist *zsl;

    dict *dict;

} zset;

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

除此之外, zset 结构中的 dict 字典为有序集合创建了一个从成员到分值的映射, 字典中的每个键值对都保存了一个集合元素: 字典的键保存了元素的成员, 而字典的值则保存了元素的分值。 通过这个字典, 程序可以用 O(1) 复杂度查找给定成员的分值, ZSCORE 命令就是根据这一特性实现的, 而很多其他有序集合命令都在实现的内部用到了这一特性。

有序集合每个元素的成员都是一个字符串对象, 而每个元素的分值都是一个 double 类型的浮点数。 值得一提的是, 虽然 zset 结构同时使用跳跃表和字典来保存有序集合元素, 但这两种数据结构都会通过指针来共享相同元素的成员和分值, 所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值, 也不会因此而浪费额外的内存

1565948499293

1565948510627

zscore的查询,不是由skiplist来提供的,而是由那个dict来提供的。

为了支持排名(rank),Redis里对skiplist做了扩展,使得根据排名能够快速查到数据,或者根据分数查到数据之后,也同时很容易获得排名。而且,根据排名的查找,时间复杂度也为O(log n)。

zrevrange的查询,是根据排名查数据,由扩展后的skiplist来提供。

zrevrank是先在dict中由数据查到分数,再拿分数到skiplist中去查找,查到后也同时获得了排名。

前述的查询过程,也暗示了各个操作的时间复杂度:

  • zscore只用查询一个dict,所以时间复杂度为O(1)
  • zrevrank, zrevrange, zrevrangebyscore由于要查询skiplist,所以zrevrank的时间复杂度为O(log n),而zrevrange, zrevrangebyscore的时间复杂度为O(log(n)+M),其中M是当前查询返回的元素个数。

总结起来,Redis中的skiplist跟前面介绍的经典的skiplist相比,有如下不同:

  • 分数(score)允许重复,即skiplist的key允许重复。这在最开始介绍的经典skiplist中是不允许的。
  • 在比较时,不仅比较分数(相当于skiplist的key),还比较数据本身。在Redis的skiplist实现中,数据本身的内容唯一标识这份数据,而不是由key来唯一标识。另外,当多个元素分数相同的时候,还需要根据数据内容来进字典排序。
  • 第1层链表不是一个单向链表,而是一个双向链表。这是为了方便以倒序方式获取一个范围内的元素。
  • 在skiplist中可以很方便地计算出每个元素的排名(rank)。

编码转换

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

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

以上两个条件的上限值是可以修改的, 具体请看配置文件中关于 zset-max-ziplist-entries 选项和 zset-max-ziplist-value

命令

| 命令      | `ziplist` 编码的实现方法                                     | `zset` 编码的实现方法                                        |
| :-------- | :----------------------------------------------------------- | :----------------------------------------------------------- |
| ZADD      | 调用 `ziplistInsert` 函数, 将成员和分值作为两个节点分别插入到压缩列表。 | 先调用 `zslInsert` 函数, 将新元素添加到跳跃表, 然后调用 `dictAdd` 函数, 将新元素关联到字典。 |
| ZCARD     | 调用 `ziplistLen` 函数, 获得压缩列表包含节点的数量, 将这个数量除以 `2` 得出集合元素的数量。 | 访问跳跃表数据结构的 `length` 属性, 直接返回集合元素的数量。 |
| ZCOUNT    | 遍历压缩列表, 统计分值在给定范围内的节点的数量。            | 遍历跳跃表, 统计分值在给定范围内的节点的数量。              |
| ZRANGE    | 从表头向表尾遍历压缩列表, 返回给定索引范围内的所有元素。    | 从表头向表尾遍历跳跃表, 返回给定索引范围内的所有元素。      |
| ZREVRANGE | 从表尾向表头遍历压缩列表, 返回给定索引范围内的所有元素。    | 从表尾向表头遍历跳跃表, 返回给定索引范围内的所有元素。      |
| ZRANK     | 从表头向表尾遍历压缩列表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 | 从表头向表尾遍历跳跃表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 |
| ZREVRANK  | 从表尾向表头遍历压缩列表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 | 从表尾向表头遍历跳跃表, 查找给定的成员, 沿途记录经过节点的数量, 当找到给定成员之后, 途经节点的数量就是该成员所对应元素的排名。 |
| ZREM      | 遍历压缩列表, 删除所有包含给定成员的节点, 以及被删除成员节点旁边的分值节点。 | 遍历跳跃表, 删除所有包含了给定成员的跳跃表节点。 并在字典中解除被删除元素的成员和分值的关联。 |
| ZSCORE    | 遍历压缩列表, 查找包含了给定成员的节点, 然后取出成员节点旁边的分值节点保存的元素分值。 | 直接从字典中取出给定成员的分值。                             |

参考

https://juejin.im/post/57fa935b0e3dd90057c50fbc

更多推荐

更多
  • Linux基础知识-五、更高级的命令行和概念 基本网络概念,安装新软件和更新系统,服务介绍,基本系统故障排除和防火墙,引入 ACLs,setuid、setgid 和粘性位,设置用户标识符,塞吉德,粘性比特,摘要, 在本章中,我们将了解以下内容:基本网络概念安装新
  • Linux基础知识-三、Linux 文件系统 理解文件系统,使用文件链接,搜索文件,与用户和组一起工作,使用文件权限,使用文本文件,使用 VIM 文本编辑器,摘要, 在前一章中,我们通过导航文件系统向您介绍了 Linux 文件和文件夹。在本章中,我们将学习如何使用、查找和更改读取和
  • Linux基础知识-四、使用命令行 基本的 Linux 命令,附加程序,网络工具,Nmap(消歧义),链接,iotop,iftop,快上来,lsof,理解过程,克隆,信号,杀,障碍,使用 Bash Shell 变量,Bash shell 脚本介绍,实现 Bash Shel
  • Linux基础知识-二、Linux 命令行 介绍命令行,文件环球化,引用命令,寻求帮助,使用 Linux Shell,理解标准流,理解正则表达式,与 sed 合作,使用 awk,浏览 Linux 文件系统,摘要, 在本章中,我们将向您介绍开始使用 Linux 命令行时最基本的概念
  • Linux基础知识-一、Linux 简介 Linux 系统概述,虚拟化,安装 VirtualBox 和 CentOS,使用 VirtualBox,通过 SSH 连接虚拟机,摘要, 一个操作系统 ( 操作系统)是一个运行在你的电脑上的特殊软件,它使得启动和运行微软
  • Linux基础知识-零、前言 这本书是给谁的,这本书涵盖了什么,充分利用这本书,下载彩色图像,使用的约定,取得联系,复习, 在这本书里,目标是建立一个坚实的基础,学习 Linux 命令行的所有要点,让你开始。它被设计成非常专注于只学习实用的核心技能和基本的 Linu
  • Git秘籍-十二、使用 Github 在这一章中,我将讨论使用 Github 来托管存储库。目前这是一个流行的开源项目托管平台。我们首先创建一个 Github 帐户并配置 SSH 密钥。完成后,您将学会如何:从公共 Github 库克隆克隆并推送至
    Apache CN

  • Git秘籍-十三、更多秘籍 在这最后一章,我将讨论一些尚未涉及的细节,这些细节迟早会成为你不可或缺的。您将了解到:如何使用命令`$ git diff`比较不同版本的文件?如何克服关于行尾的问题?配置忽略文件的三种不同方法使
    Apache CN

  • Git秘籍-十一、托管 Git 存储库 一旦你和你的同事们学会了如何提交、使用分支和远程操作,你就会想把 git 作为一个团队来使用。在这一章中,我将展示如何建立一个虚拟主机来与他人共享 git 库。 Git 可以使用 ssh、http、https 和 git 网络协议。要
    Apache CN

  • Git秘籍-十、远程存储库和同步 所有 VCS 系统背后的内在原因是使一组开发人员之间的协作尽可能无缝。最后,我们已经到了可以讨论如何使用 git 进行小组工作的时候了。我们将从最简单的设置开始,所有的存储库都可以通过本地存储获得。首先你必须学会如何使用遥控器。我
    Apache CN

  • 近期文章

    更多
    文章目录

      推荐作者

      更多