Go最新redis源码阅读—对象object_redis comparestringobjects(2),2024年Golang春招面试经历

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Go语言开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

访问时间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--;
    }
}

同样,关于引用计数,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 交互指令

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

pXTlTxLz-1715518175912)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值