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);
}