前面一系列的博客分析了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的更多相关文章

  1. 【Redis源代码剖析】 - Redis内置数据结构之压缩字典zipmap

    原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51111230 今天为大家带来Redis中zipmap数据结构的分析,该结构定义在 ...

  2. Redis源代码剖析和凝视(八)--- 对象系统(redisObject)

    Redis 对象系统 1. 介绍 redis中基于双端链表.简单动态字符串(sds).字典.跳跃表.整数集合.压缩列表.高速列表等等数据结构实现了一个对象系统,而且实现了5种不同的对象,每种对象都使用 ...

  3. 【Redis源代码剖析】 - Redis内置数据结构之字典dict

    原创作品,转载请标明:http://blog.csdn.net/Xiejingfa/article/details/51018337 今天我们来讲讲Redis中的哈希表. 哈希表在C++中相应的是ma ...

  4. 豌豆夹Redis解决方式Codis源代码剖析:Proxy代理

    豌豆夹Redis解决方式Codis源代码剖析:Proxy代理 1.预备知识 1.1 Codis Codis就不详细说了,摘抄一下GitHub上的一些项目描写叙述: Codis is a proxy b ...

  5. Redis源代码分析(一)--Redis结构解析

    从今天起,本人将会展开对Redis源代码的学习,Redis的代码规模比較小,很适合学习,是一份很不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望终于能把他啃完吧,C语言好久不 ...

  6. 【Java集合源代码剖析】LinkedHashmap源代码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985 前言:有网友建议分析下LinkedHashMap的源代码.于是花了一晚上时间 ...

  7. Python源代码剖析笔记3-Python运行原理初探

    Python源代码剖析笔记3-Python执行原理初探 本文简书地址:http://www.jianshu.com/p/03af86845c95 之前写了几篇源代码剖析笔记,然而慢慢觉得没有从一个宏观 ...

  8. Java集合源代码剖析(二)【HashMap、Hashtable】

    HashMap源代码剖析 ; // 最大容量(必须是2的幂且小于2的30次方.传入容量过大将被这个值替换) static final int MAXIMUM_CAPACITY = 1 << ...

  9. STL源代码剖析——STL算法stl_algo.h

    前言 在前面的博文中剖析了STL的数值算法.基本算法和set集合算法.本文剖析STL其它的算法,比如排序算法.合并算法.查找算法等等.在剖析的时候.会针对函数给出一些样例说明函数的使用.源代码出自SG ...

随机推荐

  1. 5日均线MACD

    1.5日均线: 5日均线是股市术语,就是股票5天的成交价格或指数的平均值,所对应的是股价的5日均线和指数的5日均线(5MA).均线指标实际上是移动平均线指标的简称. 一般在K 线图中会有3 条或4 条 ...

  2. win7设置电脑锁屏时间

    方法/步骤 1 小编用的win7电脑,进入控制面板先~ 2 选择系统与安全选项. 3 如图所示,箭头所指,可以设置锁屏时间,不过电源选项中还有个设置开启屏幕输入密码的设置,第一个就是. 4 如图所示, ...

  3. Linux学习1-Xshell连接阿里云ECS服务器

    前言 前段时间双十一活动,头脑发热买了个阿里云的服务器,买了后也不知道怎么用一直放着在.最近想起来,于是看了下阿里云ECS的相关资料. 由于平常一直用xshell比较多,不太习惯阿里云的ESC终端连接 ...

  4. JAVA card 应用开发(二) 在项目添加APPLET

    在上篇博文中.<JAVA card 应用开发创建第一个APPLET>.介绍了一个项目从无到有. 那么.我们建立了这个项目后,仅仅有一个应用(一个可选AID),假设我希望这个项目能够有多个应 ...

  5. JavaScript中的方法、方法引用和参数

    首先,我们来看一段代码,如果觉得不甚明白的,则本文会对你有益: var player = function (e) {             return (function f(m) {      ...

  6. Guava Finalizer

    /* * Copyright (C) 2008 The Guava Authors Licensed under the Apache License, Version 2.0 (the " ...

  7. 日历控件My97DatePicker WdatePicker屏蔽 onchange的解决方法

    http://www.cnblogs.com/wan-feng/archive/2013/12/13/3473439.html 受下面文章的启发,使用DatePicker自带的年月日相关的change ...

  8. 洛谷 P1082 同余方程

    题目描述 求关于 x 的同余方程 ax ≡ 1 (mod b)的最小正整数解. 输入输出格式 输入格式: 输入只有一行,包含两个正整数 a, b,用一个空格隔开. 输出格式: 输出只有一行,包含一个正 ...

  9. STM32学习笔记之EXTI(外部中断)

    參考资料:STM32数据手冊.网络资料 =========================================切割线==================================== ...

  10. 网关局域网通信协议V2.0

    http://docs.opencloud.aqara.cn/development/gateway-LAN-communication/ https://github.com/aqara/openc ...