Redis 源码分析-内部数据结构 robj

server/2025/3/5 2:06:52/

Redis 源码分析-内部数据结构 robj

Redis 中,一个 database 内的这个映射关系是用一个 dict 来维护的(ht[0])。dict 的 key 固定用一种数据结构来表达就够了,即动态字符串 sds。而 value 则比较复杂,为了在同一个 dict 内能够存储不同类型的 value,这就需要一个通用的数据结构,这个通用的数据结构就是robj(全名 redisObject )。

#define LRU_BITS 24// redis 键值对中的 value 结构体,头占 16 字节,ptr 指针指向真正的数据
typedef struct redisObject {unsigned type: 4;           // 4 bit    数据类型 string、list、setunsigned encoding: 4;       // 4 bit    编码方式 embStrraw、ziplistunsigned lru: LRU_BITS;     // 24 bit   LRU 时间/* LRU time (relative to global lru_clock) or* LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */int refcount;               // 4 字节     引用次数,主要针对共享对象void *ptr;                  // 8 字节     指向具体实现的指针
} robj;

首先解释下这5个字段的含义:

  • type 数据类型,string、list、hash…
/* 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. */#define OBJ_MODULE 5    /* Module object. */
#define OBJ_STREAM 6    /* Stream object. */
  • encoding 编码方式,比如 string 就有 embstr 和 raw 编码方式
#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 /* No longer used: old list encoding. */
#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 */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
  • lru ‌LRU(Least Recently Used)时间,在 redis 淘汰数据时会用
  • refcount 引用计数。它允许 robj 对象在某些情况下被共享,例如 redis 在启动时对于常见的响应指令(ok、error),错误信息,0-9999 的数字对象都进行了创建并复用
  • ptr 指针 指向真正的值

接着分析下这个结构体的定义方式

unsigned type: 4

这是 C 语言里的位域定义方法,表示该字段所占的比特数(bit)

最后我们分析可以知道,一个这样的 redisObject 需要占用 4bit + 4bit + 24bit + 4B +8B 即 16 字节空间,注意这个 16 字节

这对我们 redis 的调优有没有什么启发呢?

  • 如果我要存一个很短的字符串, 字符串可能不到16字节,但为了描述这个字符串的头结构就占了16字节,内存利用率是不是低了?
  • 如果我要存一个数字,8字节就能搞定,还有必要让 ptr 指针去指向一个真实存储这个数字的空间吗?

另外,目前的机器基本都是 64 位架构,通常处理器的缓存行大小是 64 字节(64B),这意味着每次从内存加载数据到缓存时,最小的单位是 64 字节。即使你只需要其中的一部分数据,剩余的部分也会被一并加载到缓存中。redis 中的创建的很多字符串,都会采用 SDS8 的结构(SDS5 无法记录有效容量,对后续拓展不友好),如果 SDS8 点字符串 + redisObject 的头大小,恰好能满足 64B,意味着就不用再去内存中取数据了。

这就是 Redis String 类型 embStr 编码方式:

// <= 44 字节就使用 embStr 的 encoding 类型,否则使用 raw 编码类型
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44// 创建字符串对象的函数
robj *createStringObject(const char *ptr, size_t len) {// 字符串少于44字节,就 embStr 类型编码,否则 raw 类型编码if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)return createEmbeddedStringObject(ptr, len);elsereturn createRawStringObject(ptr, len);
}
struct __attribute__ ((__packed__)) sdshdr8 {uint8_t len;            // 现有长度 1 字节uint8_t alloc;          // 字符数组的空间长度(不包括结构体头和字符数组最后'\0'的一字节) 1 字节unsigned char flags;    // 标识位 3 bit 用来标识类型,5 bit 暂时无用 1 字节char buf[];             // 真正存放字符串的数组,以 '\0' 结尾,注意其没有指明长度,这是一种特殊写法,柔性数组,初始化分配时不占用内存空间
};

我们来分析一下为什么是 44 字节

redisObject 16 字节

sdshdr8 3 字节

数组最后的’\0’标识 1 字节

64 - 16 - 3 - 1 = 44 字节

指的一提的是,即使刚开始我们创建的是一个 embStr 编码的字符串,只要对其进行 append 操作,编码方式就会变为 raw,原因是变动会执行下面这个函数:

void appendCommand(client *c) {size_t totlen;robj *o, *append;o = lookupKeyWrite(c->db, c->argv[1]);if (o == NULL) {/* Create the key */c->argv[2] = tryObjectEncoding(c->argv[2]);dbAdd(c->db, c->argv[1], c->argv[2]);incrRefCount(c->argv[2]);totlen = stringObjectLen(c->argv[2]);} else {/* Key exists, check type */if (checkType(c, o, OBJ_STRING))return;/* "append" is an argument, so always an sds */append = c->argv[2];totlen = stringObjectLen(o) + sdslen(append->ptr);if (checkStringLength(c, totlen) != C_OK)return;/* Append the value */// 这里在追加值的时候 会把新生成的 str 转为 raw 编码o = dbUnshareStringValue(c->db, c->argv[1], o);o->ptr = sdscatlen(o->ptr, append->ptr, sdslen(append->ptr));totlen = sdslen(o->ptr);}signalModifiedKey(c->db, c->argv[1]);notifyKeyspaceEvent(NOTIFY_STRING, "append", c->argv[1], c->db->id);server.dirty++;addReplyLongLong(c, totlen);
}robj *dbUnshareStringValue(redisDb *db, robj *key, robj *o) {serverAssert(o->type == OBJ_STRING);// 引用计数不为1 或 之前不是 raw 编码if (o->refcount != 1 || o->encoding != OBJ_ENCODING_RAW) {robj *decoded = getDecodedObject(o);// 创建 raw 编码的对象o = createRawStringObject(decoded->ptr, sdslen(decoded->ptr));decrRefCount(decoded);dbOverwrite(db, key, o);}return o;
}

可以通过以下命令来验证

127.0.0.1:6379> set test aaa
OK
127.0.0.1:6379> object encoding test
"embstr"
127.0.0.1:6379> append test b
(integer) 4
127.0.0.1:6379> object encoding test
"raw"

http://www.ppmy.cn/server/172486.html

相关文章

【朝夕教育】《鸿蒙原生应用开发从零基础到多实战》004-TypeScript 中的泛型

标题详情作者简介愚公搬代码头衔华为云特约编辑&#xff0c;华为云云享专家&#xff0c;华为开发者专家&#xff0c;华为产品云测专家&#xff0c;CSDN博客专家&#xff0c;CSDN商业化专家&#xff0c;阿里云专家博主&#xff0c;阿里云签约作者&#xff0c;腾讯云优秀博主&…

【MySQL 的数据目录】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、MySQL 的数据目录 1、数据库文件的存放路径2、相关命令目录3、配置文件目录 二、数据库和文件系统的关系 1、查看默认数据库2、数据库在文件系统中的表示3、表…

DeepSeek + 数据分析:让数据洞察更智能、更高效

本文我们来聊聊DeepSeek如何用于数据分析工作者。 想要更好的将AI用于数据分析中&#xff0c;我们就要先弄清楚数据分析的工作可以分为几个部分&#xff0c;下面列举一下&#xff1a; 数据预处理&#xff1a; 数据收集&#xff1a;确保数据的质量和完整性&#xff0c;从可靠的来…

React 中 useState 的 基础使用

概念&#xff1a;useState 是一个React Hook&#xff08;函数&#xff09;&#xff0c;它允许我们向组件添加状态变量&#xff0c;从而影响组件的渲染结果。 本质&#xff1a;和普通JS变量不同的是&#xff0c;状态变量一旦发生变化&#xff0c;组件的视图UI也会跟着变化&…

PyTorch 损失函数解惑:为什么 nn.CrossEntropyLoss 和 nn.BCELoss 的公式看起来一样?

PyTorch 损失函数解惑&#xff1a;为什么 nn.CrossEntropyLoss 和 nn.BCELoss 的公式看起来一样&#xff1f; 在使用 PyTorch 时&#xff0c;我们经常会用到 nn.CrossEntropyLoss&#xff08;交叉熵损失&#xff09;和 nn.BCELoss / nn.BCEWithLogitsLoss&#xff08;二元交叉…

做表格用什么软件?VeryReport让数据管理更高效!

在日常办公和企业管理中&#xff0c;表格软件是必不可少的工具&#xff0c;无论是财务报表、销售数据、库存管理&#xff0c;还是市场分析&#xff0c;都离不开高效的数据处理和可视化展示。那么&#xff0c;做表格用什么软件最好&#xff1f;市面上有Excel、WPS、Google Sheet…

QT-信号与槽

1.在注册登录的练习里面&#xff0c;追加一个QListWidget项目列表 要求:点击注册之后&#xff0c;将账号显示到列表窗口小部件上面去 以及&#xff0c;在列表窗口小部件中双击某个账号的时候&#xff0c;将该账号删除 头文件 #ifndef WIDGET_H #define WIDGET_H #include <…

Redis---LRU原理与算法实现

文章目录 LRU概念理解LRU原理基于HashMap和双向链表实现LRURedis中的LRU的实现LRU时钟淘汰策略近似LRU的实现LRU算法的优化 Redis LRU的核心代码逻辑Redis LRU的核心代码逻辑Redis LRU的配置参数Redis LRU的优缺点Redis LRU的优缺点 LRU概念理解 LRU&#xff08;Least Recentl…