Redis对象及redisObject源码解析
创始人
2024-02-08 20:59:01

写在前面

以下内容是基于Redis 6.2.6 版本整理总结

一、对象

前面几篇文章,我们介绍了Redis用到的主要的数据结构,如:sds、list、dict、ziplist、skiplist、inset等。

但是,Redis并没有直接使用这些数据结构来实现key-value数据库,而是基于这些数据结构构建了一个对象系统。包括字符串对象、列表对象、哈希对象、集合对象和有序集合对象五种类型的对象。每种对象都使用了至少一种前面提到的数据结构。

通过对对象的区分,Redis可以在执行命令前判断该对象是否能够执行该条命令。为对象设置不同的数据结构实现,只要是为了提高效率。

二、对象的类型及编码

Redis使用对象来表示数据中的key和value,每当我们在Redis数据库中创建一个新的键值对时,至少会创建两个对象,一个作用域key,另一个作用于value。

举个栗子:set msg “hello world” 表示分别创建了一个字符串对象保存“msg”,另一个字符串对象保存“hello world”:
在这里插入图片描述

redisObject 结构体

Redis中的每个对象由 redisObject 结构体来描述,对象的类型、编码、内存回收、共享对象都需要redisObject的支持,redisObject 结构体定义如下:

#define LRU_BITS 24
typedef struct redisObject {unsigned type:4;   // 类型unsigned encoding:4; // 编码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). */int refcount;void *ptr;
} robj;

下面我们来看看每个字段的含义:
(1)type: 占4个比特位,表示对象的类型,有五种类型。当我们执行type命令时,便是通过type字段获取对象的类型。

/* 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. */

type命令使用示例:
在这里插入图片描述
(2)encoding: 占4个比特位,表示对象使用哪种编码,redis会根据不同的场景使用不同的编码,大大提高了redis的灵活性和效率。
字符串对象不同编码类型示例:
在这里插入图片描述

(3)lru: 占 24 个比特位,记录该对象最后一次被访问的时间。千万别以为这只能在LRU淘汰策略中才用,LFU也是复用的个字段。当使用LRU时,它保存的上次读写的24位unix时间戳(秒级);使用LFU时,24位会被分为两个部分,16位的分钟级时间戳和8位特殊计数器,这里就不展开了,详细可以注意我后续的文章。
(4)refcount: 对象的引用计数,类似于shared_ptr 智能指针的引用计数,当refcount为0时,释放该对象。
(5)ptr: 指向对象具体的底层实现的数据结构。

三、不同对象编码规则

在这里插入图片描述

四、对象在源码中的使用

4.1 字符串对象

4.1.1字符串对象创建
// code location: src/object.c  
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
// 创建 strintg 对象
robj *createStringObject(const char *ptr, size_t len) {// 如果待保存的字符串的长度小于等于44,使用 embstr 编码if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)return createEmbeddedStringObject(ptr,len);else // 否则使用 raw 编码return createRawStringObject(ptr,len);
}robj *createEmbeddedStringObject(const char *ptr, size_t len) {// 申请 robj + sdshdr + data + 1 的空间robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);struct sdshdr8 *sh = (void*)(o+1);o->type = OBJ_STRING;      // 设置类型o->encoding = OBJ_ENCODING_EMBSTR; // 设置编码o->ptr = sh+1;o->refcount = 1;if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {o->lru = (LFUGetTimeInMinutes()<<8) | LFU_INIT_VAL;} else {o->lru = LRU_CLOCK();}sh->len = len;sh->alloc = len;sh->flags = SDS_TYPE_8;if (ptr == SDS_NOINIT)sh->buf[len] = '\0';else if (ptr) {memcpy(sh->buf,ptr,len);sh->buf[len] = '\0';} else {memset(sh->buf,0,len+1);}return o;
}

从 createEmbeddedStringObject 函数可以看到,该对象是robj和sds的结合体,将sds直接放入到robj里,这也是嵌入式编码embstr的由来。

为什么要限制44字节呢?因为robj结构体占16个字节,sdshdr结构体占3个字节,最后结尾的‘\0’占一个字节,限制44个字节,就能保证64个字节里能放下所有内容(16+3+1+44 = 64)。

4.1.2 字符串对象编码

Redis将节省内存做到了极致,它的作者对字符串对象又做了特殊的编码处理,以进一步达到节省空间的目的。编码处理过程及代码注释如下:

/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {long value;sds s = o->ptr;size_t len;/* Make sure this is a string object, the only type we encode* in this function. Other types use encoded memory efficient* representations but are handled by the commands implementing* the type. */// 这里只对string对象进行编码,其他类型的编码都有对应的具体实现处理serverAssertWithInfo(NULL,o,o->type == OBJ_STRING);/* We try some specialized encoding only for objects that are* RAW or EMBSTR encoded, in other words objects that are still* in represented by an actually array of chars. */// 非sds string对象,直接返回原对象if (!sdsEncodedObject(o)) return o;/* It's not safe to encode shared objects: shared objects can be shared* everywhere in the "object space" of Redis and may end in places where* they are not handled. We handle them only as values in the keyspace. */// 如果该对象由其他对象共享,不能编码,如果编码可能影响到其他对象的使用if (o->refcount > 1) return o;/* Check if we can represent this string as a long integer.* Note that we are sure that a string larger than 20 chars is not* representable as a 32 nor 64 bit integer. */// 检查能否把一个字符串表示为长整型数,长度要小于等于20len = sdslen(s);if (len <= 20 && string2l(s,len,&value)) {/* This object is encodable as a long. Try to use a shared object.* Note that we avoid using shared integers when maxmemory is used* because every object needs to have a private LRU field for the LRU* algorithm to work well. */// 如果可以被编码为long型,且编码后的值小于OBJ_SHARED_INTEGERS(10000),且未配// 置LRU替换淘汰策略, 就使用这个数的共享对象,相当于所有小于10000的数都是用的同一个robjif ((server.maxmemory == 0 ||!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&value >= 0 &&value < OBJ_SHARED_INTEGERS){decrRefCount(o);incrRefCount(shared.integers[value]);return shared.integers[value];} else {if (o->encoding == OBJ_ENCODING_RAW) {sdsfree(o->ptr);o->encoding = OBJ_ENCODING_INT;o->ptr = (void*) value;return o;} else if (o->encoding == OBJ_ENCODING_EMBSTR) {decrRefCount(o);return createStringObjectFromLongLongForValue(value);}}}// 不能转为long的字符串/* If the string is small and is still RAW encoded,* try the EMBSTR encoding which is more efficient.* In this representation the object and the SDS string are allocated* in the same chunk of memory to save space and cache misses. */// 如果字符串的长度太小,小于等于44if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {robj *emb;// 如果当前编码是embstr,直接返回原对象,否则转为embstr编码,返回if (o->encoding == OBJ_ENCODING_EMBSTR) return o;emb = createEmbeddedStringObject(s,sdslen(s));decrRefCount(o);return emb;}/* We can't encode the object...** Do the last try, and at least optimize the SDS string inside* the string object to require little space, in case there* is more than 10% of free space at the end of the SDS string.** We do that only for relatively large strings as this branch* is only entered if the length of the string is greater than* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */// 如果前面编码没有成功,这里做最后一步,当编码类型为raw,且它的sds可用空间超过10%,// 尝试释放调这部分内存trimStringObjectIfNeeded(o);/* Return the original object. */// 返回原对象return o;
}
4.1.3 字符串对象解码

有编码就有解码,实际上只需要那些可以转为整型类型的字符串传进行解码,解码代码及注释如下:

robj *getDecodedObject(robj *o) {robj *dec;// 如果编码是 embstr 和 raw ,只是把引用计数加一,然后返回原对象if (sdsEncodedObject(o)) {incrRefCount(o);return o;}// 如果编码是 int 进行解码,返回新的对象if (o->type == OBJ_STRING && o->encoding == OBJ_ENCODING_INT) {char buf[32];ll2string(buf,32,(long)o->ptr);dec = createStringObject(buf,strlen(buf));return dec;} else {serverPanic("Unknown encoding type");}
}

4.2 redis对象引用计数及自动清理

void incrRefCount(robj *o) {if (o->refcount < OBJ_FIRST_SPECIAL_REFCOUNT) {o->refcount++; // 引用计数加一} else {if (o->refcount == OBJ_SHARED_REFCOUNT) {/* Nothing to do: this refcount is immutable. */} else if (o->refcount == OBJ_STATIC_REFCOUNT) {serverPanic("You tried to retain an object allocated in the stack");}}
}// 减少引用计数
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--; // 计数减一}
}

五、总结

  • redis对象为所有类型的value提供了统一的封装
  • 为对象的淘汰策略保存相关信息
  • 实现引用计数及内存自动释放功能

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
demo什么意思 demo版本... 618快到了,各位的小金库大概也在准备开闸放水了吧。没有小金库的,也该向老婆撒娇卖萌服个软了,一切只...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...