简介: 引言 本文是对《redis 设计与实现(第二版)》中数据结构与对象相干内容的整顿与阐明。本篇文章只对对象构造,1 种对象——字符串对象。以及字符串对象所对应的两种编码——raw 和 embstr,进行了具体介绍。
引言
本文是对《redis 设计与实现(第二版)》中数据结构与对象相干内容的整顿与阐明。本篇文章只对对象构造,1 种对象——字符串对象。以及字符串对象所对应的两种编码——raw 和 embstr,进行了具体介绍。表白一些自己的想法与认识,也心愿更多敌人一起来探讨,分享交换。作者:太阳
云掣科技 - 数据库团队
数据库工程师
对象
redis 应用对象来示意数据库中的键和值,每次当咱们在 redis 的数据库中新创建一个键值对时,咱们至多会创立两个对象,一个对象用作键值对的键(键对象),另一个对象用作键值对的值(值对象)。
redis 的每种对象都由对象构造 (redisObject) 与对应编码的数据结构组合而成,redis 反对 5 种对象类型,别离是字符串(string)、列表(list)、哈希(hash)、汇合(set)、有序汇合(zset),而每种对象类型至多对应两种编码方式,不同的编码方式所对应的底层数据结构是不同的。
每个对象会用到的编码以及对应的数据结构详见下表:
每种对象对应两至三种编码,除 skiplist 编码须要用到两种数据结构 (字典 + 跳跃表) 外,其余编码均用到一种底层的数据结构。
同一个对象类型,在不同的场景下用到的编码 (数据结构) 不同,redis 反对 8 种编码以及 8 种底层的数据结构。这种形式更加灵便,能够帮忙 redis 取得更高的性能以及尽量占用更少的内存。比方如果字符串对象中要存储的字符串内容所占字节较小,会用 embstr 编码的格局,如果要存储的内容所占字节较大,会用 raw 编码的格局,具体细节后文会具体阐明。
总结阐明
上文说过,redis 中的键和值都是由对象组成的,而对象是由对象构造和数据结构独特组成的。redis 中的键,都是用字符串来存储的,即对于 redis 数据库中的键值对来说,键总是一个字符串对象,而值能够是字符串对象、列表对象、哈希对象、汇合对象或者有序汇合对象中的其中一种。
键、值的整体大抵构造能够如下图所示:
对象构造
对象构造 (redisObject) 共有 5 个属性,别离是 type 属性、encoding 属性、ptr 属性、refcount 属性、lru 属性。
其中 type 属性、encoding 属性、ptr 属性和保留数据无关:
type 属性:示意该对象的类型是什么;
encoding 属性:示意这个对象应用的底层数据结构是什么;
ptr 属性:是一个指向底层数据结构的指针;
refcount 属性是一个援用计数属性,能够用于内存回收和对象共享;
lru 属性,记录了对象最初一次被命令程序拜访的工夫,能够计算出某个键的空转时长。
对象构造的逻辑图如下所示:
内存回收 –refcount 属性
在对象构造中,有 refcount 这个属性,该属性用于记录对象的援用计数信息,redis 利用援用计数 (referencecounting) 技术实现内存回收机制,通过这一机制,程序能够通过跟踪对象的援用计数信息,在适当的时候主动开释对象并进行内存回收。
具体策略:
在创立一个新对象时,援用计数的值会被初始化为 1;
当对象被一个新程序应用时,它的援用计数值会被 +1;
当对象不再被一个程序应用时,它的援用计数值会被 -1;
当对象的援用计数值变为 0 时,对象所占用的内存会被开释。
对象共享 –refcount 属性
Redis 会在初始化服务器时,服务器会创立一万个字符串对象,这些对象蕴含了从 0 到 9999 的所有整数值,当服务器、新创建的键须要用到值为 0 到 9999 的字符串对象时,服务器就会应用这些共享对象,而不是新创建对象。
对象构造中,refcount 是援用指针属性,如果有 N 个键共享一个值,refcount 对应的值就为 N。创立共享字符串对象的数量能够通过 redis.h/redis_shared_intengers 常量来批改。object refcount 命令能够查看某个键对应的值被援用了多少次。
让多个键共享一个值,须要执行以下两个步骤:
将键的值指针,指向被共享的值对象;
被共享的值对象的援用计数器加一,即 refcount 属性的值加一。
援用数为 2 的共享对象结构图如下图所示:
总结阐明
当服务器思考将一个键的值援用共享对象时,键的值作为指标对象,程序须要先查看共享对象和指标对象的类型是否完全相同,只有在完全相同的状况下,共享对象才会被援用。而一个共享对象保留的值越简单,验证共享对象与指标对象所需的复杂度就会越高,耗费的 CPU 工夫也会越多。
所以共享对象的长处是被其它键援用时,能够节俭内存空间,毛病是被援用时须要进行判断,这个过程须要耗费 CPU,如果共享对象简略,耗费很小的 CPU 并节俭内存空间是值得的。
但如果对象共享很简单,进行判断就须要耗费大量 CPU,耗费大量 CPU 去节俭内存空间是不值得的,因为 redis 自身的内存空间还是很大的。
知识点
redis 反对 5 种对象,包含字符串对象、列表对象、哈希对象、汇合对象以及有序汇合对象。而字符串对象是 redis 中的一个根底对象,其它对象均能够在底层的数据结构外部嵌套字符串对象。
对于对象共享:
1、只有字符串对象能力被创立为共享对象,被其它字符串键应用;
2、用字符串对象创立的共享对象,不单单只有字符串键能够应用,那些在数据结构中嵌套了字符串对象的对象 (linkedlist 编码的列表对象、hashtable 编码的哈希对象、hashtable 编码的汇合对象,以及 skiplist 编码的有序汇合对象) 都能够应用这些字符串共享对象。
Q&A
Q
为什么 redis 不共享列表对象、哈希对象、汇合对象、有序汇合对象,只共享字符串对象?
A
列表对象、哈希对象、汇合对象、有序汇合对象,自身能够蕴含字符串对象,复杂度较高。
如果共享对象是保留字符串对象,那么验证操作的复杂度为 O(1);
如果共享对象是保留字符串值的字符串对象,那么验证操作的复杂度为 O(N);
如果共享对象是蕴含多个值的对象,其中值自身又是字符串对象,即其它对象中嵌套了字符串对象,比方列表对象、哈希对象,那么验证操作的复杂度将会是 O(N 的平方);
如果对复杂度较高的对象创立共享对象,须要耗费很大的 CPU,用这种耗费去换取内存空间,是不适合的。
碎碎念
1、当初咱们晓得,redis 为了防止额定的内存耗费,在初始化的时候,为 0~9999 这些整数创立了共享对象。那除了 0~9999,redis 外部是否还设置了其它类型的共享对象?但具体有哪些值被作为了共享对象还不是特地分明,不过应该都是一些简略的值。
2、另外,0~9999 整数是程序初始化时主动创立为共享对象的,咱们是否能够手动创立共享对象?比方咱们认为有很多键对应的值都是雷同的,是否能够手动创立共享对象以节俭内存?如果能够,又有哪些限度要求?
创立的共享对象,当其它键去援用共享对象时,须要进行判断,两者的类型完全相同才能够被利用,共享对象保留的内容越简单,进行判断时须要耗费的 CPU 就越大。
redis 初始化创立的 0~9999 的共享对象,构造很简略,进行判断时耗费的 CPU 很小。然而如果 redis 容许咱们手动为某些值创立共享对象,它的构造只有略微简单一些,就须要耗费很大的 CPU,这无疑是不适合的,所以 redis 为了防止这种不必要的影响,应该不反对手动创立共享对象。
欢送各位独特参加探讨
一起交换沟通~
云掣科技,一家专一于云托管(MSP)服务的企业。