Redis对象及redisObject源码解析

news2024/11/25 0:34:43

写在前面

以下内容是基于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. */
    // 检查能否把一个字符串表示为长整型数,长度要小于等于20
    len = 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的数都是用的同一个robj
        if ((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. */
    // 如果字符串的长度太小,小于等于44
    if (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提供了统一的封装
  • 为对象的淘汰策略保存相关信息
  • 实现引用计数及内存自动释放功能

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/28074.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用k8s创建一个支持ssh的pod,docker

在容器场景下 用ssh去登录一个docker 是不提倡的 可是有时为了方便定位问题 我制作了一个支持ssh的镜像 节约大家的时间 docker使用方法: docker run --namec7-sshd --privileged -itd -p 1022:22 lnwaycool/centos7_ssh:v1 /usr/sbin/init ssh root127.0.0.1 -p 1022 登…

猴子也能学会的jQuery第七期——jQuery动画(上)

&#x1f4da;系列文章—目录&#x1f525; 猴子也能学会的jQuery第一期——什么是jQuery 猴子也能学会的jQuery第二期——引用jQuery 猴子也能学会的jQuery第三期——使用jQuery 猴子也能学会的jQuery第四期——jQuery选择器大全 猴子也能学会的jQuery第五期——jQuery样式操作…

外部H5页面打开微信小程序最新流程

当前时间2022年11月22日&#xff0c;记录一下外部H5如何打开指定微信小程序的指定页面最新流程。 因为微信小程序后台已关闭生成 小程序 scheme 码 入口&#xff0c;所以只能通过如下方式&#xff1a; 1.服务端获取微信小程序 scheme 码 前提条件&#xff1a;目前仅针对国内…

基于中国新能源汽车税收政策下成都市场发展路线研究

目 录 摘 要 I Abstract II 第一章 绪论 1 1.1选题背景及意义 1 1.2国内外新能源汽车税收政策现状 2 1.2.1国内新能源汽车税收政策 2 1.2.2国外新能源汽车税收政策 3 1.3本课题研究主要内容 4 第二章 新能源汽车市场发展现状及存在的问题 5 2.1市场发展现状 5 2.2存在的问题 5 …

TensorFlow之文本分类算法-2

1 前言 2 收集数据 3 探索数据 4 选择模型 5 准备数据 数据被输入模型之前&#xff0c;需要将数据转换成模型能理解的格式&#xff0c;该过程被称之为数据标准化。 首先&#xff0c;已收集到的数据样本可能是以指定的顺序存储&#xff0c;而在实际分析中&#xff0c;期望…

邮件助手、监控邮件上报电脑截图、网课监控助手

起因:看到别的网友写的一个程序,监控在家上网课的状态,防止孩子玩游戏。没界面无法配置更多参数。自己写了一个。功能基本齐全了。 收件地址:接收邮件的邮箱 抄送地址:可以多个邮箱,用|分开即可。 标题、内容、版权位置可以自定义内容。 设置发送频率(默认10分钟),…

深入浅出日志体系(logback最佳实践)

最近和大家说了不少“大问题”&#xff08;中庸之道啥的&#xff09;&#xff0c;今天想和大家聊一个“小问题”——如何写日志。 在很多人的认知里面&#xff0c;日志的确是可有可无的小问题&#xff0c;因为有没有日志都不影响业务功能的运行。正因为如此&#xff0c;日志问…

内存取证系列6

文章目录文档说明挑战说明解题过程查看系统镜像查看系统进程查看使用命令查看命令输出查看历史命令记录搜索压缩包 获取虚拟地址并导出本地扫描ie历史记录扫描firefox历史记录(默认不支持)导出浏览器数据分析浏览器数据解压压缩包 得到flag2继续分析浏览器数据修复图片 获得fla…

Oracle AutoVue 21.0.x最新支持程序文件格式及版本

目录 前言 Engineering & Construction / Energy / Utilities Industrial Manufacturing / Automotive / Aerospace & Defense Electronics & High Tech Desktop / Office Graphics Others 前言 AutoVue官是一个多用途、多功能的图…

linux系统中cpu性能优化

cpu性能: uptime命令中的系统平均负载: 平均负载时指的单位时间内,系统处于可运行状态和不可中断状态的平均进程数,也就是平均活跃进程数,它和CPU使用率并没有直接关系. 可运行状态的进程: 是指正在使用Cpu或者正在等待Cpu的进程,也就是我们ps命令看到的处于Runing状态的进程.…

物联网安全年报事件回顾

执行摘要 随着物联网的不断发展&#xff0c;物联网安全也被越来越多的人所关注。我们于 2016 年发布《物联网安全 白皮书》&#xff0c;进行物联网安全的科普介绍&#xff1b;于 2017 年发布《2017 物联网安全年报》&#xff0c;关注物联网资产 在互联网上的暴露情况、物联网设…

【CNN】GoogLeNet——大胆创新网络结构

前言 2014年&#xff0c;GoogLeNet和VGG是当年ImageNet挑战赛(ILSVRC14)的双雄&#xff0c;GoogLeNet获得了图片分类大赛第一名、VGG紧随其后&#xff0c;这两类模型结构的共同特点是网络深度更深了。VGG继承了LeNet以及AlexNet的一些框架结构&#xff0c;而GoogLeNet则做了更…

uniapp集成Android原生sdk

最近公司有个项目&#xff0c;需要调用客户提供的sdk扫描rfid&#xff0c;项目又是用uni开发的&#xff0c;客户提供的sdk只有Java版本&#xff0c;我发现uni是提供集成原生sdk的&#xff0c;所以研究了一下怎么使用&#xff0c;并记录下来过程。 准备 SDK 文件 下载 首先下…

VUE搭建后台管理界面

后台管理一、前言二、依赖配置三、koa框架四、数据库五、路由六、前端界面6.1 登录界面6.2 注册界面6.3 展示界面七、阿里云部署7.1 前端项目7.2 后端node7.3 mysql安装7.4 测试八、总结一、前言 本篇文章从头到尾过一遍vue3搭建项目的过程&#xff0c;实现一个用户登录、注册…

Linux上使用telnet连接本机IP地址端口

场景 Linux开启了iptables&#xff01;&#xff01;&#xff01; 开启本机TCP80端口服务。Linux本机IP地址是192.168.204.129。本机telnet连接本机的TCP80端口。 目的 telnet 192.168.204.129 80&#xff0c;能够连接通。 输入规则 1&#xff09;需要配置一个让本机TCP80端…

日置IM3570阻抗分析仪产品介绍分享

日置IM3570阻抗分析仪 日本日置IM3570阻抗分析仪1台仪器实现不同测量条件下的高速检查,1台仪器实现LCR测量、DCR测量、扫描测量等的连续测量和高速检查,日本日置IM3570阻抗分析仪LCR模式下*快1.5ms&#xff08;1kHz&#xff09;&#xff0c;0.5ms&#xff08;100kHz&#xff0…

基于keras构建lstm模型自动生成音乐系统

目录 LSTM 机器学习生成音乐 1 数据集介绍 1 将 mid 转成 note 数组 4 将 note 数组转成 mid 文件 5 获取数据集并将其保存 6 将 note 进行编号 7 构建数据集 8 截取数据 8 进行 one-hot 编码 10 构建模型 11 训练 13 生成音乐 13 加载数据 16 加载模型 16 构建 id 与 note 的映…

我服了!SpringBoot升级后这服务我一个星期都没跑起来!(下)

14. DiscoveryEnabledServer Not Found 主要问题还是 eureka 中没有了 ribbon 相关的依赖。 Caused by: java.lang.NoClassDefFoundError: com/netflix/niws/loadbalancer/DiscoveryEnabledServerat java.lang.Class.getDeclaredMethods0(Native Method) ~[?:?]at java.lan…

论文笔记(二十二):GRiD: GPU-Accelerated Rigid Body Dynamics with Analytical Gradients

GRiD: GPU-Accelerated Rigid Body Dynamics with Analytical Gradients文章概括摘要1. 介绍2. 相关工作3. 背景A. *计算硬件&#xff1a;CPU vs. GPU*B. 刚体动力学4. GRiD库A. 设计B. 当前特征C. 代码优化方法5. 性能基准A. 方法B. 多重计算延时C. 单一计算延时扩展6. 结论和…

母线电容及其计算方法

母线电容及其计算方法 1.母线电容是什么&#xff1f; 2.母线电容有什么作用&#xff1f; 3.母线电容的参数。 4.母线电容参数计算。 1.母线电容是什么&#xff1f; 工程定义&#xff1a; &#xff08;1&#xff09;在电机控制器中&#xff0c;电池包的直流电作为输入电源&am…