《闲扯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 ...
随机推荐
- 【Linux】linux history命令执行后显示历史命令执行时间
vim ~/.bashrc 或者 ~/.bash_profile 或者 获取root权限修改vim /etc/profile 最后添加 export HISTTIMEFORMAT="%F ...
- C#LeetCode刷题-链表
链表篇 # 题名 刷题 通过率 难度 2 两数相加 29.0% 中等 19 删除链表的倒数第N个节点 29.4% 中等 21 合并两个有序链表 C#LeetCode刷题之#21-合并两个有序链 ...
- Android SharedPreferences存储详解
什么是SharedPreferences存储 一种轻量级的数据保存方式 类似于我们常用的ini文件,用来保存应用程序的一些属性设置.较简单的参数设置. 保存现场:保存用户所作的修改或者自定义参数设定, ...
- 洛谷P1149.火柴棒等式(暴力搜索)
题目描述 给你n根火柴棍,你可以拼出多少个形如"A+B=C"的等式?等式中的A.B.C是用火柴棍拼出的整数(若该数非零,则最高位不能是0).用火柴棍拼数字0-9的拼法如图所示: 注 ...
- put数据到topic
基于python3.6 # -*-coding:utf-8 *- __author__ = 'lc_yy' from pykafka import KafkaClient import logging ...
- 使用Postman工具做接口测试(五)——生成随机参数
引言 我们平时使用最多的接口调试工具就是postman了,比如开发将一个接口给到你,你想看看接口是否正常.最常用的方法就是用postman去调一下.如果通,就写接口测试用例,反之,将开发打一顿吧o(* ...
- leetcode刷题记录——树
递归 104.二叉树的最大深度 /** * Definition for a binary tree node. * public class TreeNode { * int val; * Tree ...
- Hyperledger Fabric 手动搭建【区块链学习三】
Hyperledger Fabric 手动搭建 前面我们学习了区块链是什么.还有自动搭建学习东西我们就要从简单到深入(入门到放弃),现在自动部署已经跑通了接下来就是手动搭建Fabric 网络可以更好的 ...
- 第1章 Hive入门
第1章 Hive入门 1.1 什么是Hive Hive:由Facebook开源用于解决海量结构化日志的数据统计. Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张表,并提 ...
- ftp 搭建 centos 源
1. yum -y install vsftpd* 2. /etc/init.d/vsftpd restart 3. mount CentOS-7-aarch64-Everything-1810 ...