
struct redisObject{unsigned type:4;// [0-3 bit] 对象类型 (如 String)unsigned encoding:4;// [4-7 bit] 编码方式 (如 int/embstr/raw)unsigned lru:24;// [8-31 bit] 缓存淘汰数据intrefcount;// [32-63 bit] 引用计数 (4字节)void*ptr;// [64-127 bit] 关键指针 (8字节)};String在 Redis 的底层实现中String字符串类型并不只有一种形态。为了平衡“内存占用”与“处理性能”Redis 会根据字符串的内容和长度在int、embstr和raw三种编码方式之间自动切换。这三种编码都封装在redisObject这个“外壳”下通过encoding字段进行区分。struct sdshdr8{uint8_t len;/* 已使用长度 */uint8_t alloc;/* 总分配空间不含头和 \0 */unsignedcharflags;/* 类型标志如 sdshdr8, sdshdr16 等 */charbuf[];/* 实际字节数组 */};1.int编码直接存储整数当一个字符串对象保存的是整数值且这个整数可以用long类型8 字节有符号整数表示时Redis 就会使用int编码。物理特征它不会分配额外的 SDS 空间而是直接将整数值存储在redisObject结构体的ptr指针字段中通过强制类型转换。共享对象优化Redis 启动时会预先创建0 ∼ 9999 0 \sim 99990∼9999这 10,000 个整数对象。如果你存的值在这个范围内所有的 Key 都会指向同一个物理内存地址引用计数加 1内存开销几乎为零。适用场景计数器、ID 存储等数值场景。2.embstr编码嵌入式短字符串当字符串的长度小于等于 44 字节时Redis 使用embstr编码。这是为了极致压榨小对象的性能。物理特征redisObject和sdshdrSDS 头部及数据在内存中是连续的一整块。它是通过一次malloc申请出来的。核心逻辑只读性它是只读的任何修改操作如APPEND都会迫使它先升级为raw。高性能由于内存连续CPU 缓存命中率极高且分配/释放内存只需要一次系统调用。计算门槛44 字节的限制是为了让整个对象16BredisObject 3Bsdshdr8 1B\0 44B Data刚好适配内存分配器的64 字节内存槽位。3.raw编码常规长字符串当字符串的长度大于 44 字节或者对embstr进行了修改操作时Redis 会使用raw编码。物理特征redisObject和sdshdr分布在两块不连续的内存空间中。ptr指针指向独立的 SDS 区域。核心逻辑可扩展性适合存储长文本、二进制数据或频繁修改的字符串。分配代价创建或销毁对象需要两次malloc或free。适用场景JSON 数据、序列化后的对象、较大的文本内容。ListRedis3.2之前ZipList/LinkedList在 Redis 3.2 之前List 的实现非常简单粗暴当数据量小时使用 ZipList压缩列表通过连续内存压榨空间当数据量大或字符串长时直接转换为 LinkedList双向链表通过指针实现灵活增删但代价是每个节点都要背负两个 8 字节指针的沉重负担且内存碎片极多。Redis3.2之后QuickListRedisObject中的*ptr指向quicklist对象typedef struct quicklist{quicklistNode*head;/* 指向头节点 */quicklistNode*tail;/* 指向尾节点 */unsignedlongcount;/* 所有元素总数 */unsignedlonglen;/* 节点车厢总数 */intfill:16;/* 节点填充因子 */unsignedintcompress:16;/* 压缩深度 */}quicklist;typedef struct quicklistNode{struct quicklistNode*prev;/* 前驱指针 */struct quicklistNode*next;/* 后继指针 */unsignedchar*zl;/* 指向物理内存中的连续块 (ZipList/Listpack) */unsignedintsz;/* 连续块占用的总字节数 */unsignedintcount:16;/* 连续块包含的元素个数 */// ... 其他标志位}quicklistNode;SetRedis 的Set集合编码设计同样遵循“从小到大”的进化逻辑。它在物理实现上主要在IntSet整数集合、Listpack紧凑列表Redis 7.2和Hashtable哈希表之间切换。它的核心哲学是如果全是小整数我用数组排好序如果有字符串我用哈希表锁死。1. 物理结构intset(整数集合)当集合满足以下两个条件时Redis 优先使用intset集合内所有成员均为整数。成员数量小于配置参数set-max-intset-entries默认 512 个。内存布局与查找逻辑intset是一块绝对连续的内存空间。物理存储内部是一个有序数组支持int16_t、int32_t或int64_t编码。有序性元素在数组内按从小到大严格排序。查找算法使用二分查找Binary Search时间复杂度为O ( log N ) O(\log N)O(logN)。升级逻辑当新插入的整数超出当前位宽如int16存入int32时会触发整块内存的重新分配和数据迁移。注意为了保持效率该过程不可逆不支持降级。2. 物理结构listpack(紧凑列表)这是Redis 7.2引入的新物理层。在旧版本中集合只要出现一个字符串就会立刻膨胀为dict而listpack充当了中间的缓冲带。触发场景集合中包含字符串但成员数量和单个字符串长度未达到set-max-listpack-entries和set-max-listpack-value阈值。物理特征连续字节流存储。性能权衡虽然查找复杂度退化为O ( N ) O(N)O(N)顺序遍历但由于数据规模极小其内存利用率远高于dict且在小数据量下连续内存对 CPU 缓存的友好性抵消了O ( N ) O(N)O(N)的算法劣势。3. 物理结构dict(字典 / 逻辑名称 HashTable)当集合规模超过阈值或包含长字符串时Redis 会使用dict作为终极物理载体。物理映射与内存布局此时redisObject-ptr指向一个真实的dict结构体实例。Key (键)存储集合的成员指向一个SDS字符串对象。Value (值)物理上统一设置为NULL指针。唯一性保证直接利用dict自身的哈希碰撞处理和 Key 唯一性逻辑实现集合去重。性能特征查找复杂度为O ( 1 ) O(1)O(1)。支持渐进式 Rehash在数据量极大时仍能保持稳定的响应速度。4. 宏观物理映射RedisObject 的指向对于 Set 来说redisObject的包装方式非常直观字段IntSet 编码Hashtable 编码typeOBJ_SETOBJ_SETencodingOBJ_ENCODING_INTSETOBJ_ENCODING_HTptr指向一整块连续的intset结构一个复杂的dict字典结构ZSetRedis 的ZSet有序集合在底层编码上设计得最为复杂因为它必须同时满足O ( 1 ) O(1)O(1)成员查分和O ( log N ) O(\log N)O(logN)按分数排序/范围检索这两个核心需求。其物理实现主要分为两个阶段listpack和dict zskiplist。1. 紧凑阶段listpack(紧凑列表)当 ZSet 满足以下两个条件时Redis 使用listpack编码OBJ_ENCODING_LISTPACK成员数量小于zset-max-listpack-entries默认 128。所有成员字符串长度小于zset-max-listpack-value默认 64 字节。物理存储逻辑在listpack内部成员Member和分值Score被存储为两个相邻的 Entry布局[Member1, Score1, Member2, Score2, ...]有序性内部元素按分值Score从小到大严格排序。性能特征由于是连续内存插入和查找涉及O ( N ) O(N)O(N)的顺序遍历及内存搬迁。但在小数据量下这种结构的 CPU 缓存命中率极高且省去了复杂的指针开销。2. 进化阶段zset结构体 (跳表 字典)当数据量突破阈值后redisObject-ptr会指向一个专门的zset结构体。这是一个双重物理结构的组合typedefstructzset{dict*dict;/* 成员 - 分值的哈希表 */zskiplist*zsl;/* 按分数排序的跳跃表 */}zset;A. 物理组件一dict(字典)作用实现O ( 1 ) O(1)O(1)复杂度的ZSCORE操作。逻辑Key 是成员SDSValue 是分值double。必要性如果没有dict查找一个成员的分数需要遍历跳表复杂度为O ( log N ) O(\log N)O(logN)。B. 物理组件二zskiplist(跳跃表)作用实现高效的范围查询ZRANGE和排名计算ZRANK。逻辑节点按分数排序。每个节点包含多个层级的指针支持快速跳跃寻址。性能平均查找复杂度为O ( log N ) O(\log N)O(logN)。3. 内存优化SDS 的“引用共享”你可能会担心同一个成员既存在dict里又存在zskiplist里岂不是浪费了一倍内存物理真相dict的 Key 和zskiplistNode的ele指向的是同一个物理内存地址同一个 SDS 对象。Redis 只是在两个数据结构中各存了一个指针。这种设计通过增加少量指针开销每个节点约几十字节换取了两个维度的极致查询速度。4. 物理特性对比表物理结构逻辑编码 (Encoding)核心优势算法复杂度内存特征listpackLISTPACK极致节省内存O ( N ) O(N)O(N)(查找/插入)连续内存无碎片zset(复合)SKIPLIST全能性能查分O ( 1 ) O(1)O(1)范围O ( log N ) O(\log N)O(logN)双重索引指针较多5. 状态转换逻辑ZSet 的转换通常是单向不可逆的一旦数据量超过阈值listpack会被拆解重新装载进一个新的dict和zskiplist中。原因从复杂的双重结构回退到连续内存块涉及大规模的内存重分配和 CPU 计算收益不抵成本。HashRedis 的Hash哈希结构在底层编码的设计上逻辑与 ZSet 非常相似在数据量小时采用紧凑的连续内存在数据量大时进化为散列表。目前的物理实现主要分为listpack和dict两种。1. 紧凑编码listpack(紧凑列表)当 Hash 结构满足以下两个条件时Redis 使用listpack存储编码名称为OBJ_ENCODING_LISTPACK哈希中字段Field的数量小于hash-max-listpack-entries默认 512 个。所有字段名和值的长度都小于hash-max-listpack-value默认 64 字节。物理存储逻辑在listpack的字节流中Field 和 Value 是作为两个相邻的 Entry存储的布局[Field1, Value1, Field2, Value2, ...]查找方式完全依靠顺序遍历。由于内存是绝对连续的CPU 在读取时可以利用预取机制Prefetching在小规模数据下速度极快。内存优势没有指针开销没有内存对齐的空隙空间利用率达到极致。2. 散列编码dict(字典)一旦数据量突破阈值或者某个 Value 太长Redis 就会将物理结构转换为dict编码名称为OBJ_ENCODING_HT。物理实现逻辑此时redisObject-ptr指向一个真实的dict结构体Key (键)存储的是 Hash 的字段名Field物理上是一个SDS对象。Value (值)存储的是 Hash 的字段值Value物理上同样是一个SDS对象。冲突处理使用拉链法链地址法解决哈希冲突。性能特征查找、插入和删除的复杂度均为O ( 1 ) O(1)O(1)。