一、前言: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 时)。

💡 设计哲学“小而美”原则。对于小对象,使用内存紧凑但操作稍慢的编码;对于大对象,切换到操作高效但内存开销稍大的编码。这一切对用户透明!

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
  1. 接收命令:Redis 解析命令,知道要操作一个 String/Hash。
  2. 查找/创建对象:在数据库的 Dict 中查找键。若不存在,则根据命令语义创建一个新的 RedisObject,设置好 type 和初始 encoding
  3. 执行操作:根据对象的 type 和 encoding分发到对应的处理函数。例如,HSET 命令会先检查 type 是否为 OBJ_HASH,然后根据 encoding 调用 ziplist 或 dict 的相关函数。
  4. 内存管理:在整个过程中,refcount 被精确维护。如果操作导致对象大小超过阈值(如 Hash 的 hash-max-ziplist-entries),Redis 会自动进行编码转换(Encoding Conversion),并更新 encoding 字段。
  5. 淘汰与销毁:当键过期或被显式删除时,其关联的 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"

五、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