Redis源码学习简记(七)object原理与个人理解

本文探讨了Redis中的object系统,它将不同数据类型封装为robj,包括string、list、set等。object包含类型、编码、LRU信息、引用计数和数据指针。编码方式有11种,LRU用于缓存替换,refcount管理对象生命周期。对象创建、字符串对象的两种创建方式、整型和浮点数存储以及对象复制等细节也在文中逐一解析。
摘要由CSDN通过智能技术生成

        object是redis中的封装系统。其把string,list,set,zset与hash封装成一个统一的对象,命名为robj。该数据结构中,存储了类型,编码,引用次数,数据与LRU替换算法的一些数据。具体先看看这个数据结构的定义,在server.h中定义。

 //redis属于key-value 数据库
 //nosql数据库,这种映射关系使用dict用来维护
 //而dict实体数据(dictentry)中有字段void *value
typedef struct redisObject {
    unsigned type:4;
    /*
    	string 0
    	list 1
    	set 2
    	zset 3
    	hash 4
    	4个bit

    */

    unsigned encoding:4;
    //编码方式 4个bit
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
                            * LFU data (least significant 8 bits frequency
                            * and most significant 16 bits access time). */
    //24bit,LRU替换算法                            
    int refcount;//引用计数32bit 4字节
    void *ptr;//32位系统4字节 64位系统8字节
    //指向真正数据
} robj;

来详细讲一下这里面的东西。首先type,定义如下,其实就是4个bit用来存储0~4的数据。

/* The actual Redis Object */
#define OBJ_STRING 0    /* String object. */
#define OBJ_LIST 1      /* List object. */
#define OBJ_SET 2       /* Set object. */
#define OBJ_ZSET 3      /* Sorted set object. */
#define OBJ_HASH 4      /* Hash object. */

然后编码方式encoding。编码方式的话,总的来说有11种,也是用4个bit来存,4个bit最大为16种编码。

#define OBJ_ENCODING_RAW 0     /* Raw representation */
#define OBJ_ENCODING_INT 1     /* Encoded as integer */
#define OBJ_ENCODING_HT 2      /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3  /* Encoded as zipmap */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

接下来的为lru,用于存储LRU替换算法,现在先不深究,使用了LRU_BITS (24bits)三个字节存储。

refcount为引用指针,作用跟智能指针相似,用于计算对象的引用次数,当对象引用次数为0时,则释放其使用空间。

最后的void *ptr则为最终指向数据的指针。这些数据基本就是之前分析的那些类型的数据。

数据结构不难理解,然后看看他的基本实现函数吧。基本都在object.c中实现。

首先是创建,该函数根据类型与ptr指向的数据返回一个robj指针。

robj *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), or
     * alternatively the LFU counter. */
    //对于页面置换算法先不考虑,后面研究到了再看。
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }
    return o;
}

设置一种特殊的refcount。使得该对象时shred。增加引用于减少引用,都会检查这种特殊的引用计数。下面是使得对象变为共享的方法,其实就是将refcout数值设为int_max.

robj *makeObjectShared(robj *o) {
    serverAssert(o->refcount == 1);
    o->refcount = OBJ_SHARED_REFCOUNT;
    //使得对象为共享,设置为obj_shared_refcount 其大小为int_max
    return o;
}

接下来是创建字符串对象的两种方法。

先来介绍原生字符串的创建方法。简单的调用sdsnewlen的方法,然后得到字符串指针后在调用create进行创建操作。

/* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain
 * string object where o->ptr points to a proper sds string. */
robj *createRawStringObject(const char *ptr, size_t len) {
	//创建RawStringObject类型
	//其实是一个sds类型,而在默认情况下,encoding为OBJ_ENCODING_RAW
	/*
		#define OBJ_STRING 0    
		#define OBJ_LIST 1     
		#define OBJ_SET 2       
		#define OBJ_ZSET 3      
		#define OBJ_HASH 4      
	*/
    return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}

另外一种创建的方法这是一种字符串直接是添加在对象后面,该字符串直接存储在对象的后面。规定整个对象不大于64个字节。

