access ole 对象 最大长度_Redis 数据结构和对象系统,有这 12 张图就够了!

34d9ef614c7771fdac33211d578b17c5.png

作者 | 程序员历小冰

责编 | 林瑟

Redis 是一个开源的 key-value 存储系统,它使用六种底层数据结构构建了包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象的对象系统。 

今天我们就通过 12 张图来全面了解一下它的数据结构对象系统的实现原理

01

数据结构

简单动态字符串

Redis 使用动态字符串 SDS 来表示字符串值。下图展示了一个值为 Redis 的 SDS结构 :

  • len: 表示字符串的真正长度(不包含 NULL 结束符在内)。

  • alloc: 表示字符串的最大容量(不包含最后多余的那个字节)。

  • flags: 总是占用一个字节。其中的最低 3 个 bit 用来表示 header 的类型。

  • buf: 字符数组

6df54e269d5015869dc8be21328cf7fd.png

SDS 的结构可以减少修改字符串时带来的内存重分配的次数,这依赖于内存预分配和惰性空间释放两大机制。

当 SDS 需要被修改,并且要对 SDS 进行空间扩展时,Redis 不仅会为 SDS 分配修改所必须要的空间,还会为 SDS 分配额外的未使用的空间。

  • 如果修改后, SDS 的长度(也就是len属性的值)将小于 1MB ,那么 Redis 预分配和 len 属性相同大小的未使用空间。

  • 如果修改后, SDS 的长度将大于 1MB ,那么 Redis 会分配 1MB 的未使用空间。

比如说,进行修改后 SDS 的 len 长度为 20 字节,小于 1MB,那么 Redis 会预先再分配 20 字节的空间, SDS 的 buf 数组的实际长度(除去最后一字节)变为 20 + 20 = 40 字节。

当 SDS 的 len 长度大于 1MB 时,则只会再多分配 1MB 的空间。

类似的,当 SDS 缩短其保存的字符串长度时,并不会立即释放多出来的字节,而是等待之后使用。

链表

链表在 Redis 中的应用非常广泛,比如列表对象的底层实现之一就是链表。除了链表对象外,发布和订阅、慢查询、监视器等功能也用到了链表。

d7014bfd59213f7585af13aeedbb75d3.png

Redis 的链表是双向链表,示意图如上图所示。链表是最为常见的数据结构,这里就不在细说。

Redis 的链表结构的 dup 、 free 和 match 成员属性是用于实现多态链表所需的类型特定函数:

  • dup 函数用于复制链表节点所保存的值,用于深度拷贝。

  • free 函数用于释放链表节点所保存的值。

  • match 函数则用于对比链表节点所保存的值和另一个输入值是否相等。

字典

字典被广泛用于实现 Redis 的各种功能,包括键空间和哈希对象。其示意图如下所示。

ec2e06c830a7770b7c9519d2d39ca8ea.png

Redis 使用 MurmurHash2 算法来计算键的哈希值,并且使用链地址法来解决键冲突,被分配到同一个索引的多个键值对会连接成一个单向链表。

跳跃表

Redis 使用跳跃表作为有序集合对象的底层实现之一。它以有序的方式在层次化的链表中保存元素, 效率和平衡树媲美 —— 查找、删除、添加等操作都可以在对数期望时间下完成, 并且比起平衡树来说, 跳跃表的实现要简单直观得多。

a3fe7135cb78cc68b95646ab8a82bd19.png

跳表的示意图如上图所示,这里只简单说一下它的核心思想,并不进行详细的解释。

如示意图所示,zskiplistNode 是跳跃表的节点,其 ele 是保持的元素值,score 是分值,节点按照其 score 值进行有序排列,而 level 数组就是其所谓的层次化链表的体现。

每个 node 的 level 数组大小都不同, level 数组中的值是指向下一个 node 的指针和 跨度值 (span),跨度值是两个节点的 score 的差值。越高层的 level 数组值的跨度值就越大,底层的 level 数组值的跨度值越小。

level 数组就像是不同刻度的尺子。度量长度时,先用大刻度估计范围,再不断地用缩小刻度,进行精确逼近。

当在跳跃表中查询一个元素值时,都先从第一个节点的最顶层的 level 开始。比如说,在上图的跳表中查询 o2 元素时,先从 o1 的节点开始,因为 zskiplist 的 header 指针指向它。

先从其 level[3] 开始查询,发现其跨度是 2,o1 节点的 score 是1.0,所以加起来为 3.0,大于 o2 的 score 值2.0。所以,我们可以知道 o2 节点在 o1 和 o3 节点之间。这时,就改用小刻度的尺子了。就用level[1]的指针,顺利找到 o2 节点。

整数集合

