Redis源码阅读笔记(2)-- 对象robj

熟悉redis的同学都应该知道,redis中主要的数据结构包括简单动态字符串sds、双端链表adlist、跳跃表skiplist、压缩列表ziplist和整数集合intset等,我们之前只分析了sds,剩下的几个数据结构我会在后面给大家一一介绍。
我们知道,redis中有字符串对象(string)、列表对象(list)、哈希对象(hash)、集合对象(set)和有序集合对象(zset),而这五种对象都至少用了上面至少一种数据结构实现。通过使用对象“接口”(暂且理解),针对不同的使用场景,对象可以使用不同的数据结构实现,这样可以优化在不同场景下的效率。下面就让我们一起来看看redis中的对象是怎么实现的:

robj数据结构

typedef struct redisObject {
    unsigned type:4;              // 类型
    unsigned encoding:4;          // 编码
    unsigned lru:REDIS_LRU_BITS;  // 对象最后一次被访问的时间
    int refcount;                 // 引用计数
    void *ptr;                    //ptr指向实现对象的数据结构
} robj;

对象的类型type

数据结构中type属性记录了对象的类型,分别是string、hash、list、set和zset,type占用4 bit,其取值和类型如下:

/* Object types */
#define REDIS_STRING 0
#define REDIS_LIST 1
#define REDIS_SET 2
#define REDIS_ZSET 3
#define REDIS_HASH 4

对象的编码encoding

ptr指针指向对象实现的底层数据结构,而这些数据结构由encoding属性标识,encoding也占用4 bit,其取值和对应类型如下:

#define REDIS_ENCODING_RAW 0     // Raw representation 
#define REDIS_ENCODING_INT 1     // Encoded as integer 
#define REDIS_ENCODING_HT 2      // Encoded as hash table 
#define REDIS_ENCODING_ZIPMAP 3  // Encoded as zipmap 
#define REDIS_ENCODING_LINKEDLIST 4 /* Encoded as regular linked list */
#define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define REDIS_ENCODING_INTSET 6  /* Encoded as intset */
#define REDIS_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define REDIS_ENCODING_EMBSTR 8  /* Embedded sds string encoding */

我阅读的源码是redis3.0的,zipmap已经不再使用了,我们就不会再讨论它,有兴趣的同学可以自己看看。

  1. REDIS_ENCODING_RAW:简单动态字符串sds
  2. REDIS_ENCODING_INT:long型值
  3. REDIS_ENCODING_EMBSTR:采用embstr编码的sds
  4. REDIS_ENCODING_HT:字典dict
  5. REDIS_ENCODING_LINKEDLIST:双端链表adlist
  6. REDIS_ENCODING_ZIPLIST:压缩列表ziplist
  7. REDIS_ENCODING_INTSET:整数集合inset
  8. REDIS_ENCODING_SKIPLIST:跳跃表skiplist
    相同type属性的对象,可能对应不用的编码(encoding)实现,例如:同是string对象,编码实现就有三种
    1、OBJ_ENCODING_RAW;
    2、OBJ_ENCODING_INT;
    3、OBJ_ENCODING_EMBSTR。
    总结如下:
    1、string对象(REDIS_STRING)的编码实现:如上;
    2、list对象(REDIS_LIST)的编码实现:REDIS_ENCODING_ZIPLIST、REDIS_ENCODING_LINKEDLIST;
    3、hash对象(REDIS_HASH)的编码实现:REDIS_ENCODING_ZIPLIST、REDIS_ENCODING_HT;
    4、 set对象(REDIS_SET)的编码实现:REDIS_ENCODING_INTSET、REDIS_ENCODING_HT;
    5、zset对象(REDIS_ZSET)的编码实现:REDIS_ENCODING_ZIPLIST、REDIS_ENCODING_SKIPLIST。

访问时间lru

lru属性(占24 bit)表示对象最后一次别访问的时间,根据lru判断对象是否应该被释放,暂不做深入分析;

引用计数refcount

C语言并不具备内存回收机制,所以redis通过refcount记录robj共享的次数,当refcount为0是,表示该对象应该被释放,回收内存;

redis对象的作用:

1、为5种不同的数据类型提供了统一的表达方式–类似于一个统一的接口;
2、通过使用redis对象,针对不同的使用场景,同一种数据类型可以使用不同的数据结构实现,即提升了Redis的灵活性,又可以优化对象在某一场景下的效率;
3、采用引用计数和对象共享机制,节约内存。

对象的操作函数

创建对象

对象的函数主要集中在redis.h和object.c两个文件中,任何对象的创建都会调用一个底层函数–createObject()

robj *createObject(int type, void *ptr) {
    robj *o = zmalloc(sizeof(*o));
    o->type = type;
    o->encoding = REDIS_ENCODING_RAW;
    o->ptr = ptr;
    o->refcount = 1;
    /* Set the LRU to the current lruclock (minutes resolution). */
    o->lru = LRU_CLOCK();
    return o;
}

例如创建一个REDIS_ENCODING_RAW编码的string对象,

robj *createRawStringObject(char *ptr, size_t len) {
    return createObject(REDIS_STRING,sdsnewlen(ptr,len));
}

createRawStringObject()就是调用了createObject()函数创建了一个string对象,其中ptr指针指向了底层的sds数据结构。
创建其他的对象类似。下面列出的是创建5种类型对象的函数:

robj *createRawStringObject(char *ptr, size_t len)  //创建一个简单动态字符串编码(sds)的字符串对象
robj *createEmbeddedStringObject(char *ptr, size_t len) //创建一个embstr编码的字符串对象
robj *createStringObjectFromLongLong(long long value) //根据传入的 long long 整型值,创建一个字符串对象
robj *createStringObjectFromLongDouble(long double value) //根据传入的 long double 类型的数值,创建一个字符串对象
robj *createListObject(void) //创建一个 linkedlist 编码的列表对象
robj *createZiplistObject(void) //创建一个 ziplist 编码的列表对象
robj *createSetObject(void) //创建一个 dict 编码的集合对象
robj *createIntsetObject(void) //创建一个 intset 编码的集合对象
robj *createHashObject(void) //创建一个 ziplist 编码的哈希对象
robj *createZsetObject(void) //创建一个 dist 和 skiplist编码的有序集合对象
robj *createZsetZiplistObject(void) //创建一个 ziplist 编码的有序集合对象

对象的释放

我们知道,对象有引用计数机制,当obj.refcount为0时,redis就会将该对象所占的内存释放。

/*对象的引用计数增一*/
void incrRefCount(robj *o) {
    o->refcount++;
}
/*
 * 为对象的引用计数减一
 * 当对象的引用计数降为 0 时,释放对象。
 */
void decrRefCount(robj *o) {
    if (o->refcount <= 0) redisPanic("decrRefCount against refcount <= 0");
    if (o->refcount == 1) {
        switch(o->type) {
            case REDIS_STRING: freeStringObject(o); break;
            case REDIS_LIST: freeListObject(o); break;
            case REDIS_SET: freeSetObject(o); break;
            case REDIS_ZSET: freeZsetObject(o); break;
            case REDIS_HASH: freeHashObject(o); break;
            default: redisPanic("Unknown object type"); break;
        }
        //释放对象
        zfree(o);
    // 减少计数
    } else {
        o->refcount--;
    }
}

从代码可以看出,释放对象时,现根据对象的类型type,释放对象保存的数据结构,再释放对象。例如释放列表对象时:

void freeListObject(robj *o) {
//根据不同的编码实现,调用不同的底层释放函数
    switch (o->encoding) {
    case REDIS_ENCODING_LINKEDLIST:
        listRelease((list*) o->ptr);
        break;
    case REDIS_ENCODING_ZIPLIST:
        zfree(o->ptr);
        break;
    default:
        redisPanic("Unknown list encoding type");
    }
}

对象释放函数:

void freeStringObject(robj *o) //释放字符串对象
void freeListObject(robj *o) //释放列表对象
void freeSetObject(robj *o) //释放集合对象
void freeZsetObject(robj *o) //释放有序集合对象
void freeHashObject(robj *o) //释放哈希对象

其他对象的操作函数都在object.c文件中,这里就不一一列举了,不过有些函数可能之后我们会讲到,例如tryObjectEncoding()等。

object交互命令

其实,redis为我们提供了object命令,我们可以根据key获取redis内部对象的一些参数:

  • object refcount – 返回key所指的对象的引用计数
  • object encoding – 返回key所指的对象中存放的数据的编码方式
  • object idletime – 返回key所指的对象的空转时长

函数的具体实现:

void objectCommand(redisClient *c) {
    robj *o;
    //返回key所指对象的引用计数
    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);
    // 返回key所指对象的编码
    } 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));
    //返回key所指对象的空闲时间
    } 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)");
    }
}

好吧,就分析到这~加油

你可能感兴趣的:(Redis)