围着灰机转圈圈

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

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

8.7 类型检查与命令多态

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

  1. 其中一种命令可以对任何类型的key 执行,比如DELEXPIRERENAME
  2. 另一种命令只能对特定类型的key 执行

8.7.1 类型检查的实现

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

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

8.7.2 多态命令的实现

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

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

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

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

DELEXPIRE等命令和LLEN等命令的区别在于:

  1. 前者是基于类型的多态——一个命令可以同时用于处理多种不同类型的key ;
  2. 后者是基于编码的多态——一个命令可以同时用于处理多种不同编码。

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,那么服务器会有以下两种做法:

  1. 为key B新创建一个包含了整数值100的字符串对象。
  2. 让key A和key B共享同一个字符串对象。

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

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

  1. 将db key 的value pointer 指向一个现有的value object;
  2. 将被共享的value object 的引用计数加一。

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

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

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

为什么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 对象的空转时间

除了前面介绍过的typeencodingptrrefcount四个属性之外,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释放,从而回收内存。

posted on 2015-08-25 08:42  围着灰机转圈圈  阅读(360)  评论(0编辑  收藏  举报