Redis设计与实现读书笔记-数据结构与对象

一.简单动态字符串

简单动态字符串(simple dynamic string,SDS)是redis的默认字符串表示,除此之外,SDS还被用做缓冲区(AOF模块中的AOF缓冲区和客户端状态中的输入缓冲区),AOF模块缓冲区指的是在做AOF备份的时候新增加的指令会缓冲到缓冲区,之后再发起部分同步到磁盘;客户端状态的输入缓冲区是指在服务端保存着客户端输入指令的一个缓冲区.

SDS的表示结构如下:

struct sdshdr{
    //记录buf数组中已使用的字节的数量,等于SDS所保存字符串的长度
    int len;
    //保存buf数组中未使用字节的数量
    int free;
    //字节数组,用于保存字符串
    char buf[]
}

以上两图为SDS存储示例.图1中free为0,表示该SDS没有分配任何未使用空间,len=5表示存储的字符串字节长度,buf指向保存数据的数组,结尾保存空字符串'\0',这是为了遵循C的保存习惯,以便可以使用部分C的函数,但不计入len的统计中.图二中free=5表示除了保存的redis字符串之外,还分配了5字节未使用的空间.

因为保存了字符串的长度,所以redis获取字符串的长度时间复杂度为O(1),同时可以杜绝缓冲区溢出,当需要修改SDS时,会先检查空间是否足够,不够的话会先扩展空间再保存新的数据.

通过buf中的free表示的未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略.

1.1空间预分配

当进行字符串增长操作,需要对SDS进行空间扩展时,程序会在分配修改所需的必要空间之外,再分配额外的未使用空间,分配策略如下:

如果修改之后SDS的长度小于1MB,那么程序将分配和修改之后len长度同样大小的未使用空间;如果修改之后的SDS长度大于1MB,那么

程序将分配1MB的未使用空间.

1.2惰性空间释放

当进行字符串缩短操作时,修改后空余出来的空间并不会被立即释放,而是记录在free中,当下次进行字符串的扩展时,如果字符串长度小

于free的值,就不需要进行空间扩展操作,通过这个策略避免了既避免了缩短字符串之后的内存重分配操作,又为将来的拓展留出空间,同

时SDS提供了专门释放空间的api,不需要担心free空间太大造成的内存浪费.

以下是SDS字符串与C字符串的区别:

 

二.链表

链表在redis中的应用比较广泛,list类型的值对象底层实现之一就是链表,当列表键中包含了数量比较多的元素,或者包含的元素都是比较

长的字符串时,就会使用链表(后面笔记中记录),除此之外发布与订阅,慢查询,监视器等功能也用到了链表,Redis服务器本身还用链表来保

存多个客户端的状态信息以及构建客户端输出缓冲区.

redis的链表实现是双端无环链表,其结构与示意图如下:

此外还通过list结构对链表进行持有;示意图及结构如下:

三.字典

字典中一个key对应一个value,每个key是唯一的,redis数据库底层就是使用字典实现,增删改查操作也是建立在对字典的操作之上.除此

之外,字典还是值对象类型为hash时的底层实现,当一个hash值对象包含的数据比较多或者包含的数据的长度都比较长的时候,redis会使

用字典作为其底层实现,而字典的底层又是使用哈希表实现,每个哈希表包含多个哈希节点每个哈希节点保存了字典中的一个键值对

redis哈希表使用链地址法解决哈希冲突,多个冲突的节点通过next指针相链接,当有冲突时,新的节点放在其他节点的前面.当hash表中

数据过多或者过少时,会通过rehash来重新分配空间(将原来小空间哈希表上的节点rehash保存到另外一个大空间哈希表上的,之后将原

来的小表置空).

3.1哈希表的扩展与收缩

当以下条件中的任意一个满足时,程序会自动开始对hash表进行扩展操作:

1)服务器目前没有执行BGSAVE或者BGREWRITEAOF命令,且韩系表的负载因子大于等于1(负载因子=已保存的节点数量/哈希表大小)

2)服务器正在执行以上两个命令中的一个,但是负载因子大于等于5

当哈希表负载因子小于0.1,程序自动开始对哈希表执行收缩操作.

