8.7 类型检查与命令多态
Redis中用于操作键的命令基本上可以分为两种类型。
其中一种命令可以用于对任何类型的键执行,比如DEL
、EXPIRE
、RENAME
、TYPE
、OBJECT
命令等。
而另一种命令只能对特定类型的键执行
-
SET
、GET
、APPEND
、STRLEN
等命令只能对字符串键执行; -
HDEL
、HSET
、HGET
、HLEN
等命令只能对哈希键执行; -
RPUSH
、LPOP
、LINSERT
、LLEN
等命令只能对列表键执行; -
SADD
、SPOP
、SINTER
、SCARD
等命令只能对集合键执行; -
ZADD
、ZCARD
、ZRANK
、ZSCORE
等命令只能对有序集合键执行;
8.7.1 类型检查的实现
Redis
在执行一个类型特定的命令之前,会先检查输入键的类型是否正确,然后再决定是否执行给定的命令
类型特定命令所进行的类型检查是通过redisObject
结构的type
属性来实现的
- 在执行一个类型特定命令之前,,服务器会先检查输入数据库键的之对象是否为执行命令所需的类型,如果是的话,服务器就对键执行指定的命令;
- 否者,服务器将拒绝执行命令,并向客户端返回一个类型错误。
8.7.2 多态命令的实现
Redis
除了会根据值对象的类型来判断键是否能够执行指定命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行名。
实际上,我们可以将DEL
、EXPIRE
、TYPE
等命令也称为多态命令,因为无论输入的键是什么类型,这些命令都可以正确的执行
DEL
、EXPIRE
等命令和LLEN
等命令的区别在于,前者是基于类型的多态--一个命令可以同时用于处理多种不同类型的键,而后者是基于编码的多态--一个命令可以同时用于处理多种不同编码。
8.8 内存回收
Redis
在自己的对象系统中构建了一个引用计数计数实现的内存回收机制,通过这一技之长,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。
每个对象的引用计数信息由redisObject
结构的refcount
属性记录:
typedef struct redisObject{
// ...
// 引用计数
int refcount;
// ...
}robj;
对象的引用计数信息会随着对象的使用状态而不断变化:
- 在创建一个新对象时,引用计数的值会被初始化为1;
- 当对象被一个新程序使用时,它的引用计数值会被增一;
- 当对象不再被一个程序使用时,他的引用计数值会被减一;
- 当对象的引用计数值变为0时,对象所占用的内存会被释放。
对象的整个生命周期可以划分为创建对象、操作对象、释放对象三个阶段。
8.9 对象共享
除了用于实现引用计数内存回收机制之外,对象的引用计数属性还带有对象共享的作用。
在Redis
中,让多个键共享同一个值对象需要执行以下两个步骤:
1) 将数据库键的值指针指向一个现有的值对象;
2) 将被共享的值对象的引用计数增一。
目前来说,Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当服务器需要用到值为0到9999的字符串对象时,服务器就会使用这些共享对象,而不是新创建对象。
另外,这些共享对象不单单只有字符串键可以使用,那些在数据结构中潜逃了字符串对象的对象(linkedlist
、hashtable
、set
、zset
)都可以使用这些共享对象。
为什么
Redis
不共享包含字符串的对象?
当服务器考虑一个共享对象设置为键的值对象时,程序需要先检查给定的贡献该对象和键想创建的目标对象是否完全相同,只有在共享对象和目标对象完全相同的情况下,程序才会将共享对象用作键的值对象,而一个共享对象保存的值越复杂,验证共享对象和目标对象是否相同的复杂度就会越高,消耗的CPU
时间也会越多:
- 如果共享对象保存整数值的字符串对象,那么验证操作的复杂度为O(1);
- 如果共享对象时保存字符串值得字符串对象,那么验证操作的复杂度为O(N);
- 如果共享对象是包含了多个值(或者对象的)对象,那么验证操作的复杂度将会是O(N2)
因此,尽管共享更复杂的对象可以节约更多的内存,但受到CPU时间的限制,Redis只对包含整数值得字符串对象进行共享。
8.10 对象的空转时长
除了前面介绍过的type
、encoding
、ptr
和refcount
四个属性之外,redisObject
结构包含的最后一个属性为lru
属性,该属性记录了对象最后一次被命令程序访问的时间:
typedef struct redisObject{
// ...
unsigned lru:22;
// ...
}robj;
OBJECT IDLETIME
命令可以打印出给定键的空转时长,这一空转时长就是通过当前时间减去键的值对象的lru
时间计算得出的:
OBJECT IDLETIME
命令的实现是特殊的,这个命令在访问键的值对象时,不会修改值对象的lru
属性。
如果服务器打开了maxmemory
选项,并且服务器用于回收内存的算法为volatile-lru
或者allkeys-lru
,那么当服务器占用的内存数超过了maxmemory
选项所设置的上限值时,空转时长较高的那部分键会被服务器释放,从而回收内存。