乐趣区

关于redis:redis的5种对象与8种数据结构之字符串对象上

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

退出移动版