`
xpenxpen
  • 浏览: 725948 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

redis源码阅读笔记(7)——对象

阅读更多
本篇我们研究redis里的对象。

1. 概述
redis有5种对象类型
  • 字符串
  • 列表
  • 哈希
  • 集合
  • 有序集合

它们的底层数据结构都是由前面6篇文章提到的所实现的。

请先参考如下文章,并结合源码阅读体会。
http://www.redisbook.com/en/latest/toc.html里的8.对象
http://origin.redisbook.com/en/latest/里的第三部分:Redis 数据类型

2. 一种类型,多种实现
redis使得一种对象类型的实现可以由不同的数据结构来切换,从而同时兼顾性能(CPU)和内存。而且当某个底层数据结构有性能问题时,可以替换掉。当然这种替换的实现还是没有java这么优雅的,redis里面只能靠if else来切换不同的数据结构。
对象类型实现方式一实现方式二实现方式三
字符串REDIS_ENCODING_INTREDIS_ENCODING_EMBSTRREDIS_ENCODING_RAW
列表REDIS_ENCODING_ZIPLISTREDIS_ENCODING_LINKEDLIST
哈希REDIS_ENCODING_ZIPLISTREDIS_ENCODING_HT
集合REDIS_ENCODING_INTSETREDIS_ENCODING_HT
有序集合REDIS_ENCODING_ZIPLISTREDIS_ENCODING_SKIPLIST


3. zipmap
以前小的哈希表使用zipmap实现的,而redis从2.6版本开始,改用ziplist来实现,因为zipmap被爆有性能问题
所以现在zipmap已经没用了,如果读者感兴趣的话,代码就在zipmap.c里面。我就不关注了。

4. redisObject的定义
redis.h中定义了redisObject

typedef struct redisObject {
    // 类型
    unsigned type:4;
    // 编码
    unsigned encoding:4;
    // 对象最后一次被访问的时间
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
    // 引用计数
    int refcount;
    // 指向实际值的指针
    void *ptr;
} robj;


这里解释一下里面的一个奇怪的语法:(冒号)
该语法的定义叫位字段(bit field)。

4.1 位字段(bit field)
type:4的意思是定义了一个属性叫type,占4个比特,而前面的unsigned等价于unsigned int。
下面又定义了一个属性叫encoding,占4个比特。以及lru,占24个比特。
这样这3个属性合在一起,占32个比特,也就是4个字节。
剩下的refcount和ptr自不必说,各占4个字节。
所以整个结构体robj占了12个字节。可以看到巧妙运用了bit field以后,可以节省内存,同时相对直接操纵位来编程也更容易。

4.2 写个程序测试一下
bitfieldtest.c(可在附件下载)
int main(void)
{
	printf("sizeof(robj)=%d\n", sizeof(robj));
	robj obj1;
	void *address = &obj1;
	printf("address=%#X\n", address);
	obj1.type = 1;
	obj1.encoding = 5;
	obj1.lru = 255;
	obj1.refcount = 2;
	obj1.ptr = NULL;
	return 0;
}


在我的32位的win7下测试结果和预期一致。
sizeof(robj)=12
address=0X22ABE0


我们随后对结构体robj的属性赋了值,用eclipse调试观察内存如下图所示。

图已经很明白了,文字就不再赘述了,只解释一点,type和encoding为何顺序是倒过来的,这个和大端小端有关系。

5. 对象共享
用flyweight模式,将一些常用的整数对象缓存起来。这个思路和java里面的Integer.valueOf如出一辙。

redis.h中定义
struct sharedObjectsStruct {
    //......此处省略一部分代码......
    *integers[REDIS_SHARED_INTEGERS];
};


redis.c中初始化
#define REDIS_SHARED_INTEGERS 10000
struct sharedObjectsStruct shared;

void createSharedObjects(void) {
    int j;

    //......此处省略一部分代码......

    // 常用整数
    for (j = 0; j < REDIS_SHARED_INTEGERS; j++) {
        shared.integers[j] = createObject(REDIS_STRING,(void*)(long)j);
        shared.integers[j]->encoding = REDIS_ENCODING_INT;
    }
}


可以看到将0~9999的整数缓存了起来。而java里面的Integer则是将-128到127缓存了起来。

6. 引用计数以及对象的销毁
引用计数是垃圾回收的一种算法,但是此算法无法解决循环引用问题,所以java里面没有采用这种算法。

/*
 * 为对象的引用计数减一
 *
 * 当对象的引用计数降为 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--;
    }
}

/*
 * 释放字符串对象
 */
void freeStringObject(robj *o) {
    if (o->encoding == REDIS_ENCODING_RAW) {
        sdsfree(o->ptr);
    }
}


在这里我不禁要感叹c程序员的功力了,事无巨细都亲力亲为。而java程序员就幸福多了,语言,类库还有框架的封装使得程序员生产力大增,且不用动用脑细胞做这类琐事。

7. 具体对象类
下面稍微记录一下redis中5种对象类型所对应的源码位置,方便读者自学。

7.1 字符串对象
redis.h中定义了原型
/* Redis object implementation */
robj *createObject(int type, void *ptr);
robj *createStringObject(char *ptr, size_t len);
robj *createRawStringObject(char *ptr, size_t len);
robj *createEmbeddedStringObject(char *ptr, size_t len);
robj *createStringObjectFromLongLong(long long value);
robj *createStringObjectFromLongDouble(long double value);


实现在object.c里
会分3情况创建不同的实现。

7.2 列表对象
见t_list.c
举例:
rpush命令键入后将执行的函数是rpushCommand-->pushGenericCommand-->listTypePush
listTypePush函数里可以看到,如果是ziplist,则调用ziplistPush,如果是adlist,则调用listAddNodeHead或listAddNodeTail

具体的每个命令所调用的函数,可参考
http://www.redisbook.com/en/latest/preview/object/list.html#id3

阻塞。比较难懂,看不懂可以放到以后再看。
http://origin.redisbook.com/en/latest/datatype/list.html#id4

7.3 哈希对象
见t_hash.c
举例:
hset命令键入后将执行的函数是hsetCommand-->hashTypeSet
hashTypeSet函数里可以看到,如果是ziplist,则调用ziplistPush两次,将key和value都加入列表尾部,如果是hashtable(dict),则调用dictReplace

具体的每个命令所调用的函数,可参考
http://www.redisbook.com/en/latest/preview/object/hash.html#id3

7.4 集合对象
见t_set.c
举例:
sadd命令键入后将执行的函数是saddCommand-->setTypeAdd
setTypeAdd函数里可以看到,如果是hashtable(dict),则调用dictAdd,如果是intset,则调用intsetAdd

具体的每个命令所调用的函数,可参考
http://www.redisbook.com/en/latest/preview/object/set.html#id3

7.5 有序集合对象
见t_zset.c

为了让有序集合的查找和范围型操作都尽可能快地执行, Redis 选择了同时使用字典和跳跃表两种数据结构来实现有序集合。
typedef struct zset {
    // 字典,使得ZSCORE只需要O(1)
    dict *dict;
    // 跳跃表,使得ZRANK只需要O(1)
    zskiplist *zsl;
} zset;


举例:
zadd命令键入后将执行的函数是zaddCommand-->zaddGenericCommand
zaddGenericCommand函数里可以看到,如果是ziplist,则调用zzlInsert,进而调用ziplistInsert两次,将value和score都加入列表,如果是skiplist,则调用zslInsert插入跳跃表,然后调用dictAdd插入到字典。

具体的每个命令所调用的函数,可参考
http://www.redisbook.com/en/latest/preview/object/sorted_set.html#id3
  • 大小: 20.1 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics