Redis | 第一部分:数据结构与对象 中篇《Redis设计与实现》
前言
参考资料:《Redis设计与实现 第二版》;
本篇笔记按照书里的脉络,将知识点分为四个部分。其中第一部分数据结构与对象分为上中下篇,上篇包括:SDS、链表和字典;中篇包括跳跃表、整数集合和压缩列表;下篇为对象;
上篇的链接:https://www.cnblogs.com/dlhjw/p/15569578.html
下篇的链接:https://www.cnblogs.com/dlhjw/p/15594048.html
Redis常用命令及示例总结:https://blog.csdn.net/dlhjw1412/article/details/119713214
1. 跳跃表
- 跳跃表支持平均 O(logN)、最坏 O(N) 复杂度的节点查找,还可以通过顺序性操作来批量处理节点;
- 跳跃表的效率可以媲美平衡树,实现比平衡树简单;
- 跳跃表在Redis里只有两个应用:有序集合键的底层实现、集群节点中用作内部数据结构;
1.1 跳跃表与其节点的定义
跳跃表的定义,在
redis.h/zskiplist
结构里:typedef struct zskiplist {
//表头节点和表尾节点
structz skiplistNode *header, *tail;
//表中节点的数量(不包括表头指针)
unsigned long length;
//表中层数最大的节点的层数(不包括表头指针)
int level;
} zskiplist;
跳跃表节点的定义,在
redis.h/zskiplistNode
结构里:typedef struct zskiplistNode{
//后退指针
struct zskiplistNode *backwars;
//分值
double score;
//成员对象
robj *obj;
//层
struct zskiplistLevel{
//前进指针
struct zskiplistNode *forward;
//跨度
unsigned int apan;
} level[];
} askiplistNode;
- 节点中使用
L1
、L2
、L3
等来标记节点的各个层,每个层有前进指针和跨度;- 带数字的箭头为前进指针,数字为跨度;
- 一般来说,层数越多访问其他节点速度越快;
- 创建新跳跃表节点时,随机生成介于1和32之间的数作为level数组的大小;
- 跨度与遍历无关,与排位
rank
有关。查找某个节点时,将沿途层相加,得到排位;
- 带
BW
字样的为后退指针; 1.0
、2.0
、3.0
为分值,分值从小到大排列;- 当分值相同时,成员对象在字典中排序小的靠近表头节点;
o1
、o2
、o3
等是成员对象,成员对象必须唯一;- 表头节点也有后退指针、分值和成员对象,不会用到所以图中没有显示;
- 下图中
level
为5是因为o3对象有5层,为该跳跃表中最大层;
- 节点中使用
1.2 跳跃表的API
函数 | 作用 | 时间复杂度 |
---|---|---|
zslCreate | 创建一个新的跳跃表 | O(1) |
zslFree | 释放给定跳跃表,以及表中包含的所有节点 | O(N),N为跳跃表的长度 |
zslInsert | 将包含给定成员和分值的新节点添加到跳跃表中 | 平均O(logN),最坏O(N),N为跳跃表长度 |
zslDelete | 删除跳跃表中包含给定成员和分值的节点 | 平均O(logN),最坏O(N),N为跳跃表长度 |
zslGetRank | 返回包含给定成员和分值的节点在跳跃表中的排位 | 平均O(logN),最坏O(N),N为跳跃表长度 |
zslGetElementByRank | 返回包含给定成员和分值的节点在跳跃表中的排位 | 平均O(logN) ,最坏O(N),N为跳跃表长度 |
zslIsInRange | 给定一个分值范围(range),比如0到15,20到28,诸如此类,如果给定的分值范围包含在跳跃表的分值范围内,返回1,否则返回0 | O(1),基于通过跳跃表的表头节点和表尾节点的分值得到范围 |
zslFirstInRange | 给定一个分值范围,返回跳跃表中第一个符合这个范围的节点 | 平均O(logN),最坏O(N),N为跳跃表长度 |
zslLastInRange | 给定一个分值范围,返回跳跃表中最后一个符合这个范围的节点 | 平均O(logN),最坏O(N),N为跳跃表长度 |
zslDeleteRangeByScore | 给定一个分值范围,删除跳跃表中所有在这个范围之内的节点 | O(N),N为被删除节点数量 |
zslDeleteRangeByRank | 给定一个排位范围,删除跳跃表中所有在这个范围之内的节点 | O(N),N为被删除节点数量 |
2. 整数集合
- 整数集合 intset,其特点是从小到大保存整数且不会重复;
- 整数集合在Redis里的应用:集合键的底层实现;
2.1 整数集合的实现
整数集合的定义,在
intset.h/intset
结构中:typedef struct intset{
//编码方式
uint32_t encoding;
//集合包含的元素数量
uint32_t length;
//保存元素的数组
int8_t contents[];
} intset;
contents
声明为 int8_t 类型的数组,但数组的真正类型取决于encoding
属性的值;
encoding值 contents值 范围 INTSET_ENC_INT16 int16_t -32768~32768 INTSET_ENC_INT32 int32_t -2147483648~2147483647 INTSET_ENC_INT64 int64_t -9223372036854775808~9223372036854775807
2.2 整数集合的类型升级
- 当新增的元素类型比整数集合现有元素的类型长时,需要升级;
- 步骤:
- 根据新元素类型,扩展整数集合底层数组空间大小,并为新元素分配空间;
- 将底层数组现有元素转换成新元素相同的类型,在维持集合有序性质不变情况下将转换后的元素放置到正确位置上;
- 将新元素添加到底层数组里;
- 因为添加新元素可能会引起升级,每次升级需要对所有元素进行类型转换,因此时间复杂度为O(N);
- 因为引起升级操作的新元素比现有元素长,所以新元素要么添加到数组开头,要么数组末尾;
- 好处:
- 灵活性:C语言通常不会将不同类型值放在同一个数据结构里,Redis的升级使其可以;
- 节约内存;
- 整数集合不允许降级操作;
2.3 整数集合的API
函数 | 作用 | 时间复杂度 |
---|---|---|
intsetNew | 创建一个新的整数集合 | O(1) |
intsetAdd | 将给定元素添加到整数集合里面 | O(N) |
intsetRemove | 从整数集合中移除给定元素 | O(N) |
intsetFind | 检查给定值是否存在于集合 | O(logN),整数集合有序排列,可以用二分查找法 |
intsetRandom | 从整数集合中随机返回一个元素 | O(1) |
intsetGet | 取出底层数组在给定索引上的元素 | O(1) |
intsetLen | 返回整数集合包含的元素个数 | O(1) |
intsetBlobLen | 返回整数集合咱用的内存字节数 | O(1) |
3. 压缩列表
- 压缩列表 ziplist,其特点是管理小整数值和短字符串;
- 压缩列表在Redis里的应用:列表键与哈希键的底层实现之一;
- 压缩列表的Redis为节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型(sequential)数据结构;
3.1 压缩列表的结构
- 压缩列表是由一系列特殊编码的连续内存块组成的顺序型数据结构;
3.2 压缩列表节点的定义
节点的定义在
ziplist.c/zlentry
结构里:typedef struct zlentry {
// prevrawlen :前置节点的长度
// prevrawlensize :编码 prevrawlen 所需的字节大小
unsigned int prevrawlensize, prevrawlen;
// len :当前节点值的长度
// lensize :编码 len 所需的字节大小
unsigned int lensize, len;
// 当前节点 header 的大小
// 等于 prevrawlensize + lensize
unsigned int headersize;
// 当前节点值所使用的编码类型
unsigned char encoding;
// 指向当前节点的指针
unsigned char *p;
} zlentry;
- 可以用当前节点地址减去
prevrawlen
的值获得前置节点的首地址,可以由此实现从尾到头的遍历; *p
指向一个content
,保存节点的值,值的类型和长度由encoding
决定;encoding
的属性(下划线表示留空,abcdx代表实际二进制数据):
- 可以用当前节点地址减去
3.3 连锁更新
- 首先,压缩列表节点有个
prevrawlen
属性,用于记录前一个节点的长度,前一个节点的长度变化会影响prevrawlen
属性的长度取值(使用1个字节存储前一个节点的长度还是5个); - 假设所有结点(e1, e2......eN)长度介于250~253字节之间,在表头新增长度大于等于254字节的new节点,因为e1的
prevrawlen
属性仅1字节,无法保存大于254的数字(new的长度),因此需要扩展为5字节长,此时e1的长度介于254~257字节之间。这样,new引发e1的扩展,e1引发e2的扩展,形成连锁更新; - 删除节点也可能引发连锁更新;
- 连锁更新的最坏时间复杂度为 O(N2);
- 在实际中,连锁更新造成的性能问题几率很低;
3.4 压缩列表的API
函数 | 作用 | 时间复杂度 |
---|---|---|
ziplistNew | 创建一个新的压缩列表 | O(1) |
ziplistPush | 创建一个包含给定值的新节点,并将这个新节点添加到压缩列表的表头或表尾 | 平均O(N),最坏O(N2) |
ziplistInsert | 将包含给定值的新节点插入到给定节点之后 | 平均O(N),最坏O(N2) |
ziplistIndex | 返回压缩列表给定索引上的节点 | O(N) |
ziplistFind | 在压缩列表中查找并返回包含了给定值的节点 | 当保存的是字节数字时为O(N2),整数时为O(N) |
ziplistNext | 返回给定节点的下一个节点 | O(1) |
ziplistPrev | 返回给定节点的前一个节点 | O(1) |
ziplistGet | 获取给顶节点说保存的值 | O(1) |
ziplistDelete | 从压缩列表中删除给定的节点 | 平均O(N),最坏O(N2) |
ziplistDeleteRange | 删除压缩列表在给定索引上的连续多个节点 | 平均O(N),最坏O(N2) |
ziplistBlobLen | 返回压缩列表目前占用的内存字节数 | O(1) |
ziplistLen | 返回压缩列表目前包含的节点数量 | 节点数量小于65535时为O(1),大于65535时为O(N) |
- 最坏时间复杂度为O(N2)是因为可能引发连锁更新;
最后
新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!
Redis | 第一部分:数据结构与对象 中篇《Redis设计与实现》的更多相关文章
- [redis读书笔记] 第一部分 数据结构与对象 对象类型
- 从前面redis的基本数据结构来看,可以看出,redis都是在基本结构(string)的基础上,封装了一层统计的结构(SDS),这样让对基本结构的访问能够更快更准确,提高可控制度. - redis ...
- Redis笔记(1)数据结构与对象
1.前言 此系列博客记录redis设计与实现一书的笔记,提取书本中的知识点,省略相关说明,方便查阅. 2.基本数据结构 2.1 简单动态字符串SDS(simple dynamic string) 结构 ...
- Redis 的底层数据结构(对象)
目前为止,我们介绍了 redis 中非常典型的五种数据结构,从 SDS 到 压缩列表,这都是 redis 最底层.最常用的数据结构,相信你也掌握的不错. 但 redis 实际存储键值对的时候,是基于对 ...
- 左手Mongodb右手Redis 第一章,进入Mongodb和Redis的世界
---恢复内容开始--- 1,为什么要使用非关系型数据库,关系型数据库咋滴,不能用嘛? 存在即合理,非关系型数据库的出现,那说明关系型数据库不适用了. 非关系型数据库(NOSQL)-->Not ...
- .net core工具组件系列之Redis—— 第一篇:Windows环境配置Redis(5.x以上版本)以及部署为Windows服务
Cygwin工具编译Redis Redis6.x版本是未编译版本(官方很调皮,所以没办法,咱只好帮他们编译一下了),所以咱们先下载一个Cygwin,用它来对Redis进行编译. Cygwin下载地址: ...
- [REDIS 读书笔记]第一部分 数据结构与对象 跳跃表
下面是跳跃表的基本原理,REDIS的实现大致相同 跳跃表的一个特点是,插入NODE是通过随机的方式来决定level的,比较奇特 下面是skipList的一个介绍,转载来的,源地址:http://ken ...
- [redis读书笔记] 第一部分 数据结构与对象 对象特性
一 类型检查和多态 类型检查,即有的命令是只针对特定类型的,如果类型不对,就会报错,此处的类型,是指的键类型,即robj.type.下面为有类型检查的命令: 对于某一种类型,redis下底层的实 ...
- [redis读书笔记] 第一部分 数据结构与对象 字典
三 字典 字典是Hash对象的底层实现,比如用HSET创建一个HASH的对象,底层可能就是用一个字典实现的键值对. 字典的实现主要设计下面三个结构: /* * 哈希表节点 */ typedef str ...
- [redis读书笔记] 第一部分 数据结构与对象 简单动态字符串
本读书笔记主要来自于<<redis设计与实现>> -- 黄键宏(huangz) redis主要设计了字符串,链表,字典,跳跃表,整数集合,压缩列表来做为基本的数据结构,实现键值 ...
随机推荐
- RabbitMQ的web页面介绍(三)
一.Virtual Hosts 每一个 RabbitMQ 服务器都能创建虚拟的消息服务器,我们称之为虚拟主机 (virtual host) ,简称为vhost.每一个 vhost 本质上是一个独立的小 ...
- 题解 「SCOI2016」萌萌哒
link Description 一个长度为 $ n $ 的大数,用 $ S_1S_2S_3 \ldots S_n $表示,其中 $ S_i $ 表示数的第 $ i $ 位,$ S_1 $ 是数的最高 ...
- Spring Security OAuth2 微服务认证中心自定义授权模式扩展以及常见登录认证场景下的应用实战
一. 前言 [APP 移动端]Spring Security OAuth2 手机短信验证码模式 [微信小程序]Spring Security OAuth2 微信授权模式 [管理系统]Spring Se ...
- SpringCloud-SpringBoot-SpringCloudAlibaba对应版本选择
一.SpringCloud-SpringBoot 对应的版本选择 SpringCloud官网常规方式只能查看最新的几个版本信息 https://spring.io/projects/spring-cl ...
- Unity——自动化代码生成
自动化代码生成 一.前言 由于之前写过关于UI框架的文章,这篇基于之前的基础,添加了自动生成代码的功能: 如果学习过程有困惑可以跳转到之前的文章<Unity--基于UGUI的UI框架>: ...
- 力扣 - 剑指 Offer 53 - II. 0~n-1中缺失的数字
题目 剑指 Offer 53 - II. 0-n-1中缺失的数字 思路1 排序数组找数字使用二分法 通过题目,我们可以得到一个规律: 如果数组的索引值和该位置的值相等,说明还未缺失数字 一旦不相等了, ...
- logging模块二
背景,在学习logging时总是遇到无法理解的问题,总结,尝试一下更清晰明了了,让我们开始吧! logging模块常用format格式说明 %(levelno)s: 打印日志级别的数值 %(level ...
- 第七次Scrum Metting
日期:2021年5月5日 会议主要内容概述:前后端对接,以及接下来的测试优化等工作. 一.进度情况 组员 负责 两日内已完成的工作 后两日计划完成的工作 工作中遇到的困难 徐宇龙 后端 测试数据模块和 ...
- elasticsearch使用ik中文分词器
elasticsearch使用ik中文分词器 一.背景 二.安装 ik 分词器 1.从 github 上找到和本次 es 版本匹配上的 分词器 2.使用 es 自带的插件管理 elasticsearc ...
- P2774 方格取数问题(最小割)
P2774 方格取数问题 一看题目便知是网络流,但由于无法建图.... 题目直说禁止那些条件,这导致我们直接建图做不到,既然如此,我们这是就要逆向思维,他禁止那些边,我们就连那些边. 我们将棋盘染色, ...