1.类型检查与命令多态
redis中用于操作键的命令可以分为两种,一种是可以对任何类型的键执行的命令,比如del,expire,rename,type,object等;另一种是只能对特定类型的键执行,比如set,hset,lpop,rpush,sadd,zadd等等。
1.1 类型检查的实现
为了确保只有制定类型的键才可以执行某些特定的命令,在执行一个命令前,会先检查输入键的类型是否正确。
类型特定命令所进行的类型检查是通过redisObject结构的type属性来实现的:在执行一个命令前,服务器会先检查输入数据库键的值对象是否为执行命令所需的类型,是的话则执行,否则,拒绝执行,返回错误。
1.2 多态命令的实现
redis除了会根据值对象的类型来判断键是否能执行指定命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行。
例如:当我们执行llen命令时,列表对象有ziplist和linkedlist两种编码可用,那么服务器会现根据对象的编码选择正确的llen命令实现,如果是ziplist编码,将使用ziplist函数来返回列表的长度;如果是linkedlist,程序将使用listlength函数来返回双端列表的长度。
2.内存回收
因为C语言并不具备自动内存回收的功能,所以redis在自己的对象系统中构建了一个引用计数来实现内存的回收机制。
每个对象的引用计数信息由redisobject结构的refcount属性记录,在创建一个对象时,引用计数的值会被初始化为1;当对象被一个新程序使用时,他的引用计数值会被增1;当对象不再被一个程序使用时,他的引用计数值会被减1,当对象的引用计数值变为0时,对象所占用的内存会被释放。
3.对象共享
对象的引用计数属性除了用于实现引用计数内存回收之外,还带有对象共享的作用。例如:键A创建了一个包含整数值100的字符串对象作为值对象,这是键B也要创建一个同样保存了整数值100的字符串对象,那么服务器有以下两种做法:
- 为键B创建一个包含整数值100的字符串对象;
- 让键A和键B共享同一个字符串对象;
在redis中,让多个键共享同一个值对象需要执行以下两个步骤:
- 将数据库键的值指针指向一个现有的值对象;
- 将被共享的值对象的引用计数加1;
目前来说,redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值,当需要用到时,服务器会使用这些共享对象,而不是新创建对象;另外,共享对象不单单只有字符串键可以使用,那些在数据结构中嵌套了字符串对象的对象(如列表,哈希对象,集合对象,有序集合)都可以使用这些共享对象;
为什么redis只共享整数型字符串对象?
因为当服务器考虑将一个共享对象设置为键的值对象时,程序需要先检查给定的共享对象和键想创建的目标对象是否完全相同,一个共享对象越复杂,验证两个对象是否相同就会越复杂,消耗的cpu时间越多。
4.对象的空转时长
除了前面介绍的type、encoding、ptr、refcount四个属性之外,redisObject结构还包含一个属性lru,该属性记录了对象最后一次被命令程序访问的时间;Object idletime命令可以打印出给定键的空转时长,这一空转时长就是通过当前时间减去键的值对象的lru时间计算得出的。
object idletime命令不会修改值对象lru属性;
空转时长的另外一个作用:当服务器打卡了maxmemory选项,并且服务器用于内存回收的算法为volatile-lru或者allkeys-lru,那么服务器占用的内存数超过了maxmemory选项所设置的上限值时,空转时长较高的那部分键会被优先释放。