简介: 引言 本文是对《redis设计与实现(第二版)》中数据结构与对象相干内容的整顿与阐明。本篇文章只对对象构造,1种对象——字符串对象。以及字符串对象所对应的两种编码——raw和embstr,进行了具体介绍。
引言本文是对《redis设计与实现(第二版)》中数据结构与对象相干内容的整顿与阐明。本篇文章只对对象构造,1种对象——字符串对象。以及字符串对象所对应的两种编码——raw和embstr,进行了具体介绍。表白一些自己的想法与认识,也心愿更多敌人一起来探讨,分享交换。
作者:太阳
云掣科技-数据库团队
数据库工程师
字符串对象
字符串对象能够存储整数、浮点数、字符串,具体策略是:
当存储整数时,用到的编码是int,底层的数据结构能够用来存储long类型的整数;
当存储字符串时,如果字符串的长度小于等于32字节,那么将用编码为embstr的格局来存储;如果字符串的长度大于32字节,将用编码为raw的SDS格局来存储;
当存储浮点数时会先将浮点数转换为字符串,如果转换后的字符串长度小于32字节就用编码为embstr的格局来存储,否则用编码为raw的SDS格局来存储。
下图是一个字符串对象的结构图,最左侧是对象构造,两头跟右侧合起来是raw编码的SDS数据结构(sdshdr),示例图:
raw编码,简略动静字符串(simple dynamic string-SDS)
redis用的并不是C语言传统的字符串,而是本人构建了简略动静字符串(simpledynamic string,SDS)。
当redis打印日志信息或输入报错信息,这些输入的字符串是不会被批改的字符串字面量(sting literal),此时用的是C语言传统的字符串来存储这些信息的。当redis须要存储的是能够被批改的字符串时,就会应用SDS构造。
除了用来保留数据库中的字符串值之外,SDS还被用作缓冲区(buffer):AOF模块中的AOF缓冲区,以及客户端状态中的输出缓冲区,都是由SDS实现的。
SDS的构造
SDS构造示意图如下所示:
sdshdr是该数据结构的名称即SDS,其中:
buf属性,是一个字节数组,用来保留字符串,前面箭头对应的就是理论保留的字符串内容,最初以’0’空字符串结尾;
len属性,记录的是buf数组中理论已应用的字节数量,等于SDS所保留字符串的长度;
free属性,记录的是buf数组中未应用字节的数量。
SDS长处
一、能够用O(1)的复杂度获取到字符串长度
SDS的len属性记录了字符串的长度,而传统C字符串要想晓得长度须要遍历整个字符串。相比于传统C字符串,redis获取字符串长度所需的复杂度从O(N)升高到了O(1)。
即便对十分长的字符串重复执行STRLEN命令(获取字符串长度),也不会造成过多的性能耗费。
二、杜绝缓冲区溢出
在传统的C字符串中,如果要批改字符串的内容,但批改后字符串的长度超过原先的长度就会产生溢出景象。详见下图:
在SDS中,当须要对buf字节数组中存储的内容进行批改(削减或删除)时,API会先通过free和len属性查看SDS的空间是否足够,如果不够的话,SDS会主动扩大空间再对内容进行批改。对于主动扩大空间的策略见下方“空间预调配”的内容。
三、缩小批改字符串长度时所需的内存重调配次数
对于传统C字符串:
如果执行的是增长字符串的操作,如拼接操作(append),那么在执行命令之前,程序须要先通过内存重调配来扩大底层数据的空间大小——否则会产生缓冲区溢出。
如果执行的是缩短字符串的操作,如截断操作(trim),那么在执行这个操作之后,程序须要通过内存重调配来开释字符串不再应用的空间——否则会产生内存透露。
对于redis中的SDS构造:
内存重调配设计简单的算法,是一个比拟耗时的操作,redis作为速度要求严苛、数据会被频繁执行的数据库,如果每次批改字符串都须要进行一次内存重调配,会重大影响性能。
应用SDS,buf数组里能够蕴含未应用的字节,这些字节的数量由free属性记录,能够缩小批改字符串长度时所需的内存重调配次数。
空间预调配和惰性空间开释
通过SDS中free属性定义的未应用空间,SDS能够实现空间预调配和惰性空间开释两种优化策略:
1、空间预调配策略——能够升高字符串增长操作引起的内存重调配
当须要批改SDS的内容,且须要进行空间扩大的时候,程序不仅会为SDS调配批改所需的必须空间,还会为SDS调配额定的未应用空间。
其中,额定调配的未应用空间数量由以下公式决定:
如果对SDS进行批改之后,SDS的长度(即len属性的值)将小于1MB,那么程序将调配和len属性同样大小的未应用空间,这时SDS len属性的值将和free属性的值雷同。
如果对SDS进行批改后,SDS的长度将大于等于1MB,那么程序会调配1MB的未应用空间。
阐明
如果对一个字符串的开端继续追加内容,当字符串整体大小大于1MB时,即便只追加一字节的字符,程序也会额定调配1MB的空间,当再次追加一字节的字符时,程序不会再额定调配1MB的空间,而是应用已有的闲暇空间。
即在扩大空间之前,会先查看未应用的空间是否足够,如果足够,是不会额定再扩大的。
通过空间预调配策略,SDS将间断增长N次字符串所需的内存重调配次数从必然N次升高为最多N次。
2、惰性空间开释策略——能够升高字符串缩短操作引起的内存重调配
当SDS中的字符串长度被缩短时,程序并不会立刻应用内存重调配来回收缩短后多进去的字节空间,而是应用free属性将这些字节的数量记录起来,以备未来应用。
当然,redis提供了相应的命令来真正开释这些未应用空间,防止不必要的内存节约。
四、二进制平安
C字符串中的字符必须合乎某种编码(比方ASCII),并且除了字符串的开端之外,字符串外面不能蕴含空字符,如果字符串除开端外还有其它空字符,那么最先被程序读入的空字符将被误认为是字符串结尾,这些限度使得C字符串只能保留文本数据,而不能保留图片、音频、视频、压缩文件这样的二进制数据。
为了确保redis能够实用于各种不同的应用场景,SDS的API都是二进制平安的(binary-safe),所有SDS API都会以解决二进制的形式来解决SDS寄存buf数组里的数据,程序不会对其中的数据做任何限度、过滤或者假如,数据在写入时是什么样的,它被读取时就是什么样。
这也是RDS的buf属性被称为字节数组的起因——redis不是用这个数组来保留字符,而是用它来保留一系列二进制数据。
五、兼容局部C字符串函数
SDS遵循空字符串结尾这一常规,益处是能够间接重用C字符串函数库里的函数,从而防止了不必要的代码反复。
embstr编码
如果字符串对象保留的是长度小于等于32字节的字符串,那么将会应用embstr编码,embstr编码是专门用来保留短字符串的一种优化编码方式。embstr编码与raw编码对应的字符串对象,都是由对象构造(redisObject)和数据结构(sdshdr)组成的。
区别在于用raw编码的字符串对象会调用两次内存调配函数来别离创立redisObject构造和sdshdr构造,而embstr编码则通过调用一次内存调配函数来调配一块间断的空间,空间中一次蕴含redisObject和sdshr两个构造,embstr编码的字符串对象结构图如下所示:
两者的区别
embstr编码的字符串对象在执行命令时,产生的成果和raw编码的字符串对象执行命令时产生的成果是雷同的的,但应用embstr编码的字符串对象来保留短字符串值有以下益处:
1、embstr编码将创立字符串对象所需的内存调配次数从raw编码的两次升高为一次;
2、开释embstr编码的字符串对象只须要调用一次内存开释函数,而开释raw编码的字符串对象须要调用两次内存开释函数;
3、embstr编码的字符串对象的所有数据都保留在一块间断的内存里,构造更加紧凑,而raw编码是扩散开的,redisObject对象构造和sdshdr数据结构彼此间是用指针相关联的,embstr编码的对象比raw编码的对象可能更好的利用缓存带来的劣势。
编码的转换
int编码的字符串对象和embstr编码的字符串对象在条件满足的状况下,会被转换成raw编码的字符串对象。encoding命令能够查看键对应的值,底层用的是什么编码。
int转换为raw
对于int编码的字符串对象来说,如果咱们向对象执行了一些命令,使得这个对象保留的不再是整数值,而是一个字符串值,那么字符串对象的编码将从int变为raw。
27.0.0.1:6379> set a 100 //设置a=100OK127.0.0.1:6379> object encoding a //查看键a存储的值用的是什么编码"int" 127.0.0.1:6379> append a 'a' //向键a的值中追加内容’a’,此时键a存储的值将变为字符串类型(integer) 4127.0.0.1:6379> get a //查询键a的值"100a" 127.0.0.1:6379> object encoding a //查看键a存储的值当初对应的编码,发现曾经变为raw格局的编码,示意外面当初存储的是字符串"raw"
int编码的字符串,存储的是long类型的整数,范畴是2^63-1(2的63次方减一) ~ -2^63(2的63次方),当存储的整数在该范畴内时,编码为int,当值超过该范畴,编码将转换为embstr。
27.0.0.1:6379> set number1 9223372036854775807 OK127.0.0.1:6379> object encoding number1 "int"127.0.0.1:6379> set number2 9223372036854775808 OK127.0.0.1:6379> object encoding number2"embstr"127.0.0.1:6379> set number3 -9223372036854775808OK127.0.0.1:6379> object encoding number3"int"127.0.0.1:6379> set number4 -9223372036854775809OK127.0.0.1:6379> object encoding number4"embstr"
embstr转换为raw
embstr编码的字符串对象无奈被批改(redis没有为embstr编码的字符串对象编写任何响应的批改程序),只有int、raw编码的字符串对象能够被批改,所以embstr编码的字符串实际上是只读的。
当对embstr编码的字符串对象执行任何批改命令时,程序都会先将对象的编码从embstr转换为raw,而后再执行批改命令。所以一旦embstr编码的字符串被批改,它的数据结构就会变成raw编码的格局。
127.0.0.1:6379> set a 'ab'OK127.0.0.1:6379> object encoding a"embstr"127.0.0.1:6379> append a 'c'(integer) 3127.0.0.1:6379> get a"abc"127.0.0.1:6379> object encoding a"raw"
以上就是依据《redis设计与实现(第二版)》中数据结构与对象相干内容进行的局部整顿与分享,欢送各位独特参加探讨一起交换沟通。
云掣科技,一家专一于云托管(MSP)服务的企业