整数集合 intset 是集合对象的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时, Redis 就会使用整数集合作为集合对象的底层实现。

0af7e9eaaa90238af957410dcaf27276.png

如上图所示,整数集合的 encoding 表示它的类型,有 int16t,int32t 或者 int64_t。其每个元素都是 contents 数组的一个数组项,各个项在数组中按值的大小从小到大有序的排列,并且数组中不包含任何重复项。length 属性就是整数集合包含的元素数量。

压缩列表

压缩队列 ziplist 是列表对象和哈希对象的底层实现之一。当满足一定条件时,列表对象和哈希对象都会以压缩队列为底层实现。

468a2484f8e81230ee783f1eecdd1ff9.png

压缩队列是 Redis 为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构。它的属性值有:

  • zlbytes : 长度为 4 字节,记录整个压缩数组的内存字节数。

  • zltail : 长度为 4 字节,记录压缩队列表尾节点距离压缩队列的起始地址有多少字节,通过该属性可以直接确定尾节点的地址。

  • zllen : 长度为 2 字节,包含的节点数。当属性值小于 INT16_MAX时,该值就是节点总数,否则需要遍历整个队列才能确定总数。

  • zlend : 长度为 1 字节,特殊值,用于标记压缩队列的末端。

中间每个节点 entry 由三部分组成:

  • previous_entry_length : 压缩列表中前一个节点的长度,和当前的地址进行指针运算,计算出前一个节点的起始地址。

  • encoding:节点保存数据的类型和长度

  • content :节点值,可以为一个字节数组或者整数。

02

对象系统的实现原理

上面介绍了 6 种底层数据结构,Redis 并没有直接使用这些数据结构来实现键值数据库,而是基于这些数据结构创建了一个对象系统.

这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合这五种类型的对象,每个对象都使用到了至少一种前边讲的底层数据结构。

Redis 根据不同的使用场景和内容大小来判断对象使用哪种数据结构,从而优化对象在不同场景下的使用效率和内存占用。

Redis 的 redisObject 结构的定义如下所示。

typedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; int refcount; void *ptr;} robj;

其中 type 是对象类型,包括 REDISSTRING, REDISLIST, REDISHASH, REDISSET 和 REDIS_ZSET。

encoding 是指对象使用的数据结构,全集如下。8bb882ac1205372b3542c709f2535f12.png

字符串对象

我们首先来看字符串对象的实现,如下图所示。

a5bfddcbbc6ad141e5db6222852151a8.png

如果一个字符串对象保存的是一个字符串值,并且长度大于 32 字节,那么该字符串对象将使用 SDS 进行保存,并将对象的编码设置为 raw,如图的上半部分所示。

如果字符串的长度小于 32 字节,那么字符串对象将使用 embstr 编码方式来保存。

embstr 编码是专门用于保存短字符串的一种优化编码方式,这个编码的组成和 raw 编码一致,都使用 redisObject 结构和 sdshdr 结构来保存字符串,如上图的下半部所示。

但是 raw 编码会调用两次内存分配来分别创建上述两个结构,而 embstr 则通过一次内存分配来分配一块连续的空间,空间中一次包含两个结构。

embstr 只需一次内存分配,而且在同一块连续的内存中,更好的利用缓存带来的优势,但是 embstr 是只读的,不能进行修改,当一个 embstr 编码的字符串对象进行 append 操作时,redis 会现将其转变为 raw 编码再进行操作。

列表对象

列表对象的编码可以是 ziplist 或 linkedlist。其示意图如下所示。

a8dff5d2e054ae73d659cf9629465e5a.png

当列表对象可以同时满足以下两个条件时,列表对象使用 ziplist 编码:

  • 列表对象保存的所有字符串元素的长度都小于 64 字节。

  • 列表对象保存的元素数量数量小于 512 个。

不能满足这两个条件的列表对象需要使用 linkedlist 编码或者转换为 linkedlist 编码。

哈希对象

哈希对象的编码可以使用 ziplist 或 dict。其示意图如下所示。

当哈希对象使用压缩队列作为底层实现时,程序将键值对紧挨着插入到压缩队列中,保存键的节点在前,保存值的节点在后。如下图的上半部分所示,该哈希有两个键值对,分别是 name:Tom 和 age:25。

ca58271e885f26706342c13c24094213.png

当哈希对象可以同时满足以下两个条件时,哈希对象使用 ziplist 编码:

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节。

  • 哈希对象保存的键值对数量小于512个。

不能满足这两个条件的哈希对象需要使用 dict 编码或者转换为 dict 编码。

集合对象

集合对象的编码可以使用 intset 或者 dict。

intset 编码的集合对象使用整数集合最为底层实现,所有元素都被保存在整数集合里边。

