前面已经介绍了 SDS,双端链表,字典,整数集合,压缩列表等数据结构,但是Redis 并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构,创建了一个对象系统:
- 字符串对象
- 列表对象
- 哈希对象
- 集合对象
- 有序集合对象
redis 使用这些对象来表示数据库中的键和值。
typedef struct redisObject {
unsigned type;
unsigned encoding;
void* ptr;
int refcount; // 引用计数
}
object.png
类型
对于redis保存的键值对来说:
- 键总是一个字符串对象
- 值可以是字符串对象,列表对象,哈希对象,集合对象,有序集合对象
对象的type
属性记录了对象的类型。
redis > SET msg "hello world"
OK
redis > TYPE msg
string
编码
对象的ptr
指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding
属性决定。
使用OBJECT ENCODING
命令可以查看一个数据库键的值对象的编码
redis> SET msg "hello world"
OK
redis> OBJECT ENCODING msg
"emstr"
type | encoding | 对象 |
---|---|---|
REDIS_STRING | REDIS_ENCODING_INT | 使用整数值实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_EMBSTR | 使用embstr编码的简单动态字符串实现的字符串对象 |
REDIS_STRING | REDIS_ENCODING_RAW | 使用简单动态字符串实现的字符串对象 |
REDIS_LIST | REDIS_ENCODING_ZIPLIST | 使用压缩列表实现的列表对象 |
REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 使用双端链表实现的列表对象 |
REDIS_HASH |
类型检查与命令多态
Redis 中用于操作键的命令基本上可以分为2类:
- 其中一种对任何类型的都可以执行
- 而另一种只能对特定类型的键执行
类型检查
类型特定命令进行的类型检查是通过来实现,在执行一个类型特定命令之前,服务器会先检查输入数据库键的值对象是否为执行命令所需的类型。
多态命令
Redis 除了根据值对象的类型来判断键是否能够执行指定的命令之外,还会根据值对象的编码方式,选择正确的命令实现代码来执行命令。
比如列表对象有ziplist
和linkedlist
2种编码可以使用,比如现在可以对一个键执行LLEN
命令,
- 如果列表对象的编码为
ziplist
,那么说明列表对象的实现为压缩列表,程序将使用ziplistLen
函数来返回列表的长度 - 如果列表对象的编码为
linkedlist
,那么说明列表对象的实现为双端列表,程序将使用listLength
函数来返回双端链表的长度
完整实现过程如图:
LLEN key.png内存回收
C语言并不具备内存自动回收功能,所以Redis在自己的对象系统中构建了一个引用计数实现的内存回收机制。
typedef struct redisObject {
// ...
int refcount; // 引用计数
} robj;
-
对象共享
除了用于实现引用计数内存回收机制外,对象的引用计数属性还带有对象共享的作用。
比如键A创建了一个包含整数值100的字符串对象作为值对象。此时键B也要创建一个包含整数值100的字符串对象作为值对象。
那么可以:- 将数据库键的值指针指向一个现有的值对象
- 将被共享的值的对象的引用计数增1
实际上,Redis会在初始化服务器时,创建一万个字符串对象,这些对象包含了从0到9999的所有整数值。
void initServer() { ... // 创建共享对象 createSharedObjects(); ... } #define REDIS_SHARED_INTEGERS 10000 void createSharedObjects() { ... // 常用整数 for (j = 0; j < REDIS_SHARED_INTEGERS; j++) { shared.integers[j] = createObject(REDIS_STRING,(void*)(long)j); shared.integers[j]->encoding = REDIS_ENCODING_INT; } ... }
redis> SET A 100 OK redis> OBJECT REFCOUNT A (integer) 2 redis> SET B 100 OK redis> OBJECT REFCOUNT A (integer) 3 redis> OBJECT REFCOUNT B (integer) 3
对象空转时长
typedef struct redisObject {
// ...
unsigned lru:22; // 引用计数
} robj;
lru
记录了对象最后一次被命令程序访问的时间。使用OBJECT IDLETIME
可以打印出给定键的空转时长,这一空转时长就是通过当前时间 - lru时间。
redis > SET msg "hello world"
OK
# 等待一段时间
redis> OBJECT IDLETIME msg
(integer) 20
# 访问
# redis> OBJECT IDLETIME msg
(integer) 0
键的空转时就是:
- 如果服务器打开了
maxmemory
选项 - 并且服务器回收内存的算法为``
那么当服务器占用内存数超过了所设置的上限值时,空转时长较高的那部分键会优先被服务器释放,从而回收内存。
网友评论