redis设计与实现读书笔记

这里主要记录一下在阅读redis设计与实现中碰到的一些没有记录过的知识。

引用计数技术

Redis的对象系统实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放;另外,Redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存。

关于引用计数技术:当一个程序开始使用这个对象时,它会将这个对象的引用计数加1;而当一个程序抛弃该对象时,它会将这个对象的引用计数减1。当某个对象的引用计数降至0时,Redis会自动回收该对象占用的内存空间。

关于redis的实现:每个redisObject对象在创建时都会初始化一个refcount属性,用来记录该对象被引用的次数。

关于redis通过引用计数技术实现了对象共享机制和垃圾回收机制

Redis通过引用计数技术和对象共享机制来节约内存空间。当多个Redis客户端使用相同的字符串值时,Redis会将它们的redisObject对象合并成一个,并且他们引用计数属性会自增以记录所有这些客户端都在使用这个对象。

具体地说,Redis的共享机制是通过字典结构实现的,Redis内部维护了一个字符串对象池(stringpool),里面保存着所有已经创建的字符串对象。当一个新的字符串被创建时,Redis首先检查stringpool中是否已经存在该字符串,如果存在,那么就直接返回这个对象的引用,并且将它的引用计数加1;如果不存在,则创建一个新的redisObject对象,并将该对象加入到stringpool中,同时返回该对象的引用。

对象共享机制的优点

这种实现方式能够显著减少Redis的内存占用,因为许多大量出现的字符串只需要被存储一次,并且可以被多个客户端共享使用。同时,由于引用计数技术的存在,只有当所有的客户端都不再使用某个共享对象时,Redis才会自动回收该对象所占用的内存空间。

redis对象系统介绍

对象的类型与编码

Redis 中的每个对象都由一个redis0bject结构表示,该结构中和保存数据有关的三个属性分别是type属性、encoding属性和ptr属性:

type属性:记录了对象的类型。

为下图中类型的一个

对于Redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种。

对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定。 

encoding属性:记录了对象所使用的编码,也就是说这个对象使用了什么数据结构作为对象的底层实现。

encording属性的作用

通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大地提升了Redis 的灵活性和效率,因为Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化对象在某一场景下的效率。

 这就是encording属性的作用,可以方便的更改对象的底层数据结构。

字符串对象

首先,字符串对象可以使用的编码有三种int、 raw或者embstro。

int:如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,就会使用int编码也就是以整数类型存储。

raw:如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值。

embsto:如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。

embsto与raw的区别

 embstr编码是专门用于保存短字符串的一种优化编码方式,其与raw一样也是一个redisObject和sdshdr结构来表示字符串对象,区别在于,raw编码会调用两次内存分配函数来创建redisObject和sdshdr,而embstr通过一次内存分配函数来分配一块连续的空间依次包含redisObject和sdshdr。

对于是embstr编码还是raw编码的字符串对象来说,执行命令时产生的效果是相同的,但使用embstr编码字符串对象来保存短字符串值有以下好处:

 但是在长字符串的情况下,这种连续的内存结构,就会因为要为每个字符串值单独分配一块内存,这种情况导致内存浪费,所以在长字符串的情况下就会使用raw编码。 

对于redis来说,浮点数的保存也是以字符串来进行保存的。

编码转换

int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象。

int变为字符串

对于这个对象保存的值进行修改让这个对象保存的值不再是整数,就会让int变成字符串。

embstr变成raw

这个主要是在修改embstr的时候会发生这种情况,因为在embstr编码的字符串对象实际上是只读的,也就是说对于embstr编码的对象来说,它没有修改程序,所以在对embstr编码的对象进行数据修改的时候一般来说是先进行转换出raw之后,才会进行修改。

列表对象

编码类型,ziplist或者linkedlist

ziplist底层是用压缩链表,linkedlist底层是双向链表。

不在这两个条件的情况下会变成linkedlist编码。

哈希对象

编码类型:ziplist或者hashtable。

ziplist底层是压缩链表,hashtable底层是dict

 不在这两个条件的情况下会变成hashtable编码。

集合对象

集合对象的编码可以是intset或者hashtable。

intset的底层是整数集合,集合对象的所有元素,都被包含在整数集合里面,hashtable的底层是dict,每一个键都是一个字符串对象,对象里包含了一个集合元素。值全部设置为null.

有序集合对象

有序集合的编码可以是ziplist或者skiplist。

ziplist底层是压缩链表,skiplist是跳表加字典

这里具体介绍一下skiplist为什么要现在跳表加字典来作为底层,

在理论上,有序集合可以单独使用字典或者跳跃表的其中一种数据结构来实现,但无论单独使用字典还是跳跃表,在性能上对比起同时使用字典和跳跃表都会有所降低。举个例子,如果我们只使用字典来实现有序集合,那么虽然以O(1)复杂度查找成员的分值这一特性会被保留,但是,因为字典以无序的方式来保存集合元素,所以每次在执行范围型操作―—比如 ZRANK、ZRANGE等命令时,程序都需要对字典保存的所有元素进行排序,完成这种排序需要至少o(NlogN)时间复杂度,以及额外的o(N)内存空间(因为要创建一个数组来保存排序后的元素)。

 另一方面,如果我们只使用跳跃表来实现有序集合,那么跳跃表执行范围型操作的所有优点都会被保留,但因为没有了字典,所以根据成员查找分值这一操作的复杂度将从0(1)上升为o(logN)。因为以上原因,为了让有序集合的查找和范围型操作都尽可能快地执行,Redis 选择了同时使用字典和跳跃表两种数据结构来实现有序集合。

所以redis使用了跳表加字典来作为底层,在需要执行那个数据结构更擅长的方面时,使用那个数据结构的api.

然后,因为这两种数据结构都会通过指针来共享相同元素的成员和分值,所以不会有同一个数据被存储两次这种事情,也就不会用内存浪费。

类型检查机制

这个机制相当于redis在用户输入针对键的命令后,由服务器检查输入数据库键的值对象是否为执行命令需要的类型,如果是就会执行。然后这样又有一个问题,那就是对于很多对象都要两种编码类型,我们不会一直清楚这个对象现在是什么编码类型,所以这里就有了redis的命令多态。

多态命令

Redis除了会根据值对象的类型来判断键是否能够执行指定命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。

这两种机制的实现图如下

 对象的空转时长

redis0bject结构包含的最后一个属性为1ru属性,该属性记录了对象最后一次被命令程序访问的时间,当服务器占用内存数超过maxmemory选项所设置的上限值,空转时长长的会优先被回收。

关于共享对象那个事情,有一个问题就是Redis不共享包含宇符串的对象

因为当服务器考虑将一个共享对象设置为键的值对象时,程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同,只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象用作键的值对象,而一个共享对象保存的值越复杂,验证共享对象和目标对象是否相同所需的复杂度就会越高,消耗的CPU时间也会越多:

如果共享对象是保存整数值的字符串对象,那么验证操作的复杂度为O(1);如果共享对象是保存字符串值的字符串对象,那么验证操作的复杂度为O(N);如果共享对象是包含了多个值(或者对象的)对象,比如列表对象或者哈希对象,那么验证操作的复杂度将会是O(N)。

因此,尽管共享更复杂的对象可以节约更多的内存,但受到CPU时间的限制,Redis只对包含整数值的字符串对象进行共享。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值