3.2渐进式rehash

当扩展或收缩哈希表的时候,需要对其中保存的键值对进行rehash,但是为了表面对服务器性能造成影响,并不是一次性rehash全部键值

对而是分多次渐进式分配.渐进式rehash期间新增的键值对不会保存到老的哈希表中,会直接进入新hash表.

 

四.跳表

redis使用跳表(skiplist)作为zset对象类型的底层实现之一:当一个zset包含的元素数量比较多或者包含的成员都是比较长的字符串时

redis中只在两个地方使用了跳表,一个是实现zset数据类型,另外一个是在集群节点中用作内部数据结构.同一个跳跃表中,各个节点保存

的成员对象必须是唯一的,但是分值可以相同,分值相同时按照字典排序小的在前大的在后.

 

五.整数集合

整数集合(intset)值对象为set数据类型的底层实现之一,当一个集合质包含整数值元素,并且集合的元素数量不多时.

intset是redis用来保存整数值的集合抽象数据结构,可以保存类型为int16_t,int32_t或者int64_t的整数值,并且保证不会出现重复:

整数集合数据结构如下:

typedef struct intset{
    //编码方式
    uint32_t encoding;
    //集合包含的元素数量
    uint32_t length;
    //保存元素的数组
    int8_t contents[];
}intset

contents[]用于保存集合中的元素(按照值的大小从小到大,不重复)

encoding中保存的编码方式决定了contents[]中保存的值类型,有三种对应关系,encoding(content)->INTSET_ENC_INT16(int16_t):可保存-32768~32767;INTSET_ENC_INT32(int32_t)可保存-2147483648~2147283647,INTSET_ENC_INT64(int64_t)可保存-9223372036854775808~9223372036854775807

升级

当要将一个新元素添加到整数集合里时,如果新元素的类型比整数集合现有的所有元素的类型都长,整数集合会先进行升级,然后才将新元素添加进去,比如现有三个是int16_t,现在要添加一个int32_t会先将之前的三个转换为int32_t,然后再加入新元素.升级的好处一是提升整数集合的灵活性,另外能够节约内存空间,目前暂不支持降级\

六.压缩列表

压缩列表(ziplist)是list和hash值对象的底层实现之一,当一个list只包含少数元素,并且每个元素要么是小整数值要么是长度比较短的

字符串,或者一个hash键的值包含少量的键值对,并且每个键值对要么要么是小整数值要么是长度比较短的字符串.

压缩列表是redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多

个节点(entry),每个节点可以保存一个字节数组或者一个整数值.

七.对象

7.1对象类型与编码

Redis使用对象来保存数据库中的键和值,分别为键对象和值对象.每个对象由一个redisObject结构表示,该结构中和保存数据有关的

三个属性分别是type,encoding和ptr:

typedef struct redisObject{
    //类型
    unsigned type:4;
    //编码
    unsigned encoding:4;
    //指向底层实现数据结构的指针
    void *ptr;
    //...
}

对于redis数据库保存的键值对来说,键总是一个字符串对象,值可以是字符串对象,list对象,hash对象,set对象和zset对象.可以使用

tape命令查看值对象的类型,命令格式为type key.

对象的ptr指针指向对象的底层实现数据结构,由对象的encoding属性决定,encoding属性记录了对象的底层实现,这个属性的值可以

是下表中列出的常量中的一个:

对象的编码
编码常量编码所对应的底层数据结构
REDIS_ENCODING_INT                         long类型的整数
REDIS_ENCODING_EMBSTR embstr编码的简单动态字符串
REDIS_ENCODING_RAW简单动态字符串
REDIS_ENCODING_HT字典
REDIS_ENCODING_LINKEDLIST双端链表
REDIS_ENCODING_ZIPLIST压缩列表
REDIS_ENCODING_INTSET 整数集合
REDIS_ENCODING_SKIPLIST跳跃表和字典

每种类型的对象都至少使用了两种类型的编码,如下表所示:

