【面朝大厂】万字+图解 Redis,面试不用愁了!2万字详解

发布时间:2026/10/4 20:05:01
【面朝大厂】万字+图解 Redis,面试不用愁了!2万字详解
一、开篇Redis 为什么是面试高频考点如果你正在准备大厂面试Redis 几乎是绕不开的一道坎。不管是后端开发、基础架构岗位还是大数据、中间件方向面试官总会顺着「缓存」「高并发」「分布式」这些关键词把问题一路追问到 Redis 的底层实现。原因并不复杂Redis 是目前使用最广泛的缓存中间件同时也是分布式场景下最常用的「瑞士军刀」——缓存、排行榜、计数器、分布式锁、消息队列、限流、去重都能看到它的身影。更重要的是Redis 本身足够「小而美」。它的核心代码量不大但设计处处体现出对性能的极致追求单线程模型、IO 多路复用、内存数据结构、渐进式 rehash、惰性删除……每一个设计决策背后都藏着值得深挖的计算机基础知识。面试官通过 Redis可以高效地考察你对网络编程、操作系统、数据结构、分布式系统的综合理解。本文的目标就是帮你把 Redis 面试中最高频、最容易挖坑的知识点系统梳理一遍。全文结合图示与实战命令从数据类型到底层结构从持久化到高可用从缓存三大问题到分布式锁形成一张完整的知识地图。建议收藏后配合实战逐步消化面试前再过一遍心里会踏实很多。二、Redis 全景认知是什么、为什么快2.1 Redis 是什么RedisRemote Dictionary Server是一个开源的、基于内存的键值对存储系统通常被归类为 NoSQL 数据库。虽然它以「缓存」闻名但它的能力远超缓存它支持丰富的数据结构可以把一个 key 映射到字符串、哈希、列表、集合、有序集合等多种类型而不是只能存字符串。Redis 的官方描述是「in-memory data structure store」核心特点可以概括为以下几点基于内存数据主要存放在内存中读写速度极快单机 QPS 可以达到 10 万级别。数据结构丰富String、Hash、List、Set、ZSet 五种基础类型外加 Bitmap、HyperLogLog、GEO、Stream 等扩展类型。支持持久化虽然基于内存但可以通过 RDB、AOF 把数据落盘重启后恢复。支持高可用与扩展主从复制、哨兵、集群三种模式支撑从单机到分布式集群的演进。功能多样事务、发布订阅、Lua 脚本、过期策略、管道、Lazy Free 等能满足各种复杂业务。下图展示了 Redis 的整体能力版图flowchart TD CORE[Redis 核心能力] -- DATA[数据类型] CORE -- PERSIST[持久化] CORE -- HA[高可用与扩展] CORE -- ADV[高级功能] DATA -- D1[String / Hash / List / Set / ZSet] DATA -- D2[Bitmap / HyperLogLog / GEO / Stream] PERSIST -- P1[RDB 快照] PERSIST -- P2[AOF 日志] PERSIST -- P3[混合持久化] HA -- H1[主从复制] HA -- H2[哨兵 Sentinel] HA -- H3[Redis Cluster] ADV -- A1[事务 / Lua] ADV -- A2[发布订阅 / 消息队列] ADV -- A3[过期与淘汰 / 分布式锁]2.2 Redis 为什么这么快「Redis 为什么快」是面试第一问很多候选人都能说出「纯内存操作、单线程、IO 多路复用」但往往经不起追问。我们把原因拆开讲清楚1. 纯内存操作Redis 的数据几乎全部存放在内存中内存的访问延迟是纳秒级别相比磁盘的毫秒级访问差距在 10 万倍左右。绝大多数请求都是「纯内存读写」不涉及磁盘 IO这是它快的大前提。2. 单线程模型Redis 6.0 之前核心网络模型是彻头彻尾的单线程一个线程负责所有客户端的连接、命令解析、命令执行和数据返回。这听起来很反直觉但恰恰是它快的秘密之一。原因是避免上下文切换开销单线程不用在多个线程之间切换没有线程调度成本。避免锁竞争多线程需要加锁保护共享数据锁的竞争和死锁风险会拖慢性能单线程天然线程安全不需要锁。瓶颈不在 CPU 而在网络 IORedis 的操作大多是内存和简单计算真正的瓶颈是网络收发数据而不是 CPU 计算。单线程的 CPU 完全够用。当然单线程是有边界的。如果某个命令执行时间过长比如KEYS *、SMEMBERS大集合、DEL大 key就会阻塞整个 Redis。这也是为什么面试官喜欢追问「Redis 单线程为什么会阻塞」以及「如何避免 big key」的原因。3. IO 多路复用单线程最怕阻塞在等待上。如果每次等待客户端读写数据都要阻塞吞吐量会非常低。Redis 使用 IO 多路复用技术Linux 上通常是 epoll让一个线程同时监听多个连接的事件当某个 socket 有数据可读时epoll 通知 Redis 去处理没数据的时候线程不会被某个连接的等待卡死而是去处理其他就绪的连接。简单说IO 多路复用让「一个线程 非阻塞 IO」实现了近似「多线程并发服务」的效果而且没有线程切换的负担。下图是 Redis 单线程 IO 多路复用的模型示意flowchart LR C1[客户端 1] -- EV[epoll / IO 多路复用] C2[客户端 2] -- EV C3[客户端 3] -- EV EV -- LOOP[单线程事件循环] LOOP -- PARSE[命令解析] PARSE -- EXEC[命令执行] EXEC -- RESP[返回结果] RESP -- C1 RESP -- C2 RESP -- C34. 高效的数据结构Redis 没有直接使用标准库的通用数据结构而是自己实现了 SDS简单动态字符串、字典、跳跃表、压缩列表、quicklist、listpack 等结构。这些结构针对 Redis 的使用场景做了大量优化比如SDS 保存长度获取字符串长度是 O(1)而 C 字符串要 O(n)字典使用渐进式 rehash扩容时不会一次性卡顿压缩列表在元素少的时候用连续内存存储节省大量指针开销。这一部分我们在第四章会详细展开它是拉开普通候选人与优秀候选人差距的关键。5. 精心设计的编码与内存分配Redis 对每种数据类型都有多种内部编码会根据数据规模自动切换例如小集合用整数集合或压缩列表大集合才升级为哈希表或跳表。这种「小对象内存紧凑、大对象结构高效」的策略既省内存又保证性能还减少了内存碎片和 CPU cache miss。综合来看Redis 快是「内存存储 合适的数据结构 单线程无锁 IO 多路复用」多方面共振的结果任何一个单点都不足以解释它的高性能。2.3 Redis 与 Memcached 的对比面试中还经常让候选人对比 Redis 和 Memcached。两者都是内存缓存但定位差异明显对比维度RedisMemcached数据结构支持 String、Hash、List、Set、ZSet 等丰富类型只支持简单的 key-value 字符串持久化支持 RDB、AOF、混合持久化不支持纯内存重启数据丢失集群模式原生支持 Cluster、哨兵需要客户端或第三方方案实现分布式线程模型核心单线程6.0 后引入 IO 多线程多线程内存管理精细的内存编码与回收策略Slab 分配易产生空间浪费高级功能事务、Lua、发布订阅、Stream、过期淘汰等功能较弱结论如果只需要最基础的字符串缓存Memcached 也不是不行但绝大多数现代业务都更依赖 Redis 的丰富数据结构和完整生态。三、五种核心数据类型与使用场景这一章是 Redis 的「基本功」。很多候选人以为会敲SET、GET就够了但面试官往往会追问「这个业务场景你选什么类型为什么底层怎么实现的有哪些坑」我们逐一讲透。3.1 String最基础也最常用String 是 Redis 最基础的类型一个 key 对应一个字符串值。它不仅是单纯的字符串还可以存数字、序列化后的 JSON、二进制数据如图片、序列化对象。常用命令SET key value [EX seconds] [PX milliseconds] [NX|XX] GET key MSET key1 value1 key2 value2 MGET key1 key2 SETNX key value INCR key DECR key APPEND key value STRLEN key GETRANGE key start end SETRANGE key offset value典型应用场景缓存对象把序列化后的 JSON 存成 String这是最常见的缓存用法。计数器利用INCR实现浏览量、点赞数、库存扣减。由于单线程INCR是原子的不用加锁。分布式锁借助SET key value NX PX实现互斥加锁。限流结合过期时间和自增实现固定窗口限流。共享 Session多台服务器共享登录态。高频追问String 最大能存多大一个 String 类型 value 最大为 512MB。实际业务中绝不建议存这么大大 value 会带来网络传输、内存分配、阻塞删除等一系列问题。3.2 Hash对象存储的更优解Hash 是一个 field-value 映射表特别适合存储对象。相比把整个对象序列化成一个大 StringHash 可以按 field 单独读写避免「为了改一个字段而整体序列化、反序列化」的性能损耗。常用命令HSET key field value HGET key field HMSET key field1 value1 field2 value2 HMGET key field1 field2 HGETALL key HDEL key field HLEN key HINCRBY key field increment HEXISTS key field HKEYS key HVALS key典型应用场景存储用户信息如user:1001里的name、age、email。购物车以用户 id 为 key商品 id 为 field数量为 value。对象缓存比 String 存 JSON 更灵活可局部更新。高频追问Hash 存对象一定比 String 好吗不一定。Hash 适合「字段经常单独更新」的场景如果对象整体读写、字段很少变化String 序列化反而更简单。另外Hash 采取压缩列表/哈希表两种编码元素少时内存非常紧凑。3.3 List有序且可重复的双向列表Redis 的 List 是一个双向链表元素可以重复、有序。它相当于一个双端队列左右两端都能高效插入和弹出。常用命令LPUSH key value [value ...] RPUSH key value [value ...] LPOP key RPOP key LRANGE key start stop LINDEX key index LLEN key LREM key count value LTRIM key start stop BLPOP key timeout BRPOP key timeout典型应用场景消息队列LPUSHBRPOP实现简单的生产者-消费者模型。最新动态、时间线关注的人发布的新动态按时间LPUSH读取时LRANGE取前 N 条。栈与队列分别用LPUSH LPOP、RPUSH LPOP等组合实现。高频追问用 List 做消息队列有什么问题List 缺少消息确认机制消费者消费后消息就没了无法可靠重试也不支持多个消费者消费同一条消息广播。更完整的消息队列能力需要用 Redis 5.0 引入的 Stream 类型我们后面会讲。3.4 Set无序且唯一的集合Set 和 List 都存多个元素但 Set 元素无序且不重复底层是哈希表或整数集合支持 O(1) 的查询和集合运算。常用命令SADD key member [member ...] SREM key member SISMEMBER key member SCARD key SMEMBERS key SRANDMEMBER key [count] SPOP key [count] SINTER key1 key2 SUNION key1 key2 SDIFF key1 key2 SINTERSTORE dest key1 key2典型应用场景点赞、收藏、关注用 Set 存某条动态的点赞用户 id天然去重。抽奖SRANDMEMBER随机取不重复元素SPOP随机弹出。标签系统给对象打多个标签用集合运算求交集。共同好友、可能认识的人SINTER求交集、SDIFF求差集、SUNION求并集。高频追问SMEMBERS 和 SSCAN 怎么选SMEMBERS一次性返回所有元素如果集合很大会阻塞 Redis 并产生大量网络传输。大集合遍历应使用SSCAN游标式分批拉取。同理KEYS *也有类似问题应改用SCAN。3.5 ZSet带权重的有序集合ZSetSorted Set是 Redis 中最「值钱」的类型之一。它在 Set 的基础上给每个 member 绑定一个 score按 score 排序。member 唯一但 score 可以重复。常用命令ZADD key score member [score member ...] ZREM key member ZSCORE key member ZRANGE key start stop [WITHSCORES] ZREVRANGE key start stop [WITHSCORES] ZRANGEBYSCORE key min max [WITHSCORES] ZRANK key member ZREVRANK key member ZCARD key ZINCRBY key increment member ZREMRANGEBYRANK key start stop ZREMRANGEBYSCORE key min max典型应用场景排行榜游戏战力榜、商品销量榜、文章热度榜。用ZADD写入分数用ZREVRANGE快速取 Top N。延迟队列和定时任务把 member 的任务执行时间设为 score消费者用ZRANGEBYSCORE拉取已到期任务再通过ZREM完成消费确认。带权重的优先级队列score 表示优先级高优先级的任务先被处理。滑动窗口限流每个请求以当前时间戳为 score 写入 ZSet先删除窗口外的记录再统计窗口内数量实现更精确的限流。高频追问ZSet 底层是怎么实现的ZSet 有两套内部编码。当元素数量少、member 长度短时使用 listpack旧版本为 ziplist紧凑存储数据量大时升级为「跳跃表 哈希表」的组合。跳跃表负责按 score 维护有序性支持范围查询和排行榜哈希表负责根据 member 快速查到对应的 score实现 O(1) 的分值读取。跳跃表的查询、插入、删除平均时间复杂度都是 O(logN)并且天然支持高效范围查询。需要注意ZSet 的 score 是 double 类型在需要精确比较的业务中要留意浮点数精度问题。四、底层数据结构与编码实现很多候选人能熟练使用五大数据类型却说不出它们底层的编码实现。实际上Redis 的性能优势很大程度上来自它的自定义数据结构。这一章我们梳理最核心的几个SDS、dict、skiplist、ziplist、listpack 和 quicklist。4.1 SDS简单动态字符串Redis 没有直接使用 C 语言的传统字符串而是实现了 SDSSimple Dynamic String。它的优化非常实用O(1) 获取长度SDS 内部保存 len 字段获取长度直接读取不需要像 C 字符串那样遍历到结束符。二进制安全SDS 以 len 判断字符串结束不依赖结束符因此可以安全存储图片、序列化对象等二进制数据。杜绝缓冲区溢出拼接前先检查并分配足够空间避免 C 字符串常见的缓冲区溢出问题。减少内存重分配通过空间预分配和惰性释放降低频繁拼接、截断带来的内存操作成本。String 的三种内部编码 int、embstr、raw就是针对不同长度和类型的字符串选择不同存储方式的体现。4.2 dict字典Redis 的 Hash、Set 底层以及全局键空间都用到字典。字典采用哈希表实现哈希冲突用链地址法解决。最值得关注的是它的渐进式 rehash当负载因子过高或过低时Redis 不是一次性搬完所有数据而是把 rehash 分散到后续每次增删改查中逐步完成避免大 key 迁移造成的长时间阻塞。渐进式 rehash 会同时维护新旧两张哈希表查找时先查旧表再查新表直到数据迁移完成、旧表释放。4.3 skiplist跳跃表跳跃表是 ZSet 数据量较大时使用的有序结构。它通过多层索引实现跳着查找平均时间复杂度为 O(logN)并且结构简单、容易实现范围查询。和红黑树相比跳跃表不需要复杂的旋转操作插入删除更直观也更适合区间遍历。4.4 ziplist 与 listpack紧凑的连续内存结构ziplist压缩列表把多个元素放在一块连续内存中没有指针开销内存非常紧凑适合元素少、长度短的场景。但 ziplist 存在连锁更新问题元素越多插入删除效率越差。Redis 7 之后逐渐使用 listpack 替代 ziplistlistpack 在保持紧凑的同时改进了变长编码减少极端情况下的连锁更新。4.5 quicklistList 的最终形态List 早期由 ziplist 和双端链表组合实现Redis 3.2 后统一使用 quicklist。quicklist 本质是一个链表链表的每个节点内又存放一个小型 ziplist相当于「分段紧凑存储」兼顾链表的灵活性和 ziplist 的省内存。4.6 各数据类型编码速查数据类型可选底层编码Stringint、embstr、rawHashlistpack/ziplist、hashtableListquicklistSetintset、hashtableZSetlistpack/ziplist、skiplist dict实战中可以用OBJECT ENCODING key查看某个 key 当前使用的内部编码这是排查内存和性能问题时非常实用的技巧。五、Redis 持久化RDB、AOF 与混合持久化Redis 虽然基于内存但生产环境必须解决重启后的数据恢复问题这就涉及 RDB、AOF 以及混合持久化三种方案。5.1 RDB快照持久化RDB 把某个时间点的内存数据以二进制快照形式写入磁盘可以由save、bgsave触发。核心是 fork 子进程完成写入主进程继续处理请求。RDB 文件紧凑、恢复速度快但两次快照之间的数据可能丢失fork 子进程在数据量较大时也会带来内存和 CPU 开销。5.2 AOF追加写日志AOF 记录的是 Redis 收到的每一条写命令采用追加写方式落盘。通过appendfsync策略控制刷盘频率always 最安全但影响性能everysec 是常用的折中方案。AOF 可以通过重写机制压缩体积。相比 RDBAOF 数据丢失更少但文件通常更大恢复也更慢。5.3 混合持久化Redis 4.0 起支持混合持久化在 AOF 重写时先写入 RDB 快照再追加后续增量 AOF兼顾恢复速度和数据完整性是目前生产环境的主流选择。5.4 RDB 与 AOF 对比维度RDBAOF写入内容某时刻内存快照写命令日志恢复速度快较慢数据安全性可能丢失两次快照之间的数据丢失更少文件体积小大可通过重写压缩六、缓存三大问题穿透、击穿、雪崩除了缓存与数据库的一致性Redis 面试几乎必问这三大问题。它们名字相似但产生的条件完全不同。6.1 缓存穿透请求的数据既不在缓存中也不在数据库中例如恶意请求一个不存在的商品。每次请求都会绕过缓存直达数据库给数据库造成巨大压力。应对方案缓存空值、布隆过滤器、参数合法性校验。布隆过滤器可以在请求进入缓存前先判断 key 是否存在从源头拦截大部分无效请求。6.2 缓存击穿某个热点 key 在过期的一瞬间大量请求同时打到数据库造成瞬时高并发。与穿透不同击穿针对的是某个存在但刚好过期的热点数据。应对方案热点数据设置逻辑过期或不过期、加互斥锁重建缓存、提前异步刷新。最常用的是「互斥锁 查库 回写缓存」。6.3 缓存雪崩大量 key 在同一时间段集中失效或者 Redis 节点整体宕机导致请求大面积落到数据库形成级联压力。应对方案给 key 的过期时间加随机值避免同时失效使用集群和高可用保障缓存服务稳定结合限流、降级和本地缓存兜底。6.4 方案速查问题触发条件核心应对思路缓存穿透缓存和数据库都没有数据空值缓存、布隆过滤器、参数校验缓存击穿热点 key 过期瞬间高并发互斥锁重建、逻辑过期、异步刷新缓存雪崩大量 key 同时失效或缓存宕机过期时间加随机、集群高可用、限流降级七、高可用与集群主从复制、哨兵、Cluster生产环境不可能只跑一个 Redis 实例。数据备份、故障转移和横向扩容分别对应主从复制、哨兵和 Cluster 三种能力。7.1 主从复制主节点负责写从节点通过复制同步数据并对外提供读能力。复制过程包括全量同步和基于缓冲区的增量同步可以提升读吞吐并实现数据冗余。但主从复制本身不能自动完成故障切换。7.2 哨兵 Sentinel哨兵是独立进程负责监控主从节点。当主节点宕机时哨兵会自动执行故障转移选出新主节点并通知客户端。它解决了主节点故障后的顶替问题但不解决数据分片。7.3 Redis ClusterCluster 通过 16384 个哈希槽把数据分散到多个主节点上实现水平扩容。每个主节点可以携带若干从节点保证高可用节点间通过 Gossip 协议交换信息。Cluster 的优点是无中心、易扩展但不支持跨槽的多 key 命令需要客户端支持跳转和其他处理机制。7.4 三者对比方案解决的问题典型特点主从复制数据冗余、读写分离不能自动故障转移哨兵自动故障转移不解决分片Cluster水平扩容与高可用哈希槽分片、跨槽命令受限八、分布式锁从 SETNX 到 Redisson单机环境下用进程级锁就够了但在多实例服务中必须使用分布式锁来保证共享资源的互斥访问。8.1 基础实现最简单可靠的方式是SET key unique_value NX PX 30000。NX 保证只有第一个客户端能设置成功PX 设置过期时间防止死锁value 使用唯一标识以便安全释放锁。8.2 释放锁为什么要校验 value如果拿到锁后直接DEL可能误删其他客户端刚持有的锁。正确做法是用 Lua 脚本先比较 value 是否一致一致才删除保证「检查和删除」的原子性if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end8.3 常见问题锁过期导致误释放业务执行时间超过锁的过期时间时应使用可自动续期的看门狗机制。主从切换丢失锁主节点宕机时从节点可能还没有同步到锁数据。对一致性要求极高的场景可考虑 Redlock 或基于 Zookeeper 的方案。重入性简单 SETNX 方案不可重入生产环境常用 Redisson 封装。实战中一般建议直接基于 Redisson 使用可重入锁、公平锁、读写锁等能力避免自己手写分布式锁出现边界问题。九、过期策略与内存淘汰机制Redis 通过过期删除和内存淘汰两套机制管理有限的内存。这两者经常被混淆面试时务必分清。9.1 过期删除策略Redis 对设置过期的 key 采用「惰性删除 定期删除」结合惰性删除访问某个 key 时先检查是否过期过期则删除。定期删除周期性随机抽检一批 key删除其中已经过期的数据。单独使用任何一种都有明显缺陷二者结合才能在 CPU 开销和内存释放之间取得平衡。9.2 内存淘汰策略当内存达到 maxmemory 上限时Redis 会根据淘汰策略处理新写入请求。常见策略包括noeviction不淘汰写入直接报错。allkeys-lru、allkeys-lfu在所有 key 中按最近最少使用或最不经常使用淘汰。volatile-lru、volatile-lfu只在设置了过期时间的 key 中淘汰。volatile-ttl优先淘汰剩余过期时间最短的 key。常用实践是 volatile-lru 或 allkeys-lru核心思想是尽量保留更可能被再次访问的数据。十、总结与面试速查回到开篇的问题Redis 为什么快答案不是一句「内存存储」那么简单而是内存存取、自定义数据结构、单线程无锁、IO 多路复用以及精细的编码与内存管理共同作用的结果。整篇文章的主线可以概括为从五大数据类型进入 Redis从底层编码理解它的性能来源从持久化、高可用、集群理解它的工程能力从缓存三大问题和分布式锁理解它在业务中的典型应用。面试时如果能把「是什么、为什么、怎么用、有什么坑」四层逻辑讲清楚就已经超过了大多数候选人。最后给三点建议第一所有结论尽量结合真实场景和命令验证第二遇到热点问题不要只背答案多想一想极端情况和边界条件第三把本文当作提纲反复自问直到可以不看稿完整复述整条知识链。