设为首页 收藏本站
查看: 538|回复: 0

[经验分享] redis源码阅读笔记(7)——对象

[复制链接]

尚未签到

发表于 2016-12-19 09:48:43 | 显示全部楼层 |阅读模式
本篇我们研究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调试观察内存如下图所示。
DSC0000.png
图已经很明白了,文字就不再赘述了,只解释一点,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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-316265-1-1.html 上篇帖子: Redis 3.0.2高可用集群(中)------ Redis-Cluster部署 下篇帖子: redis 使用需要注意的问题
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表