不同类型和编码的对象
类型编码对象
REDIS_STRINGREDIS_ENCODING_INT使用整数值实现的字符对象
REDIS_STRINGREDIS_ENCODING_EMBSTR使用embstr编码的简单动态字符串实现的字符串对象
REDIS_STRINGREDIS_ENCODING_RAW使用简单动态字符串实现的字符串对象
REDIS_LISTREDIS_ENCODING_ZIPLIST使用压缩列表实现的list对象
REDIS_LISTREDIS_ENCODING_LINKEDLIST使用双端链表实现的list对象
REDIS_HASHREDIS_ENCODING_ZIPLIST使用压缩列表实现的hash对象
REDIS_HASHREDIS_ENCODING_HT使用字典实现的hash对象
REDIS_SETREDIS_ENCODING_INTSET使用整数集合实现的set对象
REDIS_SETREDIS_ENCODING_HT使用字典实现的set对象
REDIS_ZETREDIS_ENCODING_ZIPLIST使用压缩列表实现的zset对象
REDIS_ZETREDIS_ENCODING_SKIPLIST使用跳跃表和字典实现的zset对象

使用object encoding key命令可以查看对应键的值对象的编码,每种值对象至少使用两种编码可以方便redis在不同的场景下选择合

适的数据实现,提高效率.

7.2字符串对象

字符串对象的编码可以是int,raw或者embstr.,各自使用场景如下:

int:   当字符串对象保存的是整数值,并且该整数值可以用long类型表示

raw:  当保存的是字符串并且该字符串长度大于39字节,使用一个SDS来保存值,并将编码设置为raw

embstr:   当保存的是字符串并且该字符串长度小于39字节,将编码设置为embstr

embstr编码是专门用于保存短字符串的一种优化编码方式,其和raw编码一样,都是用redisObject结构和sdshdr结构来表示字符串对

象,但raw编码需要调用两次内存分配函数来创建redisObject结构和sdshdr结构,而embstr通过调用一次内存分配函数来分配一块连

续的空间,空间中依次包含redisObject结构和sdshdr结构:

使用embstr编码好处是:1.降低内存分配次数(从两次降低为一次)2.释放字符串对象时,raw需要调用两次内存释放函数,embstr只

需要一次3.因为embstr编码对象保存在连续空间中,能更好利用缓存带来的优势

可以用long double类型表示的浮点数在redis中也是作为字符串保存的.编码对象可以相互转换,其中embstr编码没有任何修改程序,因此是只读的.下表是常见字符串命令的实现:

7.3 list对象

list对象的编码可以是ziplist或者linkedlist,当list对象同时满足以下两个条件时,使用ziplist编码,否则使用linkedlist

1.list对象保存的所有字符串元素的长度都小于64字节;

2.元素数量小于512个

以上两个数值在配置文件中可以修改,对应的是list-max-ziplist-value选项和list-max-ziplist-entries

7.4 hash对象

hash对象的编码可以是ziplist或者hashtable,当使用ziplist压缩列表编码时,一个键值对分别保存在一个节点上,一前一后紧挨在一

起.先添加的键值对在表头方向,后添加的在表尾方向;当使用hashtable编码时,底层使用字典作为实现,键值对都是字符串对象.hash

对象同时满足以下两个条件时使用ziplist编码,否则使用hashtable编码:

1.键值对的键和值字符串长度都小于64字节;

2.键值对个数小于512个

以上两个数值在配置文件中可以修改,对应的是hash-max-ziplist-value选项和hash-max-ziplist-entries

7.5 set对象

set对象的编码可以是intset或者hashtable,当对象同时满足以下两个条件是,使用intset编码,否则使用hashtable编码:

1.保存的所有元素都是整数值

2.元素的数量不超过512个

第二个数值在配置文件中可以修改,对应的是set-max-intset-entries

7.6 zset对象

zset对象的编码可以是ziplist或者skiplist.当使用ziplist编码时,每个元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保

存元素的成员(member),第二个节点保存元素的分值(score),列表内按照分值从小到大排序,分值小的在表头方向,分值大的在表尾方

向,skiplist编码看p78.当同时满足以下两个条件时,使用ziplist编码,否则使用ziplist编码:

1.元素数量个数少于128个

2.所有元素成员的长度都小于64字节

以上两个数值在配置文件中可以修改,对应的是zset-max-ziplist-entries选项和zset-max-ziplist-value

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值