《闲扯Redis十一》Redis 有序集合对象底层实现

一、前言

Redis 提供了5种数据类型:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),理解每种数据类型的特点对于redis的开发和运维非常重要。

原文解析

《闲扯Redis十一》Redis 有序集合对象底层实现

备注: 本节中涉及到的跳跃表实现,已经在上节《闲扯Redis十》Redis 跳跃表的结构实现一文中详情分析过,本文中将直接引用,不再赘述。

二、命令实现

因为有序集合键的值为有序集合对象,所以用于有序集合键的所有命令都是针对有序集合对象来构建的。

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

三、结构解析

由前文和上图可知,有序集合的编码可以是 ziplist 或者 skiplist 。ziplist 编码的有序集合对象使用压缩列表作为底层实现, 每个集合元素使用两个紧挨在一起的压缩列表节点来保存, 第一个节点保存元素的成员(member), 而第二个元素则保存元素的分值(score)。

压缩列表方式

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

例如,如果我们执行以下 ZADD 命令, 那么服务器将创建一个有序集合对象作为 price 键的值:

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

如果 price 键的值对象使用的是 ziplist 编码, 那么这个值对象将会是图 8-14 所示,, 而对象所使用的压缩列表则会是 8-15 所示。

《闲扯Redis十一》Redis 有序集合对象底层实现《闲扯Redis十一》Redis 有序集合对象底层实现

跳跃表和字典方式

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

typedef struct zset {

    zskiplist *zsl;

    dict *dict;

} zset;

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

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

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

skiplist 编码的有序集合对象, 那么这个有序集合对象将会是图 8-16 所示, 而对象所使用的 zset 结构将会是图 8-17 所示。

《闲扯Redis十一》Redis 有序集合对象底层实现《闲扯Redis十一》Redis 有序集合对象底层实现

注意:
为了展示方便, 图 8-17 在字典和跳跃表中重复展示了各个元素的成员和分值, 但在实际中, 字典和跳跃表会共享元素的成员和分值, 所以并不会造成任何数据重复, 也不会因此而浪费任何内存。

四、编码转换

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

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

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

注意:
以上两个条件的上限值是可以修改的, 具体请看配置文件中关于 zset-max-ziplist-entries 选项和 zset-max-ziplist-value 选项的说明。对于使用 ziplist 编码的有序集合对象来说, 当使用 ziplist 编码所需的两个条件中的任意一个不能被满足时, 程序就会执行编码转换操作, 将原本储存在压缩列表里面的所有集合元素转移到 zset 结构里面, 并将对象的编码从 ziplist 改为 skiplist 。

1.以下情况展示了有序集合对象因为包含了过多元素而引发编码转换:
对象包含了 128 个元素
redis> EVAL "for i=1, 128 do redis.call('ZADD', KEYS[1], i, i) end" 1 numbers
(nil)

redis> ZCARD numbers
(integer) 128

redis> OBJECT ENCODING numbers
"ziplist"

再添加一个新元素
redis> ZADD numbers 3.14 pi
(integer) 1

对象包含的元素数量变为 129 个
redis> ZCARD numbers
(integer) 129

编码已改变
redis> OBJECT ENCODING numbers
"skiplist"

2.以下情况展示了有序集合对象因为元素的成员过长而引发编码转换:
向有序集合添加一个成员只有三字节长的元素
redis> ZADD blah 1.0 www
(integer) 1

redis> OBJECT ENCODING blah
"ziplist"

向有序集合添加一个成员为 66 字节长的元素
redis> ZADD blah 2.0 oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
(integer) 1

编码已改变
redis> OBJECT ENCODING blah
"skiplist"

五、要点总结

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

2)一个 zset 结构同时包含一个字典和一个跳跃表。

3)zset 结构跳跃表和字典通过指针来共享相同元素的成员和分值。

4)有序集合对象 ziplist 或者 skiplist编码,符合条件时可发生编码转换。

over

《闲扯Redis十一》Redis 有序集合对象底层实现