/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
 * an object where the sds string is actually an unmodifiable string
 * allocated in the same chunk as the object itself. */
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
	//先一步步看代码
    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
    //分配了robj数据与sdshdr8的sds头空间加上了len+1的长度,多出来的1长度存储null
    struct sdshdr8 *sh = (void*)(o+1);
    //o+1根据robj的结构体的字节进行增长
    //robj总共16字节,那么加上16刚刚好是sdshdr8的头部
    o->type = OBJ_STRING;
    o->encoding = OBJ_ENCODING_EMBSTR;
    o->ptr = sh+1;
    //sh+1即加上sdshdr8的字节数,而sdshdr8为3字节即len+alloc+flags
    //那么加1后,ptr指向的为sh->buf
    /*
    在研究字符所占的字节数中发现一个有趣的现象;
    	struct __attribute__ ((__packed__)) sdshdr8 {
    			uint8_t len; 
    			uint8_t alloc; 
   				unsigned char flags; 
    			char buf[];
			};
	由于char buf[]	会导致计算sdshdr8的大小不算上buf[]很奇怪
	而使用指针的话,则会加上8字节用于存储
	而[]中有值时也会增加其长度,若不写值时则会不计算。而buf的地址为flags最后一位。

    */
    o->refcount = 1;
    //初始化引用计数
    if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
        o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;
    } else {
        o->lru = LRU_CLOCK();
    }
    //对于页面算法先不研究。
    //sds的初始化
    sh->len = len;
    sh->alloc = len;
    sh->flags = SDS_TYPE_8;
    if (ptr == SDS_NOINIT)
    	//sds_noint 为一个静态const char *
    	//判断是否要初始化

        sh->buf[len] = '\0';
    else if (ptr) {
    	//初始化的话则看ptr的值是否为NULL
        memcpy(sh->buf,ptr,len);
        sh->buf[len] = '\0';
    } else {
        memset(sh->buf,0,len+1);
    }
    return o;
}

这两种字符串的编码,通过一个总的字符串接口进行调用。这里面如上面所说的,EmbeddedString总共包括下面几个部分

robj 16字节 sdshdr8头部3个字节 加上一个 '\0' 一个字节,最后存储内容是44个字节

3+16+1+44=64。刚好是64节装完所有的数据。

/* Create a string object with EMBSTR encoding if it is smaller than
 * OBJ_ENCODING_EMBSTR_SIZE_LIMIT, otherwise the RAW encoding is
 * used.
 *
 * The current limit of 44 is chosen so that the biggest string object
 * we allocate as EMBSTR will still fit into the 64 byte arena of jemalloc. */
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
	//保证使用embeddedstringboject的大小为64否则调用creteRawStringObject
	//设置其长度最长为44+16+3+1=64
	//事实上sdshdr8可存储256个字符
    if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
        return createEmbeddedStringObject(ptr,len);
    else
        return createRawStringObject(ptr,len);
}

看完了字符串的封装。观察一下整型的封装方法。总的来说当能使用8个字节32位存储的时候,robj中的ptr直接就是该整数。相当于汇编中的立即数。若超出该范围,那么就是用字符串的形式存储该数据。这时ptr就当指针使用了。

robj *createStringObjectFromLongLong(long long value) {
    robj *o;
    if (value >= 0 && value < OBJ_SHARED_INTEGERS) {
        incrRefCount(shared.integers[value]);
        //大概了解了shared 它是一个机构体,包含了大量共享的的数据。
        //struct sharedObjectsStruct
        o = shared.integers[value];
    } else {
        if (value >= LONG_MIN && value <= LONG_MAX) {
            o = createObject(OBJ_STRING, NULL);
            o->encoding = OBJ_ENCODING_INT;
            o->ptr = (void*)((long)value);
            //若能使用8字节存储,直接将其存储到ptr中相当于汇编的立即数。
        } else {
        	//存不了则使用sds的模式存储
            o = createObject(OBJ_STRING,sdsfromlonglong(value));
        }
    }
    return o;
}

存储浮点数的方式,都是转化为字符串的形式。

/* Create a string object from a long double. If humanfriendly is non-zero
 * it does not use exponential format and trims trailing zeroes at the end,
 * however this results in loss of precision. Otherwise exp format is used
 * and the output of snprintf() is not modified.
 *
 * The 'humanfriendly' option is used for INCRBYFLOAT and HINCRBYFLOAT. */
robj *createStringObjectFromLongDouble(long double value, int humanfriendly) {
    char buf[MAX_LONG_DOUBLE_CHARS];
    //humanfriendly用于处理浮点数,可以先不理会
    int len = ld2string(buf,sizeof(buf),value,humanfriendly);
    //将long double转换成sds,然后进行存储
    return createStringObject(buf,len);
}

对象间字符串的复制,仅限于字符串存储的数据(这里包括了使用字符串存储的整型),或者使用立即数存储的整型。

/* Duplicate a string object, with the guarantee that the returned object
 * has the same encoding as the original one.
 *
 * This function also guarantees that duplicating a small integere object
 * (or a string object that contains a representation of a small integer)
 * will always result in a fresh object that is unshared (refcount == 1).
 *
 * The resulting object always has refcount set to 1. */
