Redis数据结构-RedisObject
一、前言:Redis 的“万能胶水”
当你在 Redis 中执行 SET name "Alice" 或 HSET user:1001 age 25 时,你可能从未想过,这些看似简单的操作背后,是由一个精巧的通用结构在默默支撑——RedisObject(常简写为 robj)。
💡 核心价值:
RedisObject 是 Redis 实现“统一对象模型”的基石。它将五花八门的数据类型(String, List, Hash...)抽象成一个通用接口,并在此基础上实现了内存管理、类型检查、编码优化等高级特性!
本文将带你:
- 拆解 RedisObject 的 16 字节精巧结构
- 揭秘“类型(type)”与“编码(encoding)”的动态协作
- 理解引用计数和 LRU 如何共同守护内存安全
二、RedisObject 是什么?统一的对象头
在 Redis 内部,每一个值(Value),无论它是一个字符串、一个列表还是一个哈希表,都会被封装在一个 redisObject 结构中。
2.1 源码定义
让我们直接看 server.h 中的核心定义:
typedef struct redisObject {
unsigned type:4; // 对象的类型 (4 bits)
unsigned encoding:4; // 对象的编码方式 (4 bits)
unsigned lru:24; // LRU 时间戳或 LFU 计数器 (24 bits)
int refcount; // 引用计数 (4 bytes)
void *ptr; // 指向实际数据的指针 (8 bytes)
} robj;
✅ 关键点:这个结构体总共占用 16 字节(在 64 位系统上),却承载了 Redis 对象系统的所有元信息。
2.2 核心字段详解
1. type:我是谁?
- 作用:标识对象的逻辑数据类型。
- 取值(定义在
server.h):OBJ_STRING(0):字符串OBJ_LIST(1):列表OBJ_SET(2):集合OBJ_ZSET(3):有序集合OBJ_HASH(4):哈希表- ... (还有模块对象、流对象等)
📌 重要:
type决定了你能对这个对象执行哪些命令。例如,对一个type=OBJ_LIST的对象执行HGET命令会直接报错。
2. encoding:我怎么存?
- 作用:标识对象的底层物理编码方式。这是 Redis 性能优化的核心!
- 同一个
type可以有多种encoding:- String:
OBJ_ENCODING_INT:存储整数(直接存于ptr中,无需额外内存)。OBJ_ENCODING_RAW:存储普通字符串(ptr指向一个 SDS)。
- List:
OBJ_ENCODING_QUICKLIST:快速列表(Redis 3.2+ 的默认实现)。
- Hash:
OBJ_ENCODING_ZIPLIST:压缩列表(小 Hash 时使用)。OBJ_ENCODING_HT:字典(Dict)(大 Hash 时使用)。
- Set:
OBJ_ENCODING_INTSET:整数集合(元素全为整数且数量少时)。OBJ_ENCODING_HT:字典(Dict)。
- ZSet:
OBJ_ENCODING_ZIPLIST:压缩列表(小 ZSet 时)。OBJ_ENCODING_SKIPLIST:跳跃表 + 字典(大 ZSet 时)。
- String:
💡 设计哲学:“小而美”原则。对于小对象,使用内存紧凑但操作稍慢的编码;对于大对象,切换到操作高效但内存开销稍大的编码。这一切对用户透明!
3. lru:我有多久没被用了?
- 作用:用于实现 LRU(Least Recently Used)或 LFU(Least Frequently Used) 内存淘汰策略。
- 细节:
- 在 LRU 模式下,它存储的是对象最后一次被访问的时间戳(秒级精度)。
- 在 LFU 模式下(Redis 4.0+),它被拆分为两部分:高 16 位是访问时间,低 8 位是访问频率计数器。
- 意义:当内存达到上限 (
maxmemory) 时,Redis 会根据此字段来决定淘汰哪些不活跃的键。
4. refcount:还有谁在用我?
- 作用:引用计数,实现自动内存管理。
- 原理:
- 创建对象时,
refcount = 1。 - 每当有新的地方引用此对象(如
INCR命令复用整数对象),refcount++。 - 当引用被移除(如键过期、被删除),
refcount--。 - 当
refcount == 0时,Redis 会立即释放该对象及其关联的所有内存。
- 创建对象时,
- 优势:避免了复杂的垃圾回收(GC)机制,保证了 Redis 单线程模型的简单和高效。
5. ptr:我的真实内容在哪?
- 作用:指向实际数据内容的指针。
- 灵活性:
- 对于
OBJ_ENCODING_INT的字符串,ptr直接存储整数值(利用了指针的低几位,因为内存地址通常是字节对齐的)。 - 对于其他所有情况,
ptr都是指向堆上分配的实际数据结构(如 SDS, QuickList, Dict 等)的指针。
- 对于
三、RedisObject 的工作流程:从命令到执行
让我们通过一个例子,看看 RedisObject 是如何工作的:
> SET msg "hello" # 1. 创建一个 OBJ_STRING 对象
> INCR counter # 2. 创建一个 OBJ_STRING 对象,但 encoding=INT
> HSET user id 1001 name "Bob" # 3. 创建一个 OBJ_HASH 对象,初始 encoding=ZIPLIST
> HSET user ... (插入很多字段) # 4. 触发编码转换,encoding 变为 HT
- 接收命令:Redis 解析命令,知道要操作一个 String/Hash。
- 查找/创建对象:在数据库的 Dict 中查找键。若不存在,则根据命令语义创建一个新的 RedisObject,设置好
type和初始encoding。 - 执行操作:根据对象的
type和encoding,分发到对应的处理函数。例如,HSET命令会先检查type是否为OBJ_HASH,然后根据encoding调用ziplist或dict的相关函数。 - 内存管理:在整个过程中,
refcount被精确维护。如果操作导致对象大小超过阈值(如 Hash 的hash-max-ziplist-entries),Redis 会自动进行编码转换(Encoding Conversion),并更新encoding字段。 - 淘汰与销毁:当键过期或被显式删除时,其关联的 RedisObject 的
refcount减一。若为0,则触发内存回收。
四、动手实验:窥探 RedisObject 的内部
4.1 使用 OBJECT 命令
Redis 提供了强大的 OBJECT 命令来查看对象的内部信息。
# 创建一个字符串
> SET number 10086
OK
# 查看其编码(应为 int)
> OBJECT ENCODING number
"int"
# 查看其引用计数(通常为1)
> OBJECT REFCOUNT number
(integer) 1
# 查看其 LRU 信息(idle time,单位10位秒)
> OBJECT IDLETIME number
(integer) 10
# 创建一个 Hash
> HSET small_hash a 1 b 2
(integer) 2
# 查看其编码(应为 ziplist)
> OBJECT ENCODING small_hash
"ziplist"
4.2 观察编码转换
# 继续向 Hash 中添加大量字段
> for i in {1..600}; do redis-cli HSET small_hash "field_$i" "value_$i"; done
# 再次查看编码(应已转为 hashtable)
> OBJECT ENCODING small_hash
"hashtable"
五、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)