Redis设计与实现笔记--数据结构与对象(一)

1. 简单动态字符串SDS

free 表示未使用的空间; len表示已保存的字符串长度; 

buf是一个char类型的数组,图片中保存了5个字符,最后一个字节空字符'\0'不算入已保存的长度。

最后保留一个空字符,是自动的,方便重用一部分C字符串函数

SDS与C字符串区别

1. SDS获取字符串长度时间复杂度为O(1),而C字符串需要遍历字符串

2.SDS不会缓冲区溢出

比如有紧邻着的C字符串s1和s2

执行 strcat(s1, " Cluster"); 造成了s2的意外修改

SDS空间分配策略可以杜绝溢出:先检查空间,不足就分配,然后再拼接

3. 减少修改字符串带来的内存重分配次数

C字符串增长或者缩短,都要对保存C字符串的数组进行一次内存重分配操作。

SDS通过free和len属性,有两种分配优化策略:

①空间预分配:->用于优化增长

如果SDS修改后len长度小于1MB,则分配与len相同的大小的未使用空间,

那么SDS的buf数组实际长度为len+free+1 (len==free,1为保存空字符 )。

如果SDS修改后len大于等于1MB,那么会额外分配1MB未使用空间,

那么这时的buf实际长度为len+1MB+1byte

②惰性空间释放:->用于优化缩短

缩短字符串不会立即释放buf多余内存,而是修改len和free值,多余的空间在增长时可能会有用

4. 二进制安全

C字符串会识别空字符作为结束符,不能保存含有空字符的数据。(如:图片、音频、视频这样的二进制数据)

SDS有len属性,可以保存任意个数二进制数据。

5.兼容部分C字符串函数

 

2.链表

integers列表键包含了1到1024的整数,底层就是链表。

还有发布与订阅、慢查询、监视器、Redis服务器本身等都会用到链表

dup函数用于复制链表节点所保存的值

free函数用于释放链表节点所保存的值

match函数用于对比链表节点所保存的值和另一个输入值是否相等

Redis链表特性:

         双端:有prev和next指针

         无环:表头prev和表尾next都指向null

         表头指针和表尾指针

         带链表长度属性len

         多态:dup、free、match设置类型特定函数,用于保存不同类型的值

 

3.字典

website键的底层就是一个字典,该字典包含了10086个键值对

哈希表结构图:

size哈希表大小;sizemask哈希表大小掩码,用于计算索引值,总是等于size-1;used已有

字典结构图:

例: 如果将键值对k0和v0添加到字典里面,程序会先使用语句:

hash = dict -> type -> hashFunction(k0);

计算键k0的哈希值

假设哈希值为8,那么继续计算索引值

index =  hash&dict->ht[0].sizemask = 8 & 3 = 0;

那么会将这个键值对放在dictEntry中索引为0的位置

解决冲突

使用链地址法

因为没有链尾指针,所以冲突插入的节点都是在链头插入的

rehash

① 为字典ht[1]哈希表分配空间

         如果是扩展,ht[1]大小为第一个大于等于ht[0].used * 2 的2^n

         如果是搜索,ht[1]大小为第一个大于等于ht[0].used 的2^n

②将ht[0]上的所有键值对rehash到ht[1]上面

③ 迁移完后,释放ht[0],将ht[1]设置为ht[0],并在ht[1]新创建一个空白哈希表,为下次rehash作准备

扩展和收缩

当服务器没有执行BGSAVE或者BGREWRITEROF时,并且负载因子大于等于1;

或者,正在执行上述命令,并且负载因子大于等于5。进行扩展

当负载因子小于0.1时,自动开始收缩操作。

渐进式rehash

当普通情况下rehashidx值为-1,当rehash过程中,随着进展改变,其实是根据ditEntry数组顺序的

在rehash中,会同时使用ht[0]和ht[1],查找键现在ht[0]找,再从ht[1]找。

新添加的键值对直接保存到ht[1]

 

