redisObject

定义

在自定义的基础数据结构的基础上,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

你可能感兴趣的:(redisObject)