这篇文章将剖析Redis提供给用户的5种对象底层的数据结构和接口
前面讲了Reids底层所使用的一些数据结构:SDS、双端链表、字典、跳跃表等等,但是Redis并没有直接使用这些数据结构来构造键值对数据库。
对象数据结构
在redis.h中定义类redisObject的数据结构如下:
1 | // Redis 对象定义 |
从代码中我们可以看到Redis对象所包含的信息有:类型、编码、实现指针、引用计数和访问时间
类型
对于 Redis数据库保存的键值对来说,键总是一个字符串对象,而值则可以是字符串对象(string)、列表对象(list)、哈希对象(hash)、集合对象(set)或者有序集合对象(zset)的其中一种
1 | /* Object types */ |
编码
对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定encoding属性记录了对象所使用的编码,也即是说这个对象使用了什么数据结构作为对象的底层实现
1 |
每种类型的对象都至少对应两种不同的编码
引用计数
Redis的对象系统实现了基于引用计数技术的内存回收机制,当程序不再使用某个对象的时候,这个对象所占用的内存就会被自动释放;另外, Redis还通过引用计数技术实现了对象共享机制,这一机制可以在适当的条件下,通过让多个数据库键共享同个对象来节约内存。
上面这些功能都是通过refcount
这个属性来实现的:
- 在创建一个新对象时,引用计数的值会被初始化为1
- 当对象被一个新程序使用时,它的引用计数值会被增一
- 当对象不再被一个程序使用时,它的引用计数值会被减一
- 当对象的引用计数值变为0时,对象所占用的内存会被释放
修改引用计数的api如下:1
2
3void decrRefCount(robj *o);
void incrRefCount(robj *o);
robj *resetRefCount(robj *obj);
内存回收
我们可以通过decrRefCount的实现看到当引用计数到达0的时候会自动释放对象所占有的资源
1 | void decrRefCount(robj *o) { |
对象共享
如果想创建一个与另外一个对象含有相同值的对象,这个时候可以启动对象的共享机制。
在Redis中,让多个键共享同一个值对象需要执行以下两个步骤
- 将数据库键的值指针指向一个现有的值对象
- 将被共享的值对象的引用计数增一
比如在从LongLong创建一个字符串对象的时候,首先要判断在不在共享对象的范围内,如果在的话就对引用计数加1
1 | robj *createStringObjectFromLongLong(long long value) { |
对象的基本操作
Redis对象的基本操作包含了创建对象、销毁对象、编码转换等等
大部分操作的实现都在object.c文件中
对象创建
各种对象创建的API如下:1
2
3
4
5
6
7
8
9
10
11
12
13robj *createObject(int type, void *ptr); // 创建对象,设定参数
robj *createStringObject(char *ptr, size_t len); // 创建字符串对象
robj *createRawStringObject(char *ptr, size_t len); // 创建raw编码的字符串对象
robj *createEmbeddedStringObject(char *ptr, size_t len); // 创建embstr编码的字符串对象
robj *createStringObjectFromLongLong(long long value); // 根据传入LongLong创建字符串对象
robj *createStringObjectFromLongDouble(long double value); // 根据传入的LongDouble创建字符串对象
robj *createListObject(void); // 创建双端链表链表编码的列表对象
robj *createZiplistObject(void); // 创建压缩列表编码的列表对象
robj *createSetObject(void); // 创建集合对象
robj *createIntsetObject(void); // 创建整型集合编码的集合对象
robj *createHashObject(void); // 创建hash对象
robj *createZsetObject(void); // 创建zset对象
robj *createZsetZiplistObject(void); //创建压缩列表编码的zset对象
具体我们以列表对象为例,看看创建对象的过程:
1 |
|
对象销毁
各种对象销毁的API如下:1
2
3
4
5void freeStringObject(robj *o);
void freeListObject(robj *o);
void freeSetObject(robj *o);
void freeZsetObject(robj *o);
void freeHashObject(robj *o);
以List为例:
1 |
|
和创建对象分多钟编码格式相对应,释放对象的时候也要根据编码具体执行释放
对象交互指令
Redis提供三个命令用户获取对象的一些参数:
- object refcount
返回key所指的对象的引用计数 - object encoding
返回key所指的对象中存放的数据的编码方式 - object idletime
返回key所指的对象的空转时长
具体的实现如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24void objectCommand(redisClient *c) {
robj *o;
// 返回对戏哪个的引用计数
if (!strcasecmp(c->argv[1]->ptr,"refcount") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyLongLong(c,o->refcount);
// 返回对象的编码
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyBulkCString(c,strEncoding(o->encoding));
// 返回对象的空闲时间
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyLongLong(c,estimateObjectIdleTime(o)/1000);
} else {
addReplyError(c,"Syntax error. Try OBJECT (refcount|encoding|idletime)");
}
}