而使用 dict 进行编码时,字典的每一个键都是一个字符串对象,每个字符串对象就是一个集合元素,而字典的值全部都被设置为NULL。如下图所示。

fdcd506aeb9f5ef656d24f5d23847b3b.png

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

  • 集合对象保存的所有元素都是整数值。

  • 集合对象保存的元素数量不超过 512 个。

否则使用 dict 进行编码。

有序集合对象

有序集合的编码可以为 ziplist 或者 skiplist。

有序集合使用 ziplist 编码时,每个集合元素使用两个紧挨在一起的压缩列表节点表示,前一个节点是元素的值,第二个节点是元素的分值,也就是排序比较的数值。

压缩列表内的集合元素按照分值从小到大进行排序,如下图上半部分所示。

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

其中,跳跃表按照分值从小到大保存所有元素,每个跳跃表节点保存一个元素,其score值是元素的分值。而字典则创建一个一个从成员到分值的映射,字典的键是集合成员的值,字典的值是集合成员的分值。通过字典可以在O(1)复杂度查找给定成员的分值。如下图所示。

跳跃表和字典中的集合元素值对象都是共享的,所以不会额外消耗内存。

34aa8c45deb0c61948df2fb3de6b863a.png

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

  • 有序集合保存的元素数量少于128个;

  • 有序集合保存的所有元素的长度都小于64字节。

否则使用 skiplist 编码。

数据库键空间

Redis 服务器都有多个 Redis 数据库,每个Redis 数据都有自己独立的键值空间。每个 Redis 数据库使用 dict 保存数据库中所有的键值对。

0ed01afed1000d2868d3606e1750ff9f.png

键空间的键也就是数据库的键,每个键都是一个字符串对象,而值对象可能为字符串对象、列表对象、哈希表对象、集合对象和有序集合对象中的一种对象。

除了键空间,Redis 也使用 dict 结构来保存键的过期时间,其键是键空间中的键值,而值是过期时间,如上图所示。

通过过期字典,Redis 可以直接判断一个键是否过期,首先查看该键是否存在于过期字典,如果存在,则比较该键的过期时间和当前服务器时间戳,如果大于,则该键过期,否则未过期。

今日话题

#聊聊 Redis 的疑难杂症#

—  —

程序员社群 | 连接更优秀的人

4002483075f697369cbb00dc92dc8616.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/385977.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【面试总结】2021Java春招面试经历

三、堆空间 基本描述 JVM启动时创建堆区,是内存管理的核心区,通常情况下也是最大的内存空间,是被所有线程共享的,几乎所有的对象实例都要在堆中分配内存,所以这里也是垃圾回收的重点空间。 堆栈关系 栈是JVM运行时的…

【高级Java架构师系统学习】最新Java高级面试题汇

性能调优 影响MySQLServer 性能的相关因素 商业需求对性能的影响系统架构及实现对性能的影响Query语句对系统性能的影响Schema设计对系统的性能影响硬件环境对系统性能的影响 MySQL 数据库锁定机制 MySQL锁定机制简介各种锁定机制分析合理利用锁机制优化MySQL MySQL数据库Qu…

vue 安装指定版本swiper_Vue中的runtime-only和runtime-compiler

在我们使用vue-cli的时候,会提示你安装的版本可以看到有两种版本:Routime Only和Runtime Compiler版本1.Runtime Only - 代码中不可以有任何template 性能更高在该版本下,通常需要借助如webpack的vue-loader发工具把.vue文件编译成js因为是在…

一文搞懂JVM架构:入职3个月的Java程序员面临转正

Java基础 1.JAVA 中的几种数据类型是什么,各自占用多少字节。 2.String 类能被继承吗,为什么。 3. 两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗? 4. String 属于基础的数据类型吗? 5.…

不显示调用super_让不懂编程的人爱上iPhone开发(2017秋iOS11+Swift4+Xcode9版)-第11篇

欢迎回到我们的iPhone开发教程系列,让我们继续前进吧。重新来过别害怕,哥不是让你抛弃之前所有的源代码,从零开始重新构建这个项目!这里说的是游戏界面里面的“Start over”按钮。在我们的to-do清单里面曾经提到过,这个…

一文搞懂JVM架构:跳槽面试大厂被拒

正文 在实际的工作项目中, 缓存成为高并发、高性能架构的关键组件 ,那么Redis为什么可以作为缓存使用呢?首先可以作为缓存的两个主要特征: 在分层系统中处于内存/CPU具有访问性能良好,缓存数据饱和,有良好…

全局变量_Python函数中的全局变量与局部变量

