Redis源代码剖析--对象object
前面一系列的博客分析了Redis的基本数据结构,有动态字符串sds、双端链表sdlist、字典dict、跳跃表skiplist、整数集合intset和压缩列表ziplist等,这些数据结构对于用户来说是不可见的。
Redis在这些数据结构的基础上构建了对用户可见的五种类型,各自是string、hash、list、set和zset,为了更方便的使用这五种数据类型,Redis定义了RedisObject结构体来表示它们。
今天,我们就一起来看看RedisObject是怎样构建的。(假设底层结构不熟悉的,能够点击上述)
RedisObject数据结构
在server.h文件里,给出了RedisObject的结构体定义,我们一起来看看。
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; // LRU_BITS为24
int refcount;
void *ptr;
} robj;
当中,ptr指向对象中实际存放的值。这里不须要过多解释,针对其它四个结构体參数,作例如以下说明:
类型type
Redis的对象有五种类型,各自是string、hash、list、set和zset。type属性就是用来标识着五种数据类型。type占用4个bit位,其取值和类型相应例如以下:
#define OBJ_STRING 0
#define OBJ_LIST 1
#define OBJ_SET 2
#define OBJ_ZSET 3
#define OBJ_HASH 4
编码类型encoding
Redis对象的编码方式由encoding參数指定,也就是表示ptr指向的数据以何种数据结构作为底层实现。
该字段也占用4个bit位。其取值和相应类型相应例如以下:
#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 /* Encoded as regular linked list */
#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 */
在Redis3.2.5版本号中。zipmap已不再使用。此处也不再讨论。
上述编码类型相应的底层数据结构实现例如以下表所看到的:
编码类型 | 底层实现 |
---|---|
OBJ_ENCODING_RAW | 简单动态字符串sds |
OBJ_ENCODING_INT | long类型的整数 |
OBJ_ENCODING_HT | 字典dict |
OBJ_ENCODING_LINKEDLIST | 双端队列sdlist |
OBJ_ENCODING_ZIPLIST | 压缩列表ziplist |
OBJ_ENCODING_INTSET | 整数集合intset |
OBJ_ENCODING_SKIPLIST | 跳跃表skiplist和字典dict |
OBJ_ENCODING_EMBSTR | EMBSTR编码的简单动态字符串sds |
OBJ_ENCODING_QUICKLIST | 由双端链表和压缩列表构成的高速列表 |
Redis的每一种对象类型能够相应不同的编码方式,这就极大地提升了Redis的灵活性和效率。Redis能够依据不同的使用场景,来选择合适的编码方式,五种对象类型相应的底层编码方式例如以下表所看到的:
对象类型 | 编码方式 |
---|---|
OBJ_STRING | OBJ_ENCODING_RAW ,OBJ_ENCODING_INT ,OBJ_ENCODING_EMBSTR |
OBJ_LIST | OBJ_ENCODING_LINKEDLIST ,OBJ_ENCODING_ZIPLIST ,OBJ_ENCODING_QUICKLIST |
OBJ_SET | OBJ_ENCODING_INTSET ,OBJ_ENCODING_HT |
OBJ_ZSET | OBJ_ENCODING_ZIPLIST ,OBJ_ENCODING_SKIPLIST |
OBJ_HASH | OBJ_ENCODING_ZIPLIST ,OBJ_ENCODING_HT |
訪问时间lru
lru表示该对象最后一次被訪问的时间,其占用24个bit位。保存该值的目的是为了计算该对象的空转时长,便于兴许依据空转时长来决定是否释放该键。回收内存。
引用计数refcount
C语言不具备自己主动内存回收机制,所以Redis对每个对象设定了引用计数refcount字段。程序通过该字段的信息。在适当的时候自己主动释放内存进行内存回收。此功能与C++的智能指针类似。
- 当创建一个对象时,其引用计数初始化为1;
- 当这个对象被一个新程序使用时。其引用计数加1;
- 当这个对象不再被一个程序使用时,其引用计数减1;
- 当引用计数为0时,释放该对象。回收内存。
对象的基本操作
Redis关于对象的操作函数主要在server.h和object.c文件里。
对象创建
redis提供以下函数用于创建不同类型的对象。
robj *createObject(int type, void *ptr); // 创建对象。设定其參数
robj *createStringObject(const char *ptr, size_t len); // 创建字符串对象
robj *createRawStringObject(const char *ptr, size_t len); // 创建简单动态字符串编码的字符串对象
robj *createEmbeddedStringObject(const char *ptr, size_t len); // 创建EMBSTR编码的字符串对象
robj *createStringObjectFromLongLong(long long value); // 依据传入的longlong整型值,创建一个字符串对象
robj *createStringObjectFromLongDouble(long double value, int humanfriendly); // 依据传入的long double类型值,创建一个字符串对象
robj *createQuicklistObject(void); // 创建高速链表编码的列表对象
robj *createZiplistObject(void); // 创建压缩列表编码的列表对象
robj *createSetObject(void); // 创建集合对象
robj *createIntsetObject(void); // 创建整型集合编码的集合对象
robj *createHashObject(void); // 创建hash对象
robj *createZsetObject(void); // 创建zset对象
robj *createZsetZiplistObject(void); //创建压缩列表编码的zset对象
以创建字符串对象为例。来说明整个redisobject的创建过程。
/*********************************创建字符串对象************************************/
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
// 短字符採用特殊的EMBSTR编码
return createEmbeddedStringObject(ptr,len);
else
// 长字符採用RAW编码
return createRawStringObject(ptr,len);
}
/******************************创建RAW编码的字符串对象********************************/
// RAW编码须要调用两次内存分配函数
// 一是为redisObject分内内存。二是为sds字符串分配内存
robj *createRawStringObject(const char *ptr, size_t len) {
// sdsnewlen函数用于创建一个长度为len的sds字符串
return createObject(OBJ_STRING,sdsnewlen(ptr,len));
}
// 通用创建redis对象的函数,採用raw编码方式
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). */
o->lru = LRU_CLOCK();
return o;
}
/***************************创建EMBSTR编码的字符串对象********************************/
// EMRSTR编码仅仅须要调用一次内存分配函数
// 它的redisobject和sds是放在一段连续的内存空间上
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
// sds的起始地址sh
struct sdshdr8 *sh = (void*)(o+1);
// 设定redisObject的參数
o->type = OBJ_STRING;
o->encoding = OBJ_ENCODING_EMBSTR;
o->ptr = sh+1;
o->refcount = 1;
o->lru = LRU_CLOCK();
// 设定sds字符串的參数
sh->len = len;
sh->alloc = len;
sh->flags = SDS_TYPE_8;
if (ptr) {
memcpy(sh->buf,ptr,len);
sh->buf[len] = '\0';
} else {
memset(sh->buf,0,len+1);
}
return o;
}
对象释放
Redis不提供释放整个redis对象的函数。每个redis对象都有一个引用计数,在引用计数变为0的时候对其总体进行释放,以下五个函数分别用来释放对象中存放的数据,其释放过程中须要推断数据的编码类型。依据不同的编码类型调用不同的底层函数。
void freeStringObject(robj *o); // 释放字符串对象
void freeListObject(robj *o); // 释放链表对象
void freeSetObject(robj *o); // 释放集合对象
void freeZsetObject(robj *o); // 释放有序集合对象
void freeHashObject(robj *o); // 释放哈希对象
我们还是以字符串对象为例,来看看对象的释放过程。
// 释放字符串对象
// 不管是embstr编码还是raw编码,其内存上存放的都是sds字符串
// 所以仅仅用调用sdsfree就能够对其进行释放
void freeStringObject(robj *o) {
if (o->encoding == OBJ_ENCODING_RAW) {
sdsfree(o->ptr);
}
}
字符串对象的释放可能看不出来须要依据编码方式来选择不同的底层释放函数,以下来看看集合的释放函数。
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");
}
}
那么,什么时候释放整个Redis对象呢?答案在以下函数。
// 引用计数减1
void decrRefCount(robj *o) {
// 引用计数为小于等于0,报错
if (o->refcount <= 0) serverPanic("decrRefCount against refcount <= 0");
// 引用计数等于1,减1后为0
// 须要释放整个redis对象
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;
default: serverPanic("Unknown object type"); break;
}
// 释放redis对象
zfree(o);
} else {
// 引用计数减1
o->refcount--;
}
}
相同。关于引用计数,redis还提供了添加引用计数的函数,这里也一并说了。
// 添加对象的引用计数+1
void incrRefCount(robj *o) {
o->refcount++; // 引用计数加1
}
其它操作函数
redis在object.c文件里还提供了非常多API接口函数。以下仅仅罗列出函数名和功能。详细实现也比較简单。这里就不赘述。
// 复制一个字符串对象
robj *dupStringObject(robj *o);
// 推断一个对象能否够用longlong型整数表示
int isObjectRepresentableAsLongLong(robj *o, long long *llongval);
// 尝试对一个对象进行压缩以节省内存,假设无法压缩则添加引用计数后返回
robj *tryObjectEncoding(robj *o);
// 对一个对象进行解码,假设不能解码则添加其引用计数并返回,反则返回一个新对象
robj *getDecodedObject(robj *o);
// 获取字符串对象的长度
size_t stringObjectLen(robj *o);
// getLongLongFromObject函数的封装,假设错误发生能够发回指定响应消息
int getLongFromObjectOrReply(client *c, robj *o, long *target, const char *msg);
// 检查o的类型是否与type一致
int checkType(client *c, robj *o, int type);
// getLongLongFromObject的封装。假设错误发生则能够发出指定的错误消息
int getLongLongFromObjectOrReply(client *c, robj *o, long long *target, const char *msg);
// 从字符串对象中解码出一个double类型的整数
int getDoubleFromObjectOrReply(client *c, robj *o, double *target, const char *msg);
// 从字符串对象中解码出一个long long类型的整数
int getLongLongFromObject(robj *o, long long *target);
// 从字符串对象中解码出一个long double类型的整数
int getLongDoubleFromObject(robj *o, long double *target);
// getLongDoubleFromObject的封装,假设错误发生则能够发出指定的错误消息
int getLongDoubleFromObjectOrReply(client *c, robj *o, long double *target, const char *msg);
// 返回编码的字符串表示,如OBJ_ENCODING_RAW编码就返回raw
char *strEncoding(int encoding);
// 以二进制方式比較两个字符串对象
int compareStringObjects(robj *a, robj *b);
// 以本地指定的文字排列次序coll方式比較两个字符串
int collateStringObjects(robj *a, robj *b);
// 比較两个字符串对象是否相同
int equalStringObjects(robj *a, robj *b);
// 计算给定对象的闲置时长,使用近似LRU算法
unsigned long long estimateObjectIdleTime(robj *o);
Object交互指令
Redis提供了三个命令用于获取对象的一些參数。其命令形式例如以下:
object refcount <key>
返回key所指的对象的引用计数object encoding <key>
返回key所指的对象中存放的数据的编码方式object idletime <key>
返回key所指的对象的空转时长
这些交互指令的实现由例如以下函数完毕。
void objectCommand(client *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);
} else if (!strcasecmp(c->argv[1]->ptr,"encoding") && c->argc == 3) {
// 返回key所指的对象中存放的数据的编码方式
if ((o = objectCommandLookupOrReply(c,c->argv[2],shared.nullbulk))
== NULL) return;
addReplyBulkCString(c,strEncoding(o->encoding));
} else if (!strcasecmp(c->argv[1]->ptr,"idletime") && c->argc == 3) {
// 返回key所指的对象的空转时长
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)");
}
}
redisObject小结
Redis为用户提供了五种数据结构,各自是string,hash,list,set和zset,每种数据结构的内部都至少有两种编码方式。不同的编码方式适用于不同的使用场景。Redis的对象带有引用计数功能,当一个对象不再被使用时(即引用计数为0)。对象所占的内存就会被自己主动释放。同一时候。Redis还会对每个对象记录其近期被使用的时间,从而计算对象的空转时长。便于程序在适当的时候释放内存。
Redis源代码剖析--对象object的更多相关文章
- 【Redis源代码剖析】 - Redis内置数据结构之压缩字典zipmap
原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51111230 今天为大家带来Redis中zipmap数据结构的分析,该结构定义在 ...
- Redis源代码剖析和凝视(八)--- 对象系统(redisObject)
Redis 对象系统 1. 介绍 redis中基于双端链表.简单动态字符串(sds).字典.跳跃表.整数集合.压缩列表.高速列表等等数据结构实现了一个对象系统,而且实现了5种不同的对象,每种对象都使用 ...
- 【Redis源代码剖析】 - Redis内置数据结构之字典dict
原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51018337 今天我们来讲讲Redis中的哈希表. 哈希表在C++中相应的是ma ...
- 豌豆夹Redis解决方式Codis源代码剖析:Proxy代理
豌豆夹Redis解决方式Codis源代码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描写叙述: Codis is a proxy b ...
- Redis源代码分析(一)--Redis结构解析
从今天起,本人将会展开对Redis源代码的学习,Redis的代码规模比較小,很适合学习,是一份很不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望终于能把他啃完吧,C语言好久不 ...
- 【Java集合源代码剖析】LinkedHashmap源代码剖析
转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985 前言:有网友建议分析下LinkedHashMap的源代码.于是花了一晚上时间 ...
- Python源代码剖析笔记3-Python运行原理初探
Python源代码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源代码剖析笔记,然而慢慢觉得没有从一个宏观 ...
- Java集合源代码剖析(二)【HashMap、Hashtable】
HashMap源代码剖析 ; // 最大容量(必须是2的幂且小于2的30次方.传入容量过大将被这个值替换) static final int MAXIMUM_CAPACITY = 1 << ...
- STL源代码剖析——STL算法stl_algo.h
前言 在前面的博文中剖析了STL的数值算法.基本算法和set集合算法.本文剖析STL其它的算法,比如排序算法.合并算法.查找算法等等.在剖析的时候.会针对函数给出一些样例说明函数的使用.源代码出自SG ...
随机推荐
- [Winform]无边框窗口悬浮右下角并可以拖拽移动
摘要 简单实现了一个这样的功能,程序启动时,窗口悬固定在右下角,并可以通过鼠标拖拽移动. 核心代码块 无边框窗口并不出现在任务栏 //无边框 this.FormBorderStyle = System ...
- 从.snk文件导出密钥
先声明该文的实用性不强, 要产生一对密钥可以有更简单的方法.该文简单解释了.snk文件的格式,并给出了从中提取密钥的C#代码. .snk文件(Strong Name Key)也可以叫签名文件,它一般用 ...
- sql语句中having的作用是?
HAVING对由sum或其它集合函数运算结果的输出进行限制.比如,我们可能只希望看到Store_Information数据表中销售总额超过1500美圆的商店的信息,这时我们就需要使用HAVING从句. ...
- Struts2标签的<s:set>标签与JSTL的<c:set>标签
<s:set>标签 set标签 用于将某个值放入指定范围内.例如application.session范围等. 当某个值所在的对象图深度非常深时,例如如下:person.worker.wi ...
- WordPress主题开发:WP_Query使用分页实例
functions.php加入 <?php function lingfeng_custom_pagenavi( $custom_query,$range = 4 ) { global $pag ...
- 朽木第一至三季/全集Deadwood迅雷下载
英文译名Deadwood,第1-3季(2004-2006)HBO. 本季看点:<朽木>又名<死木>由<纽约重案组>(NYPD Blue)制作人大卫·米奇担纲,讲述美 ...
- appstore防代充的一些想法
点击这里可以查看代充相关的报道, 利用苹果商店规则漏洞,出现了一个灰色地下产业链>> 用户点击选择要充值的物品时,先向后台服务器发起一个创建订单号的请求,然后再向appstore发起购买商 ...
- 抄袭证据之中的一个CMM与CMMI的名称
以下文字来自我即将完毕的文章,谢博士说她没有抄袭,可是文中实在是有太多的漏洞了. 6.2.7 P120页中: "实际上终于所谓的统一方法论就是标准,尽管作标准并非目的.但标准是必须有的.能够 ...
- HttpURLConnection和HttpClient的简单用法
HttpURLConnection的简单用法:先通过一个URL创建一个conn对象,然后就是可以设置get或者是post方法,接着用流来读取响应结果即可 String html = null; lon ...
- 内存数据库-H2简介与实践
一.H2数据库介绍 H2数据库地址:http://www.h2database.com/html/main.html H2是一个开源的嵌入式(非嵌入式设备)数据库引擎,它是一个用Java开发的类库,可 ...