网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
编码类型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_HASH | OBJ_ENCODING_ZIPLIST ,OBJ_ENCODING_HT |
OBJ_SET | OBJ_ENCODING_INTSET ,OBJ_ENCODING_HT |
OBJ_ZSET | OBJ_ENCODING_ZIPLIST ,OBJ_ENCODING_SKIPLIST |
访问时间lru
lru
表示该对象最后一次被访问的时间,其占用24个bit位,保存该值的目的是为了计算该对象的空转时长,便于后续根据空转时长来决定是否释放该键,回收内存
引用计数refcount
C语言不具备自动内存回收机制,所以Redis对每一个对象设定了引用计数refcount字段,程序通过该字段的信息,在适当的时候自动释放内存进行内存回收。此功能与C++的智能指针相似。
- 当创建一个对象时,其引用计数初始化为1;
- 当这个对象被一个新程序使用时,其引用计数加1;
- 当这个对象不再被一个程序使用时,其引用计数减1;
- 当引用计数为0时,释放该对象,回收内存。
对象基本操作
对象创建
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--;
}
}
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
knH1D5sh-1715639038222)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!