# a,b变量是全局变量,在整个py文件中都可以访问a 11b 12# 定义一个函数def first():# 这个变量是函数内部定义的变量,属于局部变量,只能在函数中使用c "Hello"# 大括号{} 是format()函数的用法,格式化print("c {}".format(c))# 如果局部变量定义的名称…

一文详解:字节面试官必问的Mysql锁机制

一面 1 自我介绍和项目 2 Java的内存分区 3 Java对象的回收方式,回收算法。 4 CMS和G1了解么,CMS解决什么问题,说一下回收的过程。 5 CMS回收停顿了几次,为什么要停顿两次。 6 Java栈什么时候会发生内存溢出,Jav…

install npm 到某个文件下执行_你可能不知道的 npm 依赖管理那些事

点击上方蓝字关注我们npm 是 Node.js 默认的、以 JavaScript 编写的包管理工具,如今,它已经成为世界上最大的包管理工具,是每个前端开发者必备的工具。不知你是否遇到过下面问题:哎?我本地明明是好的,线上的…

万字总结!腾讯、字节跳动面经已发

二、常见的并发问题 1、脏读 一个事务读取了另一个事务未提交的数据 2、不可重复读 一个事务对同一数据的读取结果前后不一致。两次读取中间被其他事务修改了 3、幻读 幻读是指事务读取某个范围的数据时,因为其他事务的操作导致前后两次读取的结果不一致。幻读…

ncbi查找目的基因序列_NCBI大搜索之目的基因寻踪

NCBI大搜索之目的基因寻踪最近经常碰到查找目的基因的问题,那今天就讲一下如何利用NCBI数据库查找目的基因!NCBI(National Center For Biotechnology Information),美国国家生物技术信息中心,分子生物学,生物化学及遗传学领域常用…

万字长文!2020-2021京东Java面试真题解析

我整理的spring学习笔记: 像spring这种知识点我们不能盲目的学习,首先我们得有一套学习路线,我总结了一套spring的学习思维导图,今天通过我整理的Spring学习路线.xmind给大家分析spring需要掌握的一些核心知识点。 spring的特点&…

echarts label固定位置_ECharts+百度地图网络拓扑应用

前一篇谈及到了ECharts整合HT for Web的网络拓扑图应用,后来在ECharts的Demo中看到了有关空气质量的相关报表应用,就想将百度地图、ECharts和HT for Web三者结合起来也做一个类似空气质量报告的报表拓扑图应用,于是有了下面的Demo&#xff1a…

三年Java开发,你连基础的JVM运行时内存布局都忘了

面:为什么要使用双亲委派机制去加载类? 答:避免多份同样字节码的加载,浪费内存。 类的加载方式 隐式加载:new显示加载:loadClass、forName等 类的装载过程如下图: 面:loadClass和…

vue实现可编辑的文字_苹果还自带文字转语音,只要一键按下便可实现,今天分享给大家...

如果想将文字转成语音,那大家平时都是怎么操作?下面小编就为大家介绍手机,电脑上都可以使用的方法,让我们一起来看看吧!一、手机端操作1、苹果手机其实苹果手机就自带了文字转语音功能,只要打开手机&#x…

三面美团Java岗,面试竟然被这31道Java基础题难倒了

01 分布式限流:NginxZooKeeper 1.1 分布式限流之Nginx 请解释一下什么是 Nginx? 请列举 x Nginx 的一些特性。 请列举 x Nginx 和 和 Apache 之间的不同点 请解释 x Nginx 如何处理 P HTTP 请求。 在 x Nginx 中,如何使用未定义的服务器名称来阻止…

海龟绘图小动物_震惊!被塑料绳勒成两半的海龟

海洋,其实离人类很近,我们在追逐沙滩和日落,享受美味的海鲜的时候,可曾想到我们平时的一些很随意的行为,会给一些海洋生物带来无法恢复的伤害,甚至夺取它们的生命。或许人们的冷漠无知尚未得到惩罚&#xf…

上海大厂Java面试经历:初步理解类加载运行机制和类加载过程

volatile相关经典面试题 谈谈volatile的特性volatile的内存语义说说并发编程的3大特性什么是内存可见性,什么是指令重排序?volatile是如何解决java并发中可见性的问题volatile如何防止指令重排volatile可以解决原子性嘛?为什么?v…

mysql数据库优化面试

前言 现在Java程序员面试都是因为没有丰富的工作经验和自己过硬的技术,所有都不知道一般互联网应该会问什么技术问题,加上自己可能去面试的时候没有准备的太充分,一面试刚跟面试官扯几个面试题就不知道自己在哪里了,被怼的体无完…

mysql数据库备份方式,跳槽大厂必看!

NO1:说说zookeeper是什么? ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现(Chubby是不开源的),它是集群的管理者,监视着集群中各个节点…