redisObject结构体详解
redisObject通过统一的结构管理多种数据类型,并在底层根据数据特征动态选择最合适的编码方式,力求在性能与内存效率之间找到最佳平衡。
redisObject 结构体详解
redisObject 结构体(robj)定义如下(以 Redis 6.2.6 为例):
1 |
|
| 字段名 | 作用 | 备注 |
|---|---|---|
type |
标识对象的 数据类型 | 例如 OBJ_STRING, OBJ_LIST, OBJ_SET, OBJ_ZSET, OBJ_HASH |
encoding |
标识对象底层使用的 编码(即具体数据结构) | 例如 OBJ_ENCODING_INT, OBJ_ENCODING_EMBSTR, OBJ_ENCODING_RAW, OBJ_ENCODING_HT |
lru |
记录对象 最近一次被访问的时间 或 访问频率 | 用于内存淘汰策略(LRU 或 LFU) |
refcount |
引用计数,用于 内存回收 和 对象共享 | 为 0 时对象会被回收 |
ptr |
指向实际数据的指针 | 对于 OBJ_ENCODING_INT 编码的字符串对象,ptr 直接存储整数值(而非指针) |
类型 (type) 与编码 (encoding)
Redis 的数据类型 (type) 和其底层编码 (encoding) 是多对多的关系。这意味着同一种数据类型,在不同条件下会使用不同的底层数据结构实现。
下表概述了五种基本数据类型及其可能的编码方式:
| 数据类型 (type) | 编码 (encoding) | 底层数据结构 | 应用条件(可通过配置调整) |
|---|---|---|---|
| String | OBJ_ENCODING_INT |
long 类型整数 | 字符串值可表示为 long 类型整数 |
OBJ_ENCODING_EMBSTR |
embstr 编码的 SDS | 字符串长度 ≤ 44 字节 | |
OBJ_ENCODING_RAW |
简单动态字符串 (SDS) | 字符串长度 > 44 字节 | |
| List | OBJ_ENCODING_QUICKLIST |
快速列表 (quicklist) | Redis 3.2 之后列表的默认实现 |
| Hash | OBJ_ENCODING_ZIPLIST |
压缩列表 (ziplist) | 所有键和值的字符串长度均小于 hash-max-ziplist-value,且键值对数量小于 hash-max-ziplist-entries |
OBJ_ENCODING_HT |
字典 (dict) | 不满足 ziplist 条件时 | |
| Set | OBJ_ENCODING_INTSET |
整数集合 (intset) | 所有元素都是整数,且元素数量小于 set-max-intset-entries |
OBJ_ENCODING_HT |
字典 (dict) | 不满足 intset 条件时 | |
| ZSet | OBJ_ENCODING_ZIPLIST |
压缩列表 (ziplist) | 元素数量小于 zset-max-ziplist-entries,且每个元素的字符串长度均小于 zset-max-ziplist-value |
OBJ_ENCODING_SKIPLIST |
跳跃表和字典 (dict) | 不满足 ziplist 条件时 |
这种 “对象类型-编码” 的设计让 Redis 能根据数据规模和特性,智能选择最节省内存或最高效访问的底层结构,并在条件变化时自动转换编码。
Redis 如何利用 redisObject
redisObject 结构中的字段共同支撑了 Redis 的核心功能。
1. 类型检查与多态命令
- 类型检查:执行命令前,Redis 会检查
redisObject的type属性。例如执行LLEN时,会检查操作的对象类型是否为OBJ_LIST,否则返回类型错误。 - 多态命令:许多命令的实现会根据
encoding属性选择不同的处理函数。例如LLEN命令,无论是quicklist还是旧的ziplist编码的列表,都能正确返回长度,体现了基于编码的多态。
2. 内存回收与共享对象
内存回收 (refcount):Redis 通过
refcount实现引用计数垃圾回收。创建对象时refcount初始化为 1;被新程序使用时增一;不再使用时减一;当refcount为 0 时,回收对象占用的内存。共享对象 (refcount):为节省内存,Redis 在初始化时会创建 0 到 9999 的整数字符串对象池。当多个键需要存储相同的整数值时,会指向这个共享对象,并增加其
refcount。注意:Redis 目前主要共享整数字符串对象,因为整数的比较操作时间复杂度是 O(1)。对于普通字符串,比较操作是 O(n),且重复可能性相对较低,使用共享对象可能得不偿失。
3. 空转时长与内存淘汰
lru字段记录了对象最近一次被访问的时间(或 LFU 的访问频率信息)。通过OBJECT IDLETIME key命令可以查看对象的空转时长(不更新lru字段)。- 当 Redis 内存使用达到
maxmemory限制,且淘汰策略设置为volatile-lru或allkeys-lru时,会优先淘汰lru值最大(即最久未访问)的对象。
字符串对象创建示例
字符串对象的创建过程很好地体现了 Redis 的优化策略:
1 |
|
OBJ_ENCODING_INT:若字符串表示的是整数,且值在0到9999之间,会直接使用共享对象。否则,ptr字段会直接存储该整数值(void *强制转换)。OBJ_ENCODING_EMBSTR:对于短字符串(≤44字节),redisObject和sdshdr结构体会被分配在一块连续的内存中,减少内存分配次数,提高缓存局部性。OBJ_ENCODING_RAW:对于长字符串(>44字节),redisObject和sdshdr是两次分配的不连续内存。
总结
redisObject 是 Redis 强大性能背后的无名英雄。它通过:
- 统一管理:用单一结构体统一表示多种数据类型。
- 灵活编码:根据数据特征动态选择最优底层结构,平衡内存与性能。
- 透明转换:在数据变化时自动进行编码转换,对用户无感。
- 高效内存管理:借助引用计数实现内存回收和有限的对象共享。
- 支持淘汰策略:记录访问信息以支持 LRU/LFU 等内存淘汰策略。
理解 redisObject,能帮你更深入地洞察 Redis 的内部运作机制,知其然也更知其所以然。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 技术之路!
评论


