简介: 引言 本文是对《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)服务的企业。