定义
在自定义的基础数据结构的基础上,redis 通过 redisObject 封装整合成了对外暴露的5中数据结构。 首先看看 redisObject 的定义:
#define LRU_BITS 24
typedef struct redisObject { // redis对象
unsigned type:4; // 类型,4bit
unsigned encoding:4; // 编码,4bit
unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */ // 24bit
int refcount; // 引用计数
void *ptr; // 指向各种基础类型的指针
} robj;
其中 type 用于标识 string、hash、list、set、zset 五种数据类型、encoding 用于标识底层数据结构。通过这两个字段的组合,同一种数据类型也有多种实现方式,一个完整的映射关系如下表:
- lru 用于保存对象的LRU时钟
- refcount 为对象的引用计数,redisObject都是通过简单的引用计数法进行垃圾回收
- ptr 保存了指向各种底层数据实例的指针
对象创建
obj *createObject(int type, void *ptr) { // 创建一个对象
robj *o = zmalloc(sizeof(*o));
o->type = type;
o->encoding = OBJ_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
/* Set the LRU to the current lruclock (minutes resolution). */
o->lru = LRU_CLOCK(); // LRU时钟
return o;
}
基础的创建对象函数很简单,申请一个object的空间,记录type和具体数据的指针,并将引用计数置1。针对不同的数据类型 redis 又封装了不同的函数
对象销毁
redis 采用了简单的引用计数,通过redisObject结构体中的refcount对对象的引用进行计数,当引用计数为0时就将对象销毁。
- 当创建一个对象时,引用计数会被初始化为1
- 当对象被重复使用时,会对引用计数进行加一
- 当对象不再被使用时,会对引用计数进行减一
- 引用计数为0时,对象所占的内存你会被释放
void decrRefCount(robj *o) { // 引用计数减一
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
if (o->refcount == 1) { // 当引用计数为1的时候直接释放
switch(o->type) {
case OBJ_STRING: freeStringObject(o); break;
case OBJ_LIST: freeListObject(o); break;
case OBJ_SET: freeSetObject(o); break;
case OBJ_ZSET: freeZsetObject(o); break;
case OBJ_HASH: freeHashObject(o); break;
default: serverPanic("Unknown object type"); break;
}
zfree(o);
} else {
o->refcount--;
}
}
redis 的引用计数十分简单,没有PHP等语言引用计数的复杂染色机制。主要是因为所有对象都是由 redis 自己创建和维护的,不会出现复杂的循环引用场景。
共享对象
在 redis 中有一种特殊的对象,在server初始化的时候创建很多常用的数据,用于全局共享。这部分数据不会被销毁,主要用于server的各种运行标识和用户数据存储。从而起到节省内存目的,比满大街的破铜烂铁不知道高到哪里去。
例如在string对象中,在创建一个数字时,会判断是否在shared.integers的范围中,如果命中就不进行对象创建,直接使用对应的共享对象,并将引用计数加一
if ((server.maxmemory == 0 ||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
value >= 0 &&
value < OBJ_SHARED_INTEGERS)
{ // 使用shared数据,节省内存
decrRefCount(o); // 销毁之前创建的字符串对象
incrRefCount(shared.integers[value]); // 共享对象引用计数加一
return shared.integers[value]; // 返回共享对象
}
shared.integers的默认范围为0-9999
#define OBJ_SHARED_INTEGERS 10000
for (j = 0; j < OBJ_SHARED_INTEGERS; j++) {
shared.integers[j] =
makeObjectShared(createObject(OBJ_STRING,(void*)(long)j));
shared.integers[j]->encoding = OBJ_ENCODING_INT;
}
除了用于共享存储数据的shared.integers,还有很多用于redisServer运行的字符串常量
shared.crlf = createObject(OBJ_STRING,sdsnew("\r\n"));
shared.ok = createObject(OBJ_STRING,sdsnew("+OK\r\n"));
shared.err = createObject(OBJ_STRING,sdsnew("-ERR\r\n"));
可以自行查看server.c中的createSharedObjects函数。
LRU
redisObject中的lru属性专门用来记录对象的被访问情况,lru保存了最近一次对象被正常访问的时间:
#define LRU_BITS 24
typedef struct redisObject { // redis对象
// ...
unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */
// ...
} robj;
如果 redis 设定了maxmemory数值,且maxmemory-policy设置为allkeys-lru或volatile-lru时,redis 会根据对象中的lru属性对DB中的数据进行淘汰。
在每次正常访问数据时,都会更新对应数据的lru时钟
robj *lookupKey(redisDb *db, robj *key, int flags) { // 从DB中查找对应key
dictEntry *de = dictFind(db->dict,key->ptr);
if (de) { // 如果存在
robj *val = dictGetVal(de);
if (server.rdb_child_pid == -1 &&
server.aof_child_pid == -1 &&
!(flags & LOOKUP_NOTOUCH))
{ // 当有rdb和aof子进程在运行时,不进行lru更新
val->lru = LRU_CLOCK(); // 更新lru时间
}
return val;
} else {
return NULL;
}
}
object命令比较特殊,这个命令可以查看key对应的对象的状态:引用计数、编码和lru时钟和系统时钟的时差。这个命令在访问数据的时候并不会更新lru时钟,因为其直接对DB进行查找操作,并没有通过db.c封装的函数进行访问。
void objectCommand(client *c) { // object操作对应的函数
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) { // 获取对象lru和系统lru时间的差值
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)");
}
}
robj *objectCommandLookup(client *c, robj *key) { // 从DB中查找对应数据对象
dictEntry *de;
if ((de = dictFind(c->db->dict,key->ptr)) == NULL) return NULL; // 直接查找db
return (robj*) dictGetVal(de);
}
robj *objectCommandLookupOrReply(client *c, robj *key, robj *reply) { // 获取object,如果不存在reply
robj *o = objectCommandLookup(c,key);
if (!o) addReply(c, reply);
return o;
}
原文
http://czrzchao.com/redisSourceObject