您的位置:首页 > 理论基础 > 数据结构算法

我知道点redis-数据结构与对象(对象)-对象实现

2015-08-25 08:42 666 查看

我知道点redis-数据结构与对象(对象)-对象实现

8.7 类型检查与命令多态

Redis中用于操作key 的命令基本上可以分为两种类型:

其中一种命令可以对任何类型的key 执行,比如
DEL
EXPIRE
RENAME


另一种命令只能对特定类型的key 执行

8.7.1 类型检查的实现

为了确保只有指定类型的key可以执行某些特定的命令,在执行一个类型特定的命令之前,Redis会先检查输入key 的类型是否正确,然后在决定是否执行给定的命令。

类型特定命令所进行的类型检查是通过
redisObject
结构的
type
类型来实现的。

8.7.2 多态命令的实现

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


举个例子,在前面介绍列表对象的编码时我们说过,列表对象有
ziplist
linkedlist
两种编码可用,其中,牵着使用压缩列表的API来实现列表命令,而后者则使用双端链表API来实现列表命令。



借用OO的术语来说,我们可以认为
LLEN
命令是多态(polymorphism)的,只要执行
LLEN
命令的是列表key ,那么无论对象使用的是
ziplist
编码还是
linkedlist
编码,命令都可以正常执行。


实际上,我们可以将
DEl
EXPIRE
TYPE
等命令也称为多态命令,因为无论输入的key 是什么key ,这些命令都可以正确的执行。

DEL
EXPIRE
等命令和
LLEN
等命令的区别在于:

前者是基于类型的多态——一个命令可以同时用于处理多种不同类型的key ;

后者是基于编码的多态——一个命令可以同时用于处理多种不同编码。

8.8 内存回收

因为C语言并不具备自动内存回收功能,所以Redis在自己的对象系统中构建了一个引用计数(refrence counting)技术实现的内存回收机制,通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。

每隔对象的引用计数信息有
redisObject
结构的
refcount
属性记录:

typedef struct redisObject {

// ...

// 引用计数
int refcount;

// ...

}

对象的引用计数信息会随着对象的使用状态而不断变化:

在创建一个新对象时,引用计数的值会被初始化为1;

当对象被一个新程序使用时,他的引用计数值会被增1;

当对象不再被一个程序使用时,他的引用计数会减1;

当对象的引用计数值变为0时,对象所占用的内存会被释放。

8.9 对象共享

除了用于实现对象引用计数内存回收机制之外,对象的引用计数属性还带有对象共享的作用。


假设key A创建了一个包含整数值100的字符串对象作为value object。



如果这时key B也要创建一个同样保存了整数值100的字符串对象作为value object,那么服务器会有以下两种做法:




为key B新创建一个包含了整数值100的字符串对象。

让key A和key B共享同一个字符串对象。



以上两种方法很明显是第二种更节约内存。


在Redis中,让多个key 共享同一个value object需要执行一下两个步骤:

将db key 的value pointer 指向一个现有的value object;

将被共享的value object 的引用计数加一。

目前Redis会在初始化server时,创建1w个字符串object,这些object包含了从0到9999的所有integer,当server需要用到值为0-9999的字符串object时,server就会使用这些共享object。


创建共享的字符串object的数量可以通过修改
redis.h/REDIS_SHARED_INTEGERS
常量来修改。


另外,这些共享object不单单只有字符串key可以使用,那些在数据结构中钱逃了字符串对象的对象(
linkedlist
hashtable
zset
)都可以使用这些共享对象。

为什么Redis不共享包含字符串的object?

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

如果共享对象保存的是int value,那么验证的操作复杂度是O(1);

如果共享object保存的是string value ,那么验证的操作复杂度是O(N);

如果共享对象是包含了多个value,比如list、hash等,那么复杂度将会是O(N^2);

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

8.10 对象的空转时间

除了前面介绍过的
type
encoding
ptr
refcount
四个属性之外,
redisObject
结构包含的最后一个属性就是
lru
属性,该属性记录了对象最后一次被命令程序访问的时间:

typedef struct redisObject {

// ...

unsigned lru:22;

// ...

}

OBJECT IDLETIME
命令可以打印出给定key的空转时长,这一空转时长就是通过将当前的时间减去key的value object 的lru time计算得出的。


OBJECT IDLETIME
命令的实现是特殊的,这个命令在访问key 的value object时,不会修改对象的
lru
属性。


除此之外,key的空转时长还有另外一个作用,如果server打开了maxmemory选项,并且server用于回收内存的算法为
volatile-lru
或者
allkeys-lru
,那么当server占用的memory超过了maxmemory选项所设置的上限值时,空转时长较高的那部分key会优先被server释放,从而回收内存。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: