Redis底层结构全解析:SDS、跳表、listpack与编码转换实战

Redis数据类型底层结构
于 2026-10-01 04:02:42 修改
·本内容遵循CC 4.0 BY-SA版权协议

聊到 Redis,几乎每个后端开发都能脱口而出五种数据类型:String、List、Hash、Set、ZSet。但说实话,会用命令和真正理解底层结构,完全是两个层次。面试时一个“ZSet 底层为什么用跳表”就能筛掉一大片,线上排查时一个 big key 导致阻塞,不懂编码转换机制的人也常常一头雾水。这篇文章我打算把 Redis 数据类型底层结构这件事彻底讲透:从五种类型各自适合什么场景,到 SDS、quicklist、intset、skiplist 这些底层实现为什么这么设计,再到编码转换的参数触发机制和线上踩过的坑,一次性说清楚。不管你是准备面试、做架构选型,还是日常排查问题,这篇都值得收藏慢慢看。

1. 五种数据类型全景:先搞清楚它们各自擅长什么

很多新手学 Redis 的第一个误区,就是把数据类型当成简单的“存储容器”,存进去能取出来就算会用。其实 Redis 的每一种类型都针对特定场景做了大量优化,选错类型,性能和内存可能差一个数量级。所以在拆底层之前,我们先把五种类型的能力边界和适用场景理清楚。

1.1 五种类型的核心能力速查

我用一张表把这五种类型的关键信息汇总一下,方便你对照理解。

类型 底层核心结构(小数据量/大数据量) 典型操作 最擅长的事 一句话类比
String int / embstr / raw(SDS) SET、GET、INCR、SETNX 缓存、计数器、分布式锁 一把带锁的钥匙串
List listpack / quicklist LPUSH、RPUSH、LPOP、BRPOP 消息队列、时间线、最新列表 一条双向通行的队伍
Hash listpack / hashtable HSET、HGET、HINCRBY 存储对象字段、购物车、配置项 一个可局部更新的档案袋
Set intset / listpack / hashtable SADD、SINTER、SUNION、SPOP 去重、标签系统、抽奖、好友关系 一个数学意义上的集合
ZSet listpack / skiplist + dict ZADD、ZRANGEBYSCORE、ZRANK 排行榜、延迟队列、限流滑动窗口 带分值排序的榜单

注意表中“小数据量/大数据量”的分法很关键。Redis 对数据量较小的对象会采用更紧凑的内存编码(listpack、intset),数据量大了之后才切换成更灵活的 hashtable、skiplist。这个机制后面第三章会重点讲,它是理解 Redis 内存优化的一把钥匙。

1.2 选型逻辑:什么场景选什么类型,别糊里糊涂

我见过不少同学把所有数据全塞进 String,用拼接 key 的方式模拟各种结构,最后 key 数量爆炸、内存飙升、维护困难。选错类型的代价往往不是立刻暴露的,而是集群扩容时才显现。

  • String 适合标量数据和计数器:缓存用户 token、接口返回的 JSON 字符串、库存数字、点赞数。INCR/DECR 是原子操作,做计数器、限流器非常自然。
  • List 适合先进先出或后进先出的数据流:LPUSH + BRPOP 组合就是经典的生产者消费者模型,比在数据库里维护一张队列表高效得多。注意不要把 List 当数组乱用,LINDEX 在大列表上是 O(N)。
  • Hash 适合“对象整体缓存”:比如用户信息有几十个字段,如果用 String 就得序列化整个对象,改一个字段也要全量反序列化再写回;用 Hash 可以单独操作某个字段,内存布局也更紧凑。
  • Set 适合集合运算和去重:SADD 天然去重;SINTER 可以做“共同关注”;SPOP 可以做随机抽奖,不用自己写随机算法。
  • ZSet 适合一切带权重的排序场景:分数就是权重。排行榜、延迟任务(用时间戳当 score)、滑动窗口限流都能用 ZSet 优雅实现。

记住一个朴素的原则:先想清楚你的数据是“一个值”、“一组值”还是“一组带分数的值”,再选类型。不要为了省事全程 String 一把梭,后面数据规模上来再改数据结构,迁移成本远高于一开始就选对。

2. 底层结构逐个拆解:SDS、quicklist、跳表到底解决了什么问题

这部分是全篇的核心。Redis 之所以快,除了内存操作和 IO 多路复用,更关键的是它在数据结构上做了大量极致优化。每一种结构都是为了解决某个特定痛点而存在的,理解了痛点,你才算真正理解 Redis。

2.1 String 的底层是 SDS,Redis 为什么非要自己造一个字符串

String 在 Redis 内部的载体叫 SDS(Simple Dynamic String,简单动态字符串),而不是 C 语言原生字符串。C 字符串用 char[] 加 \0 结尾,看起来够用,但 Redis 这种高性能场景下有一堆硬伤。

首先是长度获取。C 字符串要数到 \0 才能知道长度,复杂度 O(N);SDS 在头部直接用 len 字段记录长度,查询是 O(1)。其次是缓冲区溢出问题。C 字符串拼接时如果忘了手动分配内存,可能把相邻内存写坏;SDS 在 API 里自动检查剩余空间并扩容,从机制上杜绝了溢出。

更关键的两点是空间预分配和惰性空间释放。空间预分配是指 SDS 扩容时会多分配一些内存:如果修改后的长度小于 1MB,就分配 2 * len 的容量;如果大于等于 1MB,就额外分配 1MB。这样连续做多次 append 操作时,不需要每次都触发系统调用去重新分配内存。惰性空间释放则是在缩短字符串时不立即回收多余内存,而是留着下次 append 用,同样是为了减少内存重分配频率。

最后还有二进制安全问题。C 字符串遇到 \0 就会截断,所以存不了图片、序列化后的对象这些含 \0 的二进制数据;SDS 用单独的 len 字段记录长度,字符串中间是什么都不影响,真正做到二进制安全。所以 Redis 里你甚至可以安全地把一个 Java 序列化对象塞进 String。

SDS 本身还有三种编码形态:int、embstr 和 raw。当存储的是整数且可以用 long 表示时,直接用 int 编码,8 字节搞定;当字符串长度不超过 44 字节时用 embstr,把 SDS 头结构和数据存在同一块连续内存里,减少一次内存分配;超过 44 字节则用 raw,头结构和数据分两块内存。这三种编码是透明的,你正常 SET/GET 就行,但 OBJECT ENCODING key 命令可以查看到的实际编码,排查问题时非常有用。

2.2 List 的底层是 quicklist,双向链表和紧凑数组的折中方案

List 的底层实现经历了好几代演进:最早的版本是双向链表,每个节点独立分配内存,优点是插入删除方便,缺点是内存碎片严重、每个节点还要额外存前后指针。后来引入了 ziplist(压缩列表),把一串数据存在连续内存里,内存紧凑但不支持在中间高效插入;再后来变成了 quicklist,也就是“双向链表 + 若干紧凑节点”的组合。

quicklist 的思路是:把数据切分成多个节点,每个节点内部是一个小型的紧凑列表(7.0 之前是 ziplist,7.0 之后是 listpack),节点之间用双向指针串起来。这样既有链表的灵活(可以从两端快速插入弹出),又有压缩列表的内存优势(单个节点内数据连续存放,减少内存碎片)。

这里有个关键参数 list-max-listpack-size,默认值是 128,控制每个节点最多装多少元素(7.0 前的 list-max-ziplist-size 默认是 8,单位是 KB)。设置太大会让单个节点变得又大又笨重,中间插入数据要移动大量元素;设置太小则链表节点太多,指针开销增加。实际使用中,如果你用 List 做消息队列,建议保持默认配置即可,别乱调。但有一点要注意:LINSERT 或 LINDEX 在 quicklist 上是 O(N) 操作,列表很大时慎用,容易阻塞主线程。

2.3 Hash 的底层:小对象用 listpack,大对象用 hashtable

Hash 类型如果没有特别设置,内部结构会根据元素数量和单个字段长度动态切换。数据量小、字段值也短时,用 listpack 把所有键值对紧凑地排在一起;数据量大了之后切换成真正的哈希表 hashtable。

listpack 的优势是内存极其紧凑,一个 field 和一个 value 挨着放,没有额外的指针开销,非常适合字段不多的小对象。但它的劣势是字段增多后,查找需要线性扫描,插入和删除也可能涉及数据搬移。所以当哈希表中的字段数超过 hash-max-listpack-entries(默认 128)或者某个 value 长度超过 hash-max-listpack-value(默认 64)时,Redis 就会自动把它转换成 hashtable。

hashtable 就是我们常说的哈希表,用数组加链表解决冲突,查找复杂度 O(1)。Redis 的 hashtable 还有一个重要特性叫渐进式 rehash:扩容和缩容不是一次性完成的,而是分批把数据从旧表搬到新表,避免一次性搬移大对象导致主线程卡顿。你可以在扩容期间同时读写新旧两表,这期间的 CPU 开销是平滑的。

实际开发里,Hash 最大的坑是 field 数量过大引发 big key 问题。比如把一个用户的几千个字段全塞进一个 Hash,HGETALL 一次性取出所有数据时,网络传输和内存序列化的开销都很大。正确的做法是控制单个 Hash 的大小,或者拆分成多个 key。

2.4 Set 的底层:都是整数时就上 intset

Set 的底层有小惊喜。如果集合中所有元素都是整数,且元素个数不超过 set-max-intset-entries(默认 512),Redis 会用 intset(整数集合)存储。intset 的本质是一个有序的整数数组,按从小到大排列,查找可以用二分法,内存占用比哈希表小得多。

intset 有一个特别有意思的设计:升级机制。假设当前集合存储的是 16 位整数数组,当插入一个 32 位整数时,整个数组会先升级到 32 位,再把新元素插进去。升级虽然要搬移所有元素,但一个集合一生通常只升级一次,所以整体性价比很高。不过要注意,intset 只支持升级、不支持降级,删掉大整数后不会自动降回小位数,这一点在内存规划时别忽略。

当集合不再满足“全整数”或“数量超限”条件时,intset 会切换为 hashtable 编码。另外 Redis 7.2 之后新增了一个变化:小规模的字符串集合也可以使用 listpack 编码,参数是 set-max-listpack-entries 和 set-max-listpack-value,进一步优化了小型字符串集合的内存占用。

Set 实战中最常被问到的是抽奖场景。用 SADD 把参与用户 ID 全部加入集合,SPOP 随机弹出中奖者,天然去重且无需自己写随机算法。但注意 SMEMBERS 在集合很大时会一次性返回所有元素,线上建议用 SSCAN 分批遍历。

2.5 ZSet 的底层:skiplist 和 dict 的组合拳

ZSet 是 Redis 五种类型里底层结构最复杂、面试也最爱考的一个。核心设计是 skiplist(跳表)+ dict(哈希表) 的组合:跳表负责按 score 排序和范围查询,dict 负责 O(1) 地按 member 查分数。

为什么跳表能胜任排序?你可以把跳表想象成一个多层的链表,最底层是包含所有元素的普通有序链表,每一层通过稀疏的指针跳过部分节点。查找时从最高层往下走,每层都大步跳过,逐渐缩小范围,平均查找复杂度是 O(log n)。Redis 的跳表实现里还加了一个 backward 指针支持倒序遍历,score 相同时按 member 的字典序排列,保证顺序唯一且可预测。

很多人会追问:为什么不用红黑树或 B+ 树?答案有三层。第一,跳表实现简单,几十行代码就能写清楚,调试和扩展都方便;平衡树的插入删除要处理各种旋转,工程实现和维护成本高得多。第二,跳表的范围查询非常自然,从某个 score 起点开始沿着链表往后走即可,区间操作不受层数影响;第三,内存占用可控,跳表的层数是随机生成的,Redis 里层数最大 32,每层指针只有 8 字节,平均下来每个节点约 1.33 个额外指针,比平衡树省不少内存。

当 ZSet 元素较少时(默认不超过 128 个且 member 长度不超过 64 字节),也会用 listpack 紧凑存储,这时成员是按 score 有序排列的,但插入需要搬移数据,所以只适用于小规模数据。数据量变大后,listpack 会转换成 skiplist + dict 的组合。这个转换时机也是面试常考点,后面章节单独讲。

明白了跳表的结构,排行榜的实现就水到渠成了:ZADD 写入分数,ZREVRANGE 取排行榜前 N 名,ZRANK 查某个用户的排名,都是 O(log n) 级别,比在数据库里 ORDER BY score LIMIT 高效太多。

3. 编码转换与内存优化实战:为什么数据量大了 Redis 反而变慢

理解了每种类型的两套结构之后,接下来要解决一个更实际的问题:什么时候从紧凑编码切换成标准结构?这个切换逻辑直接影响你的内存占用和查询性能。很多人没意识到,某些 key 在业务增长到阈值时,会默默发生一次全量数据搬移,如果这个 key 很大,这次搬移是会卡主线程的。

3.1 编码转换的触发条件与关键参数

Redis 每个 key 的编码不是写死的,而是根据数据特征动态决定。查看编码最直接的方式是 OBJECT ENCODING key 命令,它返回 int、embstr、raw、listpack、quicklist、hashtable、intset、skiplist 等字符串。我测过的所有生产环境,排查 big key 时第一步都是先 OBJECT ENCODING 看一下它现在的形态。

触发转换的核心配置项如下:

配置项 默认值 触发切换条件
hash-max-listpack-entries 128 Hash 字段数超过 128 时 listpack 转 hashtable
hash-max-listpack-value 64 Hash 某个 value 长度超过 64 字节时转换
list-max-listpack-size 128 List 每节点元素数超过 128 时调整 quicklist 节点
set-max-intset-entries 512 Set 元素超过 512 个时 intset 转 hashtable
zset-max-listpack-entries 128 ZSet 元素超过 128 个时 listpack 转 skiplist + dict
zset-max-listpack-value 64 ZSet member 长度超过 64 字节时转换

这里要注意,转换是惰性的,也就是说只有在写入时发现新数据不满足当前编码条件,才会触发整体转换。比如一个 Hash 已经存了 127 个字段,当你第 128 个字段写入时触发转换,而不会在后台提前转。大 key 的转换过程是一块连续内存的重新分配和数据拷贝,在极端情况下会让主线程卡上几秒,线上碰到大 key 升级之前一定要评估。

3.2 listpack 替代 ziplist:修了一个隐藏最深的坑

如果你翻过旧版本的 Redis 源码或老博客,会看到 ziplist 这个词。7.0 之前 List 的 quicklist 节点、Hash 和 ZSet 的小数据量编码都用的 ziplist。ziplist 的设计在当时很优秀,但它有一个臭名昭著的问题——级联更新。

ziplist 里每个 entry 的头部都记录了前一个 entry 的长度,如果某个 entry 被修改后长度变大,下一个 entry 的“前一个 entry 长度”字段可能装不下,需要扩大,这又会让再下一个 entry 的头部字段变大,引发连锁反应。极端情况下,一次插入可能导致整个列表的所有 entry 依次搬移,时间复杂度从 O(1) 退化到 O(N)。

listpack 的解法很巧妙:每个 entry 只记录自己的长度,不记录前一个 entry 的长度,彻底斩断了级联更新的链条。虽然代价是反向遍历时无法直接跳转,但 Redis 通过额外设计解决了遍历场景,换来的是最坏情况下插入复杂度可控。

这个演进给我们的启示是:即使一个结构在 99% 的场景下表现优秀,剩下 1% 的极端抖动也值得为它重写一版。Redis 7.0 将 ziplist 全面替换为 listpack,正是为了消除长期潜伏的性能隐患。面试时如果被问到“为什么 Redis 7.0 用 listpack 替换 ziplist”,把级联更新讲清楚,同时提一下 listpack 如何从源头解决这个问题,基本就稳了。

3.3 内存优化实战:我如何把缓存内存占用砍掉一半

讲原理之外,我给你一个真实可落地的内存优化思路。有一年我接手一个缓存服务,内存涨到接近上限却找不到原因。逐个 key OBJECT ENCODING 排查后发现,一个存用户昵称的 Hash,键只有几十个字段,但 value 大多是 JSON 串,长度超过了 64 字节,全部从 listpack 升到了 hashtable。hashtable 有大量指针开销,存储小对象时内存利用率非常低。

后来做的事很简单:把字段值从完整 JSON 改成精简字符串,把长 JSON 挪到 String 类型单独缓存,Hash 里只保留短字段,让编码重新回到 listpack。目标其实不是省几个字节,而是让数据形态匹配 Redis 的内存友好编码,这样能同时减少内存碎片和序列化开销。

再补充一个容易被忽略的命令:MEMORY USAGE key 可以精确查看某个 key 实际占用的内存字节数。排查大 key 内存时,用 MEMORY USAGE 比看 DEBUG OBJECT 的 serializedlength(序列化长度)更贴近实际内存占用,因为它包含了 key 本身、值对象、底层结构的所有开销。这个命令在集群中定位内存黑洞非常有用。

4. 高频面试题与线上排查实录:别让底层结构只停留在理论

底层结构这类知识,背概念和真正能解决问题之间隔着一条鸿沟。这一章我把面试高频问题和线上实战排查经验放在一起,既给你背题模板,也给你排查问题的切入思路。面试官问底层结构,本质上不是考记忆,而是在考察你有没有真正理解 Redis 的设计哲学。

4.1 面试高频问题清单与参考思路

我整理了几个被问烂但依然有效的题目,配上回答要点,你可以照着这个思路组织自己的语言。

Q1:Redis 的 String 底层为什么不用 C 字符串?
回答方向:从 O(1) 长度获取、缓冲区溢出防护、空间预分配与惰性释放、二进制安全四个点展开。最后补一句“所以 Redis 可以安全存储序列化对象和图片二进制”,立刻显得有实战深度。

Q2:ZSet 底层为什么用跳表而不用红黑树?
回答方向:先讲跳表的多层链表结构,再说实现的简单性,然后分析范围查询场景下跳表的天然优势,最后提内存占用。如果面试官追问为什么不用 B+ 树,可以补充 B+ 树更适合磁盘 IO 的数据库存储,Redis 是纯内存系统,跳表的指针跳跃在内存里是随机访问,性能已经足够。

Q3:ziplist 被 listpack 替换的原因?
回答方向:级联更新问题讲清楚,再对比 listpack 每个 entry 只记录自身长度、切断连锁反应的做法。若再深入,可以提 7.0 的发布说明里列表、哈希、有序集合的小数据量编码统一切到 listpack,set 也在 7.2 中加入了 listpack。

Q4:什么是 big key?怎么发现?
回答方向:big key 指单个 key 的 value 过大或元素过多,比如一个几 MB 的 String、一个百万字段的 Hash。发现方式:redis-cli --bigkeys 扫描、MEMORY USAGE key 逐点确认、慢日志中超过预期的命令定位。影响主要是阻塞主线程和网络传输瓶颈。

Q5:为什么 Redis 单线程还能这么快?
虽然这题不直接对应底层结构,但很常被连在一起问。回答方向:纯内存访问 + 数据结构高效 + IO 多路复用 + 避免线程切换和锁竞争。可以顺带提一句 6.0 之后引入的网络 IO 多线程,但执行命令依然单线程,因为最核心的数据结构设计已经在单线程模型下足够高效。

4.2 我用 object encoding 排查问题的一次完整案例

分享一个我印象深刻的排查经历。线上某服务偶尔出现命令超时,慢日志指向一个 HGETALL 操作,响应时间波动到 100ms 以上。当时这个 Hash 其实就是个用户资料,理论上应该很快才对。

我第一反应是查 OBJECT ENCODING user:profile:1024,返回结果是 hashtable,说明这个 Hash 已经脱离了 listpack 紧凑编码。再查 HLEN,字段数 400 多,远超 128 的阈值。问题找到了:业务上线初期字段少,listpack 编码跑得很欢;随着字段逐渐增多,超过阈值后自动转换成了 hashtable,虽然单次操作复杂度仍接近 O(1),但连续触发的 rehash 在大对象拷贝时产生了明显的 CPU 峰值。

解决方案也很直接:把这份用户资料拆成多个 Hash,每个 Hash 控制字段数在 100 以内,让它们回到 listpack 编码。上线后 HGETALL 耗时稳定在个位数毫秒。这个案例现在回头看很基础,但当时排查了小半天,核心教训是:看到命令慢,别只盯着命令本身,先看数据结构的编码形态对不对。一个已经转成 hashtable 的大 Hash,和小而美的 listpack 小 Hash,性能是两个世界。

4.3 实战中容易踩的隐藏坑

除了 big key,我再列几个实际运行中很容易踩的坑,有的坑我都是真金白银换来的经验。

  • List 的中间插入和索引访问不要用于大列表。LINSERT 和 LINDEX 是 O(N) 操作,List 一旦有几十万元素,这类操作就可能阻塞主线程。用 List 做队列没问题,因为只动两端;但千万别拿它当数组用。
  • 不要频繁 SET 大对象到 String。String 的 raw 编码每次修改都可能触发内存重分配,如果你反复修改一个几百 KB 的 JSON,SDS 的扩容、拷贝、可能的内存碎片会逐步拖垮性能。这种数据更适合拆字段存 Hash,或者走对象存储。
  • Set 的 SMEMBERS 要小心。集合巨大时,SMEMBERS 一次性返回全部元素,网络包和内存都会爆炸。线上遍历集合用 SSCAN,不要图省事一把梭。
  • 过期删除和内存淘汰也是有开销的。虽然这和数据类型直接关系不大,但当你用带过期时间的 key 时,过期键的惰性删除会卡在某个访问的命令上,所以高峰期偶尔的偶发延迟可能和过期键清理有关,别误判成结构问题。

4.4 排查工具组合:三分钟定位问题的标准动作

最后给一套我常用的排查“标准动作”,当你怀疑某个 Redis key 的数据结构导致性能问题时,按这个顺序执行,基本不会跑偏。

先 TYPE key 确认类型,再 OBJECT ENCODING key 看编码形态,判断它是否处于预期的小数据量紧凑模式。第三步 STRLEN(String)或 HLEN/ZCARD/SCARD/LLEN 看体量级,判断是否超过配置阈值。第四步用 MEMORY USAGE key 看实际内存占用,确认是不是 big key。最后 redis-cli --bigkeys 全库扫描,一轮就能找出所有体量异常的大 key。

这套流程看起来简单,但真正遇到问题时不慌不乱、按步骤来,比随手拍脑袋要快得多。我处理过的多数 Redis 性能问题,最后都能落到“某个 key 的数据量超过编码阈值”或“某个命令复杂度太高”这两个结论上。

结尾

这篇文章写到这里,我其实最想强调的是:Redis 的底层结构不是一堆需要死记硬背的名词,而是围绕“内存效率”和“操作效率”两个核心目标的工程取舍。SDS 为了二进制安全和减少内存重分配,quicklist 为了平衡链表与紧凑存储,listpack 为了消灭级联更新,skiplist 为了用简单实现换范围查询能力——每一个设计背后都有明确的痛点,理解这些痛点,你在选型和排查时就有了判断依据。

我个人在实际操作中的体会是,花一个小时认真看一遍 OBJECT ENCODING 返回的各种编码,配合 MEMORY USAGE 实测几个 key,比单纯背十篇面经都有用。尤其是刚接手一个 Redis 集群时,先跑一遍 --bigkeys,把那些体量异常的 key 全部列出来,能省掉后面无数个加班的夜晚。你甚至可以把这套排查流程和团队的监控告警结合起来,遇到延迟毛刺时自动抓取编码和内存快照,很多问题就不用在事发后盲人摸象了。Redis 的底层知识是那种“平时用不上、出事时救命”的东西,提前把功课做了,比临时抱佛脚实在得多。

Redis 数据结构与底层实现深度解析:从 SDS 到跳表,面试考点全覆盖
本文系统剖析Redis五大基础数据类型(String、List、Hash、Set、ZSet)的底层编码实现,重点涵盖SDS动态字符串、quicklist、ziplist/listpack、intset、跳表+哈希表双结构等核心机制,深入讲解空间预分配、惰性释放、连锁更新、编码转换条件及跳表原理,并对比红黑树,覆盖面试高频考点与生产避坑要点。
.Bryce.
607
深入解析Redis底层数据结构:SDS、listpack与跳表的内存奥秘
本文深入剖析Redis中String和zset的底层实现:SDS通过多类型头(sdshdr8/16/32/64)与智能扩容策略实现O(1)长度获取与内存优化;zset采用listpack(小数据)与skiplist+dict双结构(大数据)组合,兼顾查询效率与内存控制;重点揭示编码切换机制、span字段在ZRANK中的作用、p=0.25层高概率设计原理,以及实际容量估算方法。
不靠谱的糖饼
349
Redis底层编码与数据结构优化:从SDS到跳表全面解析
本文系统解析Redis五大核心数据类型(String、List、Hash、Set、ZSet)的底层编码实现与动态切换机制。重点涵盖SDS字符串设计原理、listpack替代ziplist的演进、intset与hashtable在Set中的条件切换、跳表+哈希表在ZSet中的协同设计,以及编码转换阈值配置、内存优化策略和常见阻塞问题排查方法。内容聚焦于内存节省与性能平衡的设计哲学,适用于Redis深度调优与大Key治理。
股海求生
263
Redis 为什么又快又省内存?五大类型底层结构与编码切换全解
本文深入解析Redis五大基本类型(String、List、Hash、Set、ZSet)及三种扩展类型(Geo、HyperLogLog、Stream)的底层数据结构与encoding编码切换机制。重点涵盖SDS、ziplist、listpack、quicklist、hashtable、intset、跳表等八种核心结构的设计原理、内存优化策略与性能权衡,详细说明编码切换阈值(如44/128/512)、版本演进(如Redis 7.0用listpack替代ziplist)及实际业务适配逻辑。
青山木
356
Redis底层数据结构:String与zset的编码切换与内存优化
本文深入剖析Redis中String和zset的底层编码切换逻辑:String支持int、embstr、raw三种编码,依赖redisObject头与SDS结构,44字节为embstr/raw分界;zset在元素少时采用listpack(替代ziplist),多时切换为skiplist+dict双结构,阈值默认为128元素或64字节member。文章涵盖内存开销估算、编码切换条件、排查命令(OBJECT ENCODING/DEBUG OBJECT)及实战优化建议。
weixin_34195546
293
Redis 常见的数据类型及底层结构
本文深入剖析Redis五种核心数据类型(String、List、Set、ZSet、Hash)的底层实现机制,重点阐述SDS、quicklist、intset/listpack、skiplist+dict、hashtable等数据结构的选择逻辑,揭示其在内存占用与操作性能之间平衡的设计哲学,并说明各类型编码转换条件与时间复杂度特征。
诩星
234
【Redis】5个基本数据类型
本文深入解析Redis的5种基本数据类型:String、List、Set、Hash和ZSet,重点阐述其底层实现结构(如SDS、QuickList、IntSet、Dict+SkipList、ListPack等),自动编码切换策略及内存与性能权衡机制。涵盖各类型的适用场景、阈值配置、时间复杂度特性及Redis 7.0引入的ListPack优化。
YanDDDeat
2888
Redis存储原理与数据模型(下)
本文深入探讨了Redis的存储原理和数据模型,包括string、hash、list、set和zset等数据类型的实现方式及其编码转换机制。详细解析了Redis6.0与7.0版本中数据结构的演进,如hash从ziplist到listpack的转变,以及listpack和跳表的设计思想和优缺点。最后,总结了Redis的高效数据结构,并回答了关于渐进式rehash和字符串分界线选择的问题。
冷崖
1183
Redis内存优化实战:从数据类型到底层编码的深度解析
本文深入解析Redis内存占用的底层机制,重点剖析redisObject、SDS、dictEntry等固定开销,以及ziplist/listpack、intset、skiplist等编码对内存的影响。结合Redis 7.0编码变更,详解Hash、ZSet、List等数据类型的紧凑编码选型策略,强调结构误用(如String存小对象、ZSet长member)导致的数倍内存浪费,并提供MEMORY USAGE精准测算、bigkeys排查、OBJECT ENCODING定位等实战方法论。
GreedyAbyss
272
Redis底层数据结构深度解析:五种类型的内存编码与切换机制
本文深度解析Redis五种基础数据类型的底层内存编码实现及动态切换机制:字符串的int/embstr/raw编码、哈希与列表的listpack与hashtable切换、集合的intset与zset的skiplist+dict双结构设计;重点剖析ziplist到listpack的演进逻辑(解决级联更新)、quicklist的工程折中、跳表在zset中的不可替代性;并结合大key治理、编码转换延迟、配置调优等实战场景,揭示内存与性能的隐藏成本。
陈紫璇
213
Redis数据编码
本文深入解析Redis中String、List、Set、ZSet和Hash五种数据类型的底层内存编码机制。重点涵盖String的int/embedded/raw三态切换逻辑;List从ziplist/linkedList到quicklist的演进;Set在intset/listpack/hashtable间的动态选择策略;ZSet双结构(跳表+字典)协同实现O(1)查分与O(logN)排序;以及Hash的listpack与dict编码转换规则。所有分析均围绕内存布局、时间复杂度与状态迁移展开。
czlczl20020925
412
Redis 03 · 底层原理:RedisObject 与核心编码
本文深入解析Redis的底层编码机制,重点阐述RedisObject作为统一对象外壳的作用,以及SDS、listpack、dict、quicklist、intset和skiplist等核心数据结构的设计动机与适用场景。文章说明逻辑类型(type)与物理编码(encoding)的分层设计如何在内存占用与查询性能间动态权衡,并强调编码随元素数量、大小及版本自动切换的特性。内容覆盖String的三种编码、List/Hash/Set/Sorted Set的多编码策略、渐进式rehash原理及生产环境中的观测与调优要点。
莫得感情 o
318
Redis数据类型从原理到实战:String、Hash、ZSet选型与排障全解
本文系统解析Redis五种基本类型(String、Hash、List、Set、ZSet)及Bitmap、HyperLogLog、Geo、Stream等扩展类型的底层编码机制(如SDS、listpack、skiplist)、适用场景与性能陷阱。重点涵盖String的三种编码与序列化风险、Hash的对象缓存优势与大key治理、ZSet跳表设计与score设计坑点、List与Stream在消息队列中的定位差异,以及TYPE/OBJECT ENCODING/MEMORY USAGE等排障命令实践。
烂人不配爱
270
Redis Zset底层实现全解析:从listpack到跳表+dict,面试与排障必备
有序集合是Redis中兼具排序与高性能查询的核心数据结构,广泛用于排行榜、延迟队列与实时计数场景。要真正理解它,需从编码选型开始:数据量小时采用listpack紧凑存储,节省内存;一旦突破阈值,则升级为skiplist跳表,并与dict哈希表协同,实现O(logN)的有序插入和O(1)的精准分数查询。本文深入拆解跳表节点结构、ZADD/ZRANGE的运作机制,以及为什么选择跳表而非红黑树。同时结合参数调优、double精度陷阱和线上实测数据,给出编码转换、内存突增、排名错乱等典型问题的排查方法,帮助开发者从
全网最全Redis底层实现数据结构详解
本文深入剖析Redis常用数据类型的底层实现机制,涵盖SDS、dict、listpack、quicklist、intset、skiplist和radix tree等核心结构。重点讲解不同规模下编码的动态切换策略及其对内存与性能的影响,并提供线上诊断方法与典型性能陷阱规避建议。
CodeAmaz
619
Redis底层数据结构
本文深入解析Redis中的各种数据结构,包括RedisObject、SDS动态字符串、压缩列表ziplist、哈希表hashtable、跳表skiplist、双向链表linkedlist、整数数组intset、快速列表quicklist及紧凑列表listpack等,探讨它们的特性和应用场景。
geek-wmy
1436
面试必问:Redis五种数据结构底层原理与实战避坑指南
本文深入解析Redis String、Hash、List、Set、ZSet五种核心数据结构的底层实现机制,涵盖SDS、listpack、quicklist、intset、skiplist+dict等关键编码方式,阐明编码转换阈值、内存优化逻辑及行为边界。重点剖析各结构在实际场景(如排行榜、购物车、消息队列、去重统计)中的选型依据,并揭示大key处理、过期策略、原子性保障等生产级避坑要点。
Huigr王
299
【Redis】2026 互联网大厂 Redis 面试高频题
本文系统梳理2026互联网大厂Redis面试核心考点,涵盖基础原理(单线程高效机制、I/O多路复用、过期键删除策略)、数据类型底层实现(SDS、quicklist、listpack、dict及切换条件)、缓存三大问题(穿透/击穿/雪崩的生产级解决方案)、持久化机制(RDB/AOF/混合持久化)、高可用方案(主从复制、哨兵模式、Cluster集群原理与对比)、Pipeline原子性缺陷与主从切换风险、Pub/Sub在缓存失效广播中的关键应用。内容深度结合源码逻辑、生产调优与事故案例。
童话ing
3802
redis数据结构
本文深入解析Redis的redisObject对象结构及其九大核心数据类型(string、hash、list、set、zset、bitmap、hyperloglog、geo、stream)的底层编码实现。重点阐述Redis 7.2.5中int/embstr/raw字符串编码、listpack替代ziplist的演进、set/zset的多编码切换策略(intset/hashtable/listpack/skiplist),以及内存优化机制如整数共享池和连续内存分配原理。
一本正经的不务正业
337
Redis源码分析
本文深入分析Redis源码,介绍RedisObject结构,阐述五大常用数据类型(String、Hash、List、Set、ZSet)的底层源码。详细讲解各类型的编码方式、数据结构特点及适用场景,如String的三种编码、Hash的压缩列表与哈希表等,还分析了跳表的原理、复杂度及优缺点。
Stay Peace
662
redis数据类型八股文,看这一篇就够了
综上所述,Redis 的 Zset 数据类型以及其底层实现机制如跳表、压缩列表、Listpack 和 SDS 等,共同构建了 Redis 在内存管理和数据操作方面的强大能力。
赛尔号副船长
65
小林的图解系列之一,图解redis数据结构
这些数据类型是Redis键值对中值的存储形式,而它们的底层数据结构包括SDS、双向链表、压缩列表、哈希表、跳表、整数集合、quicklist、listpack等。1.
java路上的化工人
85
redis数据类型及其数据结构
Redis作为高性能键值数据库,支持多种数据类型,每种类型都依赖特定的数据结构。字符串类型基于SDS实现,列表类型在元素少时使用压缩列表或listpack,元素多时使用链表。哈希类型在字段少时采用zipmap或ziplist,字段多时使用哈希表。集合类型小型使用intset,通用情况使用hashtable。有序集合类型使用跳表和哈希表。
白蛇杀掉了许仙
从SDS到quicklist:图解Redis九种底层数据结构设计哲学与内存优化技巧
zhuyuhe666
别再死记硬背Redis面试题了!一张图搞懂String、Hash底层到底怎么存(附Listpack新特性)
庄大卫
《Redis深度探索:解锁高级应用与性能优化》
资源摘要信息:"《Redis深度探索:解锁高级应用与性能优化》是一份面向中高级后端开发者、分布式系统工程师及数据库架构师的系统性技术指南,其核心价值在于突破传统Redis入门级认知边界,深入剖析Redis作为现代高性能数据中间件在复杂业务场景下的工程化落地能力。本文并非简单罗列命令语法,而是以‘问题驱动+原理透析+架构演进+实战验证’四维一体的方式,构建起一套完整的Redis高阶知识体系。首先,在高级数据结构层面,文章对位图(Bitmap)进行了本质性解构——明确指出其并非独立数据类型,而是基于String类型的位级抽象,依托SDS(Simple Dynamic String)二进制安全特性实现底层存储,从而在逻辑上支持高达2³²(约42.9亿)个独立bit位的寻址能力;进一步揭示其空间效率优势的本质:在用户签到系统中,1亿用户365天的全量记录仅需约4.5MB内存(1e8×365÷8÷1024÷1024),相较传统关系型表存储可压缩99%以上空间;同时强调BITCOUNT、BITPOS等聚合操作的O(N)时间复杂度隐含的工程陷阱——当处理超长位图时需结合分片策略或预计算缓存规避响应延迟。HyperLogLog部分则从概率算法底层切入,阐明其基于LC(Linear Counting)、LLC(LogLog Counting)及HLL(HyperLogLog)三代演进的数学原理,指出其误差率0.81%的理论保障源于调和平均数对异常桶值的鲁棒性抑制,并通过UV统计实例对比MySQL COUNT(DISTINCT)在亿级数据下的秒级响应与百毫秒级差异。Geospatial模块不仅解析GEOADD/GEOSEARCH等命令的球面几何计算逻辑(采用GEODIS和Haversine公式),更深入ZSET底层跳表与GeoHash编码的协同机制,揭示地理围栏查询中精度与性能的权衡策略。Stream作为Redis 5.0引入的持久化消息队列,其设计对标Kafka,具备消费者组(Consumer Group)、消息ID自增、ACK确认、pending列表等企业级特性,文章通过电商订单履约链路案例,演示如何利用XREADGROUP+XACK构建Exactly-Once语义的分布式事务补偿机制。在分布式实践维度,分布式锁不再停留于SETNX+EXPIRE的原始方案,而是系统分析Redlock算法在时钟漂移、网络分区下的安全性缺陷,提出基于Lua脚本原子执行+租约续期(renewal)+锁标识唯一性的工业级实现范式;限流防刷则融合令牌桶与漏桶双模型,借助EVAL执行嵌入式限流脚本实现毫秒级精度控制;任务队列部分对比了RPOPLPUSH+BRPOPLPUSH的可靠性缺陷,推荐使用Stream+ACK+Dead Letter Queue的三级容错架构。性能优化体系覆盖全栈视角:主从复制强调PSYNC2增量同步的RDB+AOF混合传输机制;哨兵模式解析主观下线与客观下线的Quorum判定逻辑;集群模式深入Slot迁移过程中的ASK/MOVED重定向协议及Gossip通信开销;内存优化直击Jemalloc内存分配器碎片化问题,提出maxmemory-policy选择策略(如allkeys-lru vs volatile-ttl的适用边界)、ziplist与listpack压缩结构的阈值调优、以及大Key拆分与Hot Key本地缓存(Multi-Level Cache)的混合部署方案;慢查询分析则要求结合SLOWLOG GET 10与redis-cli --latency输出,定位由KEYS *、FLUSHALL等阻塞命令引发的事件循环阻塞(Event Loop Blocking)。针对数据过期,文章对比定时删除、惰性删除与定期删除三种策略的CPU/内存消耗比,推荐在高并发场景启用active-expire-effort参数动态调节清理强度;热点Key问题提出Proxy层一致性哈希分片+客户端本地LRU缓存+服务端读写分离的立体防护;数据丢失防范则涵盖AOF everysec策略的fsync内核缓冲区风险、RDB快照的fork阻塞问题,以及混合持久化(aof-use-rdb-preamble)的故障恢复加速机制。最后,展望Redis 7.0+的Server-Assisted Client Side Caching、Redis Functions(原生Lua替代方案)、以及与Redis Stack融合的全文搜索、JSON处理、向量相似度计算等AI增强能力,标志着Redis正从单一缓存引擎向实时数据平台演进。全文贯穿37个可运行代码片段、12个生产环境压测数据对比图表、9类典型故障排查checklist,真正实现理论深度、架构视野与工程落地的三重统一。"
奔跑吧邓邓子
Redis篇2——从命令到内存:揭秘Redis 5大对象与6大结构的动态映射法则
三言两个菜
Redis 是怎么用不同底层结构来支撑 String、List、ZSet 这些数据类型的?
@Peanut34
【Redis核心数据结构深度解析】:String、Hash、Ziplist底层实现与性能优化秘籍
SW_孙维
redis-7.0.2
Redis 7.0.2 是 Redis(Remote Dictionary Server)项目在2022年11月正式发布的稳定版本,作为第七代主版本(7.x系列)的第二个补丁版本,它在Redis 7.0.0基础上修复了若干关键缺陷、提升了稳定性与安全性,并进一步优化了核心功能模块的性能表现。该版本标志着Redis从传统单机内存数据库向更现代化、更面向云原生与高并发场景的分布式数据平台演进的重要里程碑。其源码包(redis-7.0.2.tar.gz)完整包含了C语言编写的全部核心实现,涵盖网络层(基于epoll/kqueue/io_uring)、事件驱动模型(AE异步事件库)、内存管理子系统(jemalloc集成与自定义分配器适配)、持久化引擎(RDB快照与AOF日志的协同机制)、复制协议(PSYNC2增强版)、集群模式(Redis Cluster v1.0协议兼容性加固)、模块系统(Redis Modules API v2升级)、以及全新引入的原生Redis Functions(函数即服务)能力——这是Redis首次将可编程性深度内嵌至服务端,允许用户以Lua或JavaScript(通过V8引擎插件)编写并部署无状态计算逻辑,从而大幅降低客户端与服务端之间的往返开销。在数据结构实现层面,Redis 7.0.2对底层基础类型进行了精细化重构:字符串(String)引入了“embstr”与“raw”双模式智能切换策略,并强化了SDS(Simple Dynamic String)的内存预分配与惰性释放机制;列表(List)全面弃用已废弃的ziplist编码,统一采用listpack(紧凑型字节数组)替代原有ziplist+linkedlist混合编码,显著降低内存碎片率并提升小元素场景下的遍历效率;哈希(Hash)、集合(Set)与有序集合(ZSet)亦同步迁移至listpack或intset(整数集合)+ hashtable双编码体系,其中ZSet新增了listpack-backed跳表降级路径,在元素数量较少且score为整数时自动启用低开销存储格式;位图(Bitmap)、HyperLogLog、GEO地理空间索引等高级数据结构均获得API语义增强与边界条件鲁棒性加固。尤为关键的是,Redis 7.0.2首次将Stream数据结构升级为“完全支持消费者组(Consumer Group)多播语义”的生产就绪级别,引入NOACK选项、AUTOCLAIM自动故障转移、XPENDING增强统计接口,并与ACL权限模型深度整合,使Stream真正具备企业级消息中间件能力。在高并发处理方面,Redis 7.0.2重构了I/O多路复用抽象层,正式将io_uring(Linux 5.1+内核特性)纳入默认事件驱动后端,相比传统epoll实现,在超大规模连接(10万+ client)与超高吞吐写入(百万QPS级)场景下,系统调用开销下降达40%,CPU利用率峰值降低25%以上;同时,主线程与IO线程分离架构得到进一步验证,虽未默认启用多线程IO(仍需配置io-threads),但其内部线程池调度器、任务分片策略及内存屏障插入点均已按NUMA感知方式重写,为后续全链路多线程化奠定坚实基础。此外,客户端缓存(Client Side Caching)功能在7.0.2中完成稳定性收口,支持tracking mode下的广播失效(broadcast invalidation)、前缀匹配(prefix-based)、以及OPTIN/OPTOUT细粒度控制,结合RESP3协议的push消息能力,使客户端能构建零延迟本地缓存一致性协议。安全与运维维度上,Redis 7.0.2强化了TLS 1.3支持(OpenSSL 3.0+兼容)、ACL规则继承链优化、CONFIG REWRITE原子写入保障、以及INFO命令输出字段的分级脱敏机制;其内置的Memory Analyzer工具(MEMORY DOCTOR / MEMORY USAGE)可精准定位大key、热key分布及内存泄漏路径;而全新的Redis CLI交互式调试器(redis-cli --debug-mode)支持断点设置、堆栈追踪与实时内存快照比对,极大提升源码级问题排查效率。综上,redis-7.0.2源码包不仅是学习高性能键值系统设计思想的绝佳范本,更是深入理解现代NoSQL数据库如何在内存计算、网络编程、并发控制、持久化容错与云原生适配五大维度协同演进的核心教材——其每一行C代码背后,都凝结着对低延迟、高吞吐、强一致与易运维这一永恒矛盾体的持续求解。
skystep