Redis客户端命令到服务器底层对象机制的完整流程
客户端 → RESP协议封装 → TCP传输 → 服务器事件循环 → 协议解析 → 命令表查找 → 对象机制 → 动态编码 → 数据结构操作 → 响应编码 → 网络回传
Redis客户端命令到服务器底层对象机制的完整流程可分为协议封装、命令解析、多态执行、内存操作四个阶段。以下是结合RESP协议规范、对象机制和命令处理源码的详细流程分析:
一、客户端命令发送阶段
-
命令序列化
客户端将用户输入的指令(如HSET user:1 name "Alice"
)按RESP协议编码为二进制格式。例如:*4\r\n$4\r\nHSET\r\n$6\r\nuser:1\r\n$4\r\nname\r\n$5\r\nAlice\r\n
其中
*4
表示4个参数段,$n
表示后续字符串长度。 -
网络传输
通过TCP套接字将序列化后的数据发送至Redis服务器。客户端可能使用连接池管理多个连接,支持超时重连机制。
二、服务器协议解析阶段
-
读取输入缓冲区
服务器通过I/O多路复用检测到可读事件后,将协议数据存入客户端状态client
的输入缓冲区querybuf
,最大限制1GB(超限关闭连接)。 -
参数解析
按RESP协议解码,分割出命令参数存入argv
数组。例如解析出["HSET","user:1","name","Alice"]
,参数个数存入argc
。 -
命令表查找
根据argv[0]
(命令名)在全局命令字典commandTable
中查找对应的redisCommand
结构,验证权限、参数个数等。例如HSET
对应实现函数hsetCommand
。
三、对象机制与多态执行阶段
-
键对象检索
在数据库字典中查找键user:1
对应的redisObject
:robj *keyobj = lookupKeyWrite(c->db, c->argv[1]);
若不存在则创建新对象,类型由命令决定(此处为
OBJ_HASH
)。 -
类型与编码检查
- 验证
redisObject.type
是否匹配命令要求(如哈希命令需OBJ_HASH
类型)。 - 根据当前
redisObject.encoding
选择底层数据结构操作函数(如ziplist
或hashtable
)。
- 验证
-
动态编码转换
若数据特征触发阈值(如哈希元素超过hash-max-ziplist-entries
),执行编码转换:if (hashTypeLength(o) > server.hash_max_ziplist_entries) hashTypeConvert(o, OBJ_ENCODING_HT);
此过程涉及数据迁移和旧结构内存释放。
-
执行底层操作
调用编码对应的函数操作数据结构。例如哈希表使用dictReplace
插入键值对:dictEntry *de = dictAddRaw(dict, field, &existing); dictSetVal(dict, de, value);
四、响应返回与内存管理
-
结果写入输出缓冲区
操作结果按RESP协议编码后存入客户端buf
或reply
链表。例如返回":1\r\n"
表示成功插入1个元素。 -
内存回收与共享
- 通过
refcount
引用计数自动回收无引用对象。 - 小整数等共享对象直接复用,减少内存分配。
- 通过
-
数据同步
若开启AOF或主从复制,将命令追加到AOF缓冲区或发送给从节点。
什么是Redis对象机制?为什么要有Redis对象机制?
Redis对象机制是其实现多态数据结构和高效内存管理的核心设计,通过统一的对象模型(redisObject
)封装了所有数据类型,对外提供一致的接口,同时支持多种底层编码的动态切换。
一、为什么需要对象机制?
-
统一接口与多态支持
- Redis支持5种基础数据类型(字符串、列表、哈希、集合、有序集合),每种类型可能有多种底层实现(如哈希表可用
ziplist
或hashtable
)。对象机制通过redisObject
统一封装,使得命令处理时无需关心具体实现,只需根据类型和编码调用对应函数,实现多态性。
- Redis支持5种基础数据类型(字符串、列表、哈希、集合、有序集合),每种类型可能有多种底层实现(如哈希表可用
-
灵活优化内存与性能
- 根据数据特征动态选择最优编码。例如:
- 小规模哈希使用
ziplist
节省内存,大规模时切换为hashtable
提升操作效率。 - 字符串根据长度选择
int
、embstr
或raw
编码,减少内存碎片。
- 小规模哈希使用
- 根据数据特征动态选择最优编码。例如:
-
内存管理与共享对象
- 通过引用计数(
refcount
)自动回收无引用对象,避免内存泄漏。 - 预创建常用共享对象(如0-9999的整数),减少重复分配,节省内存。
- 通过引用计数(
-
类型检查与安全
- 执行命令前检查
redisObject
的type
属性,防止对错误类型执行操作(如对字符串执行列表命令)。
- 执行命令前检查
二、对象机制的核心:redisObject结构
typedef struct redisObject {
unsigned type:4; // 数据类型(如OBJ_STRING)
unsigned encoding:4; // 编码方式(如OBJ_ENCODING_INT)
unsigned lru:24; // 最近访问时间(用于LRU淘汰)
int refcount; // 引用计数
void *ptr; // 指向实际数据结构的指针
} robj;
-
关键属性解析
type
(4位):标识数据类型,包括OBJ_STRING
、OBJ_LIST
等5种。encoding
(4位):决定底层数据结构,如ziplist
、skiplist
等11种编码方式。ptr
:指向具体数据结构(如SDS
字符串、quicklist
列表)的内存地址。refcount
:引用计数,为0时触发内存回收;>1时表示共享对象。lru
:记录对象最后一次被访问的时间,用于内存淘汰策略(如maxmemory
配置)。
-
编码方式与底层结构
数据类型 可能的编码方式 适用场景 字符串(String) int
、embstr
、raw
整数值、短字符串(≤44B)、长字符串 列表(List) quicklist
所有列表操作 哈希(Hash) ziplist
、hashtable
小哈希(字段少且值小)、大哈希 集合(Set) intset
、hashtable
整数集合、混合类型集合 有序集合(ZSet) ziplist
、skiplist+dict
小规模有序集合、大规模需范围查询
三、对象机制的工作流程
-
创建对象
- 根据数据类型和内容选择初始编码,如字符串若为整数则用
int
编码。
- 根据数据类型和内容选择初始编码,如字符串若为整数则用
-
执行命令
- 检查
type
是否匹配命令要求(如LPOP
需OBJ_LIST
类型)。 - 根据
encoding
选择底层数据结构的处理函数(如ziplist
或hashtable
的插入逻辑)。
- 检查
-
动态编码转换
- 数据变化触发编码切换。例如哈希字段数超过
hash-max-ziplist-entries
时,从ziplist
转为hashtable
。
- 数据变化触发编码切换。例如哈希字段数超过
-
内存回收与共享
- 引用计数归零时,释放
ptr
指向的数据结构内存。 - 预创建的共享对象(如小整数)被多个键共享,减少重复创建。
- 引用计数归零时,释放
四、示例:字符串对象的多编码实现
int
编码:存储整数值(如SET key 100
),直接复用ptr
指针存储数值。embstr
编码:短字符串(≤44字节)连续存储redisObject
和SDS
,减少内存分配次数。raw
编码:长字符串使用独立分配的SDS
结构,支持动态扩容。