4.跳跃表

在redis中实现有序集合键或者在集群节点中用作内部数据结构,最快logN,最慢N

header指向表头结点

tail指向表尾

level层数最大的那个节点的层数

length包含节点数量,表头节点不计入

表头节点其实和其他节点一样,省略了BW、score和obj,因为用不到

 

5.整数集合

encoding表示着整型最大可表示的范围

contents按从小到大保存

升级

当当前的encoding不能存放新的元素,就会升级,扩展分配空间。

往往新元素放在开头或者最后,

这样可以提升灵活性、节约内存。

不支持降级,如把刚刚造成升级的元素删除,编码也不会回复原来的状态

 

6.压缩列表

列表键和哈希键实现之一,都包含短字符串和小整数值才会使用。

zlbytes压缩列表总长

zltail指向尾部偏移量,指向列表起始地址指针p加上该偏移量讲就是entryN的地址

zllen实体节点数量

entry节点结构:

previous_entry_length 属性长度是1字节或者5字节

如果前一节点小于254字节,那么属性为1字节

如果前一节点大于等于254节点,属性第一个字节设置成0xFE,后面4字节保存长度

这样就可以通过偏移计算前一个节点的起始位置,可以从尾向头遍历

encoding记录了content的类型和长度

开头00、01、10分别表示1字节、2字节、5字节的数组编码,后面几位表示长度

开头11表示整数编码

content,例:

连锁更新

当插入一个254以上的节点,而其他节点都在250-253之间,那么会引起一系列内存重分配

删除也会导致这样,但是总的来说这样的概率很低,所以放心。。

 

7.对象

Redis真正实现的是对象,是基于前面至少一种数据结构

一般会有键和值两个对象用redisObject表示

使用TYPE命令时返回的是键对应的值对象类型

使用OBJECT ENCODING命令返回键对应的值对象的编码

encoding属性极大提高了效率,不同场景下用不同编码

字符串对象

编码可以是int、raw(SDS)、embstr(SDS)

如果保存的字符串对象值长度小于等于44字节,会用embstr(针对短字符串有优化)

raw会调用两次内存分配来形成redisObject和sdshdr,释放需两次

embstr只需调用一次,释放也只需一次

long double类型也会用字符串来保存,执行操作才会转换成浮点型,然后把结果转换成字符串保存

编码转换

int类型的数据经过APPEND命令加入字符串,会转成raw

embstr是只读的,经过修改命令后会变更成raw编码

列表对象

ziplist和linkedlist

满足以下两个条件会用ziplist编码:

       所有字符串元素长度都小于64字节

       元素数量小于512个

哈希对象

ziplist或者hashtable

满足一下条件会使用ziplist

       键和值的字符串长度都小于64字节

       键值对数量小于512

集合对象

intset或者hashtable

满足以下两个条件,会使用intset编码

       所有元素都是整数值

       元素数量不超过512个

有序集合对象

ziplist或者skiplist

满足以下条件,会使用ziplist编码

       元素数量小于128个

        所有元素长度小于64字节

类型检查与命令多态

执行命令前,通过检查type属性来判断是否执行

然后检查encoding来确定具体用哪个对应的API来执行

内存回收

引用计数(跟jvm垃圾回收类似)

创建新对象时,引用计数值初始化为1

随着被程序引用和完成,计数增减,到0后对象内存会被释放

对象共享

和上面的内存回收搭配使用,

在初始化服务器时,会创建0-9999所有整数值的一万个字符串对象

当服务器需要这些字符串对象时,会直接引用,计数+1,而不会新建

由于验证复杂的对象耗cpu,所以redis只对整数值的字符串对象进行共享

对象空转时长

redisObject中有个lru属性记录了对象最后一次被访问的时间

由命令OBJECT IDLETIME命令返回,他会访问对象,但不会修改对象lru属性

如果内存超过maxmemory上限,会优先释放lru属性大的对象

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值