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