Original: https://www.cnblogs.com/jstarseven/p/13636983.html
Author: jstarseven
Title: 《闲扯Redis十一》Redis 有序集合对象底层实现

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/529370/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • OpenStack glance对接swift

    配置 切换环境变量 [root@controller ~]# source keystonerc_admin 复制glance配置文件备份 [root@controller ~(k…

    Linux 2023年6月8日
    093
  • Netty源码解读(三)-NioEventLoop

    先看看EventLoop类图 我们在Netty第二篇文章中的代码中,看到有多次用到eventLoop.execute()方法,这个方法就是EventLoop开启线程执行任务的关键,…

    Linux 2023年6月7日
    097
  • pyQt中的信号

    1. 说明 在调用 exec_()方法时,应用会进入主循环,而主循环会监听、处理事件 import sys from PyQt5.QtCore import Qt from PyQ…

    Linux 2023年6月7日
    093
  • Podman基础用法

    Podman基础 1、什么是Podman? Podman是一种开源的Linux原生工具,旨在根据开放容器倡议(Open Container Initiative,OCI)标准开发、…

    Linux 2023年6月7日
    095
  • 在linux里部署OA项目环境

    1.首先要实现linux可以从windows系统里把文件拖到linux里 ①挂载光盘 [root@localhost ~]# mkdir /mnt/cdrom //创建挂载点 [r…

    Linux 2023年6月13日
    0124
  • MySQL-报错:Error when bootstrapping CMake:

    在进行MySQL的源码安装的时候,系统上找不到合适的C编译器,GCC忘了装,莫慌,直接 yum命令装上gcc,还有gcc-C++没装的话后面也会提示错误,一起装上,,, [root…

    Linux 2023年6月13日
    0108
  • 账号分享

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年6月8日
    0115
  • python正则表达式

    1.定义 正则表达式使用某种预定义的模式去匹配一类具有共同特征的字符串,主要用于处理字符串,可以快速、准确地完成复杂的查找、替换等处理要求。 re模块提供了正则表达式操作所需要的的…

    Linux 2023年6月7日
    0127
  • linux 僵尸进程处理

    什么是僵尸进程 我们启动一个程序,开始我们的任务,然后等任务结束了,我们就停止这个进程。 进程停止后, 该进程就会从进程表中移除。 但是,有时候有些程序即使执行完了也依然留在进程表…

    Linux 2023年6月6日
    0111
  • JavaWeb创建一个公共的servlet

    对于初学者来说,每次前端传数据过来就要新建一个类创建一个doget、dopost方法,其实铁柱兄在大学的时候也是这么玩的。后面铁柱兄开始认真了,就想着学习点容易的编程方式,其实说白…

    Linux 2023年6月13日
    0100
  • Pyinstaller打包教程

    由于用户使用Python脚本的时候可能没有运行环境,所以需要打包。记录下碰到的问题。 安装 pip install –upgrade pyinstaller 安装最新开发版 pi…

    Linux 2023年6月13日
    062
  • CentOS 6 安装并配置 MySQL 5.6

    1. 添加 MySQL Yum 存储库 将MySQL Yum存储库添加到系统的存储库列表中; 1.1 到MySQL官网下载MySQL Yum存储库(https://dev.mysq…

    Linux 2023年5月27日
    0126
  • Servlet 学习总结

    Servlet学习笔记 Servlet学习 学习视频为:https://www.bilibili.com/video/BV1Ta4y1H7Vc IDEA的使用 IDEA的简介 ID…

    Linux 2023年6月7日
    069
  • Linux网络服务之部署YUM仓库

    镜像下载、域名解析、时间同步请点击阿里云开源镜像站 1 YUM简介 1.1 YUM简介 CentOS使用yum和dnf 解决rpm的包依赖关系。 YUM:rpm的前端程序,可解决软…

    Linux 2023年5月27日
    0122
  • vscode搜索所有文件夹中所有文件的方法

    最近在看opencv相关的内容,看到画图这一部分时,提示我 这些代码都来自OpenCV代码的sample文件夹。 按照他的提示,我打开了相应的文件夹,却发现,so many 文件 …

    Linux 2023年6月14日
    0276
  • js学习笔记之for循环

    for 循环是在您希望创建循环时经常使用的工具。 for 循环的语法如下: for (语句 1; 语句 2; 语句 3) { 要执行的代码块 } 语句 1 在循环(代码块)开始之前…

    Linux 2023年6月13日
    074
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球