《闲扯Redis十一》Redis 有序集合对象底层实现
一、前言
Redis 提供了5种数据类型:String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),理解每种数据类型的特点对于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 所示。
跳跃表和字典方式
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 所示。
注意:
为了展示方便, 图 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 有序集合对象底层实现的更多相关文章
- redis 系列14 有序集合对象
一. 有序集合概述 Redis 有序集合对象和集合对象一样也是string类型元素的集合,且不允许重复的成员.不同的是每个元素都会关联一个double类型的分数.redis正是通过分数来为集合中的成员 ...
- 从Redis中删除大集合对象的方法
Redis中的大集合对象,如set.zset等,如果有上千万个元素,一般是不能直接用del命令来删除的,因为del命令可能会耗时几秒钟,而redis本身是单线程的,在高并发的情况下会阻塞大量的请求,严 ...
- 第二百九十九节,python操作redis缓存-SortSet有序集合类型,可以理解为有序列表
python操作redis缓存-SortSet有序集合类型,可以理解为有序列表 有序集合,在集合的基础上,为每元素排序:元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值, ...
- Redis数据类型使用场景及有序集合SortedSet底层实现详解
Redis常用数据类型有字符串String.字典dict.列表List.集合Set.有序集合SortedSet,本文将简单介绍各数据类型及其使用场景,并重点剖析有序集合SortedSet的实现. Li ...
- Redis学习---Redis操作之有序集合
有序集合,在集合的基础上,为每元素排序:元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序. zadd(name, *args, **kw ...
- redis:php-redis中有序集合 zset的使用
ZSET(stored set) 和 set 一样是字符串的集合,不同的是每个元素都会关联一个 double 类型的 score .实现使用的是 skip list 和 hash table , sk ...
- redis列表和有序集合
redis中的list数据类型是可以插入重复数据的,有去重的需求的话可以用redis有序集合数据类型 Redis Zadd 命令用于将一个或多个成员元素及其分数值加入到有序集当中. 如果某个成员已经是 ...
- redis 入门之有序集合
zadd 将一个或多个 member 元素及其 score 值加入到有序集 key 当中.如果某个 member 已经是有序集的成员,那么更新这个 member 的 score 值,并通过重新插入这个 ...
- redis:order set有序集合类型的操作(有序集合)
1. order set有序集合类型的操作(有序集合) 有序集合是在无序集合的基础上加了一个排序的依据,这个排序依据叫score,因此声明一个集合为有序集合的时候要加上score(作为排序的依据) 1 ...
随机推荐
- node根据excel批量更名
程序预览 index.js var xlsx = require('node-xlsx'); var fs = require('fs'); process.stdin.setEncoding('ut ...
- 「查缺补漏」巩固你的Redis知识体系
Windows Redis 安装 链接: https://pan.baidu.com/s/1MJnzX_qRuNXJI09euzkPGA 提取码: 2c6w 复制这段内容后打开百度网盘手机App,操作 ...
- jquery 事件对象笔记
jQuery元素操作 设置或获取元素固有属性 获取 prop(属性名) 修改 prop(属性名,值) 获取自定义属性 ...
- 从零搭建Spring Boot脚手架(3):集成mybatis
1. 前言 今天继续搭建我们的kono Spring Boot脚手架,上一文集成了一些基础的功能,比如统一返回体.统一异常处理.快速类型转换.参数校验等常用必备功能,并编写了一些单元测试进行验证,今天 ...
- 在Linux下安装nginx服务器详细教程
首先安装centos的扩展源 yum install epel-release 安装Nginx 方法一: yum install nginx -y 查看版本号,开启nginx,查看进程 nginx – ...
- 代码生成器插件与Creator预制体文件解析
前言 之前写过一篇自动生成脚本的工具,但是我给它起名叫半自动代码生成器.之所以称之为半自动,因为我觉得全自动代码生成器应该做到两点:代码生成+自动绑定.之前的工具只做了代码生成,并没有做自动绑定,所以 ...
- C#LeetCode刷题之#168-Excel表列名称(Excel Sheet Column Title)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3850 访问. 给定一个正整数,返回它在 Excel 表中相对应的 ...
- Homekit_Dohome_智能插座
简介: 本款智能插座有三个版本可供选择,分别为Homekit版本,涂鸦版本,Dohome版本,各个版本的区别如下: DoHome版特点: 支持HomeKit 支持Amazon 支持Google ...
- Homekit_人体感应器
前置需求: 苹果手机一台 Homekit人体感应一台 USB供电线一根 这款产品内置人体红外感应器,使用圆环设计,增大了其接触面积,使感应变得更加灵敏,重量轻,方便将其粘贴到墙上,同时支持二次开发,如 ...
- async 函数的含义和用法
Generator函数的含义与用法 Thunk函数的含义与用法 co函数库的含义与用法 async函数的含义与用法 一.终极解决 异步操作是 JavaScript 编程的麻烦事,麻烦到一直有人提出各种 ...