robj *dupStringObject(const robj *o) {
    robj *d;

    serverAssert(o->type == OBJ_STRING);

    switch(o->encoding) {
    case OBJ_ENCODING_RAW:
        return createRawStringObject(o->ptr,sdslen(o->ptr));
    case OBJ_ENCODING_EMBSTR:
        return createEmbeddedStringObject(o->ptr,sdslen(o->ptr));
    case OBJ_ENCODING_INT:
        d = createObject(OBJ_STRING, NULL);
        d->encoding = OBJ_ENCODING_INT;
        d->ptr = o->ptr;
        return d;
    default:
        serverPanic("Wrong encoding.");
        break;
    }
}

接下来就是一些别的类型的创建,大多数都是使用该类型的create函数进行创建,后又释放空间的函数,都是比较简单。还有引用计数减少的函数,都比较直接简单。直接看看就好了。

robj *createQuicklistObject(void) {
    quicklist *l = quicklistCreate();
    robj *o = createObject(OBJ_LIST,l);
    o->encoding = OBJ_ENCODING_QUICKLIST;
    return o;
}

robj *createZiplistObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_LIST,zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}

robj *createSetObject(void) {
    dict *d = dictCreate(&setDictType,NULL);
    robj *o = createObject(OBJ_SET,d);
    o->encoding = OBJ_ENCODING_HT;
    return o;
}

robj *createIntsetObject(void) {
    intset *is = intsetNew();
    robj *o = createObject(OBJ_SET,is);
    o->encoding = OBJ_ENCODING_INTSET;
    return o;
}

robj *createHashObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_HASH, zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}

robj *createZsetObject(void) {
    zset *zs = zmalloc(sizeof(*zs));
    robj *o;

    zs->dict = dictCreate(&zsetDictType,NULL);
    zs->zsl = zslCreate();
    o = createObject(OBJ_ZSET,zs);
    o->encoding = OBJ_ENCODING_SKIPLIST;
    return o;
}

robj *createZsetZiplistObject(void) {
    unsigned char *zl = ziplistNew();
    robj *o = createObject(OBJ_ZSET,zl);
    o->encoding = OBJ_ENCODING_ZIPLIST;
    return o;
}

robj *createStreamObject(void) {
    stream *s = streamNew();
    robj *o = createObject(OBJ_STREAM,s);
    o->encoding = OBJ_ENCODING_STREAM;
    return o;
}

robj *createModuleObject(moduleType *mt, void *value) {
    moduleValue *mv = zmalloc(sizeof(*mv));
    mv->type = mt;
    mv->value = value;
    return createObject(OBJ_MODULE,mv);
}

void freeStringObject(robj *o) {
    if (o->encoding == OBJ_ENCODING_RAW) {
        sdsfree(o->ptr);
    }
}

void freeListObject(robj *o) {
    if (o->encoding == OBJ_ENCODING_QUICKLIST) {
        quicklistRelease(o->ptr);
    } else {
        serverPanic("Unknown list encoding type");
    }
}

void freeSetObject(robj *o) {
    switch (o->encoding) {
    case OBJ_ENCODING_HT:
        dictRelease((dict*) o->ptr);
        break;
    case OBJ_ENCODING_INTSET:
        zfree(o->ptr);
        break;
    default:
        serverPanic("Unknown set encoding type");
    }
}

void freeZsetObject(robj *o) {
    zset *zs;
    switch (o->encoding) {
    case OBJ_ENCODING_SKIPLIST:
        zs = o->ptr;
        dictRelease(zs->dict);
        zslFree(zs->zsl);
        zfree(zs);
        break;
    case OBJ_ENCODING_ZIPLIST:
        zfree(o->ptr);
        break;
    default:
        serverPanic("Unknown sorted set encoding");
    }
}

void freeHashObject(robj *o) {
    switch (o->encoding) {
    case OBJ_ENCODING_HT:
        dictRelease((dict*) o->ptr);
        break;
    case OBJ_ENCODING_ZIPLIST:
        zfree(o->ptr);
        break;
    default:
        serverPanic("Unknown hash encoding type");
        break;
    }
}

void freeModuleObject(robj *o) {
    moduleValue *mv = o->ptr;
    mv->type->free(mv->value);
    zfree(mv);
}

void freeStreamObject(robj *o) {
    freeStream(o->ptr);
}

void incrRefCount(robj *o) {
    if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount++;
}

void decrRefCount(robj *o) {
    if (o->refcount == 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;
        case OBJ_MODULE: freeModuleObject(o); break;
        case OBJ_STREAM: freeStreamObject(o); break;
        default: serverPanic("Unknown object type"); break;
        }
        zfree(o);
    } else {
        if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
        if (o->refcount != OBJ_SHARED_REFCOUNT) o->refcount--;
    }
}

/* This variant of decrRefCount() gets its argument as void, and is useful
 * as free method in data structures that expect a 'void free_object(void*)'
 * prototype for the free method. */
void decrRefCountVoid(void *o) {
    decrRefCount(o);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值