)
摘要本文整理 Redis 面试中最常被问到的核心知识点覆盖基础概念、五种基本数据结构及底层实现、持久化机制、过期删除与内存淘汰、事务、主从复制、哨兵与集群、缓存穿透击穿雪崩、缓存一致性、分布式锁、常见实战场景与性能优化等内容并以「面试官提问 详细解答」的形式呈现适合校招、社招、基础复习与进阶突击。1. Redis 基础与高频考点1.1 什么是 RedisRedisRemote Dictionary Server是一个基于内存的高性能key-value存储系统通常被称为数据结构服务器。它支持字符串、哈希、列表、集合、有序集合等多种数据结构并提供持久化、主从复制、哨兵、集群、事务、发布订阅、Lua 脚本等能力。由于其数据存放在内存中读写速度非常快单机 QPS 通常可以达到10 万级别因此被广泛用于缓存、排行榜、计数器、分布式锁、消息队列、实时计算等场景。1.2 Redis 为什么这么快这是一个几乎必问的题目可以从以下几点回答基于内存操作数据存储在内存中读写不需要经过磁盘 IO这是速度快的根本原因。高效的数据结构Redis 对每种数据结构都做了专门优化并大量使用哈希表、跳表、压缩列表等高效底层结构使得多数操作的时间复杂度为 O(1) 或 O(log N)。单线程模型传统版本在 Redis 6.0 之前网络 IO 和命令执行都是单线程的避免了多线程上下文切换、锁竞争等开销。单线程配合 IO 多路复用可以高效处理大量并发连接。IO 多路复用使用 epoll 等机制同时监听多个连接一个线程高效处理大量客户端请求。基于事件驱动Redis 内部维护文件事件和时间事件事件分派器统一处理读写、定时任务等减少无效等待。补充Redis 6.0 引入了多线程但它主要是多线程网络 IO用来提升网络读写性能命令的真正执行仍然是单线程因此我们通常说的「Redis 单线程」指的是「命令执行单线程」。1.3 Redis 为什么用单线程单线程有什么优缺点优点不需要考虑线程安全问题避免锁和上下文切换开销。代码更简洁开发和维护成本更低。命令执行具备原子性天然无需担心并发修改同一数据结构。缺点无法充分利用多核 CPU。虽然单实例可用多个 CPU 核处理不同连接但单个耗时命令可能阻塞整个服务。如果出现bigkey的删除、KEYS 全量遍历等耗时操作会阻塞其他请求。为什么单线程还这么快因为 Redis 的瓶颈通常不在 CPU而在内存和网络 IO。普通操作都是纳秒、微秒级别单线程 IO 多路复用已经足够支撑极高并发反而是上下文切换和多线程竞争可能拖慢性能。1.4 Redis 和 Memcached 的区别对比项RedisMemcached数据结构丰富String、Hash、List、Set、ZSet、Stream 等仅支持简单 key-value 字符串持久化支持 RDB、AOF、混合持久化不支持重启数据丢失集群主从复制、哨兵、Cluster 集群客户端一致性哈希线程模型命令执行单线程6.0 起网络 IO 多线程多线程value 大小最大 512MB最大 1MB适用场景复杂业务、排行榜、分布式锁等纯 KV 缓存为主1.5 Redis 常用的数据类型有哪些基础类型String、Hash、List、Set、ZSetSorted Set。扩展类型Bitmap、HyperLogLog、GEO、Stream、BitField。底层还支持 Pub/Sub 发布订阅、Transaction 事务、Lua 脚本等能力。2. Redis 常用数据结构与底层实现2.1 String 字符串使用场景缓存 JSON 对象、计数器、分布式锁、分布式 ID、存储 session 等。常用命令SET、GET、INCR、DECR、SETNX、SETEX、MSET、MGET、APPEND、GETRANGE 等。底层实现Redis 没有直接使用 C 语言传统字符串而是自定义了SDSSimple Dynamic String简单动态字符串。SDS 相比 C 字符串的优点常数复杂度获取长度SDS 通过 len 字段记录长度获取长度是 O(1)而 C 字符串需要遍历 O(N)。杜绝缓冲区溢出修改前会先检查空间是否足够不够则自动扩容。减少内存重分配次数通过空间预分配和惰性空间释放优化性能。二进制安全C 字符串遇到空字符会截断SDS 使用 len 判断长度可存储图片、音频等二进制数据。2.2 Hash 哈希使用场景存储对象如用户信息、购物车、商品属性等。常用命令HSET、HGET、HMSET、HMGET、HGETALL、HINCRBY、HDEL、HLEN 等。底层实现Hash 的底层有两种结构ziplist/listpack元素较少、单个值较小时使用节省内存。hashtable字典元素较多或值较大时转为哈希表。在 Redis 7.0 之前使用 ziplist7.0 之后默认使用listpack替代 ziplist因为 listpack 在连锁更新等场景更安全、性能更好。2.3 List 列表使用场景消息队列配合 BRPOP 阻塞消费、最新消息列表、时间线等。常用命令LPUSH、RPUSH、LPOP、RPOP、LRANGE、LTRIM、BRPOP、BLPOP 等。底层实现List 在 3.2 版本之后统一使用quicklistquicklist 是双向链表 ziplist或 listpack的组合体链表节点之间通过指针连接支持两端快速插入删除。每个链表节点中存放的是一个压缩列表减少内存碎片和指针开销。2.4 Set 集合使用场景去重、共同好友、标签、抽奖等支持交集、并集、差集运算。常用命令SADD、SREM、SISMEMBER、SMEMBERS、SCARD、SINTER、SUNION、SDIFF、SPOP 等。底层实现元素为整数且数量不多时使用intset整数集合。否则使用hashtable字典。2.5 ZSet 有序集合使用场景排行榜、带权重的队列、延迟队列、按时间排序的 feed 流等。常用命令ZADD、ZREM、ZSCORE、ZRANGE、ZREVRANGE、ZRANGEBYSCORE、ZINCRBY、ZRANK 等。底层实现ZSet 需要同时满足「按分数排序」和「按成员快速查找」因此内部通常由字典 跳表skiplist组成字典保存成员 → 分值的映射O(1) 获取分值。跳表按分值排序支持 O(log N) 的范围查询和排名查询。元素较少时也会使用 ziplist/listpack 节省内存。2.6 跳表skiplist是什么为什么不用红黑树跳表是一种多层有序链表结构通过在链表上增加多级索引实现类似二分查找的效果查询、插入、删除的平均时间复杂度为O(log N)。Redis 之所以使用跳表而不是红黑树主要原因是跳表实现更简单易于维护。跳表天然支持范围查询找到起始节点后顺序遍历即可。跳表可以方便地支持按排名查找每个节点维护跨度 span 即可计算 rank。插入删除只需要修改相邻节点指针不需要像红黑树那样频繁旋转和染色。2.7 字典的渐进式 rehashRedis 的哈希表在负载因子过高或过低时会触发扩容或缩容。为了避免一次性 rehash 导致服务阻塞Redis 采用渐进式 rehash为字典同时维护ht[0] 和 ht[1]两张哈希表。扩容时分配新的 ht[1]然后在每次对字典进行增删改查操作时顺带把 ht[0] 中的若干个旧桶迁移到 ht[1]。同时定时任务也会逐步推进 rehash 进度。迁移期间查询会先查 ht[0]再查 ht[1]。这样就把一次性的 O(N) 操作分摊到了多次 O(1) 操作中避免主线程卡顿。3. Redis 持久化机制3.1 Redis 有哪几种持久化方式主要有RDB、AOF以及RDB AOF 混合持久化三种方式。3.2 RDB 持久化RDBRedis Database是在某个时间点生成数据的快照保存到磁盘上的二进制文件。触发方式手动触发SAVE阻塞主线程和 BGSAVEfork 子进程异步保存。自动触发在配置文件中通过 save 规则触发如save 900 1表示 900 秒内至少 1 次修改就触发 BGSAVE。优点文件紧凑适合做灾备和全量恢复。恢复速度快直接把二进制文件加载进内存即可。BGSAVE 由子进程完成主进程几乎不受影响。缺点数据安全性较低两次快照之间宕机会丢失最新数据。fork 子进程时如果数据量很大可能导致短暂阻塞且消耗额外内存。原理BGSAVE 使用fork创建子进程借助操作系统的写时复制Copy On WriteCOW机制让子进程和父进程共享同一份内存页。父进程继续处理写请求时被修改的内存页会被复制一份子进程读取的还是旧版本数据从而实现一致性快照。3.3 AOF 持久化AOFAppend Only File通过追加写入的方式把每一条写命令记录到日志文件中。Redis 重启时通过回放 AOF 文件中的命令来恢复数据。写回策略 appendfsyncalways每次写命令都立即同步到磁盘数据最安全但性能最差。everysec每秒同步一次最多丢失 1 秒数据性能和数据安全性平衡默认策略。no由操作系统决定何时刷盘性能最好但可能丢失较多数据。AOF 重写rewrite随着命令不断追加AOF 文件会越来越大。Redis 会对 AOF 进行重写将多条冗余命令合并为最少命令集合例如把多次 INCR 合并成一次 SET。重写由子进程完成不会阻塞主进程。优点数据更安全日志可读性好。缺点AOF 文件通常比 RDB 大恢复速度相对较慢。3.4 RDB AOF 混合持久化Redis 4.0 开始支持混合持久化。AOF 重写时先写入一份当前时刻的 RDB 快照再追加此后发生的写命令。这样既保持了 AOF 的数据安全性又兼顾了 RDB 的快速恢复能力。混合持久化文件的前半部分是 RDB 格式后半部分是 AOF 格式加载时 Redis 会自动识别。3.5 RDB 和 AOF 如何选择对数据安全性要求较高可接受一定性能开销优先选择 AOF或开启混合持久化。主要用于缓存允许少量数据丢失可以使用 RDB性能更好、文件更小。生产环境常见做法同时开启 RDB 和 AOF用 RDB 做冷备用 AOF 保证低丢失并开启混合持久化提升恢复速度。4. Redis 过期删除与内存淘汰4.1 Redis 如何删除过期键Redis 采用惰性删除 定期删除结合的策略惰性删除当客户端访问某个 key 时先检查是否过期过期则删除并返回空。这种方式的优点是节省 CPU但如果 key 一直无人访问就会一直占用内存。定期删除Redis 每隔一段时间默认每 100ms随机抽取一部分设置了过期时间的 key 进行检查删除已过期的 key。通过随机抽样控制单次检查的 CPU 和耗时避免一次性扫描所有 key 阻塞服务。这两者结合可以平衡 CPU 和内存开销。4.2 过期键处理有哪些策略常见的过期键删除策略有三种定时删除给每个 key 设置定时器到期立即删除。对内存最友好但每个 key 一个定时器CPU 开销大。惰性删除访问时才检查删除。对 CPU 友好但可能积累大量过期 key 占用内存。定期删除每隔一段时间扫描删除。是两者折中方案。Redis 选择的是惰性删除 定期删除。4.3 Redis 的内存淘汰策略有哪些当 Redis 内存使用达到maxmemory上限时会根据淘汰策略删除部分数据。Redis 4.0 之后主要有以下 8 种策略说明noeviction默认策略不删除任何数据内存不足时新的写入会报错。allkeys-lru从所有 key 中淘汰最近最少使用的键。volatile-lru从设置了过期时间的 key 中淘汰最近最少使用的键。allkeys-random从所有 key 中随机淘汰键。volatile-random从设置了过期时间的 key 中随机淘汰键。volatile-ttl从设置了过期时间的 key 中淘汰剩余 TTL 最短的键。allkeys-lfu从所有 key 中淘汰最不经常使用的键Redis 4.0 引入。volatile-lfu从设置了过期时间的 key 中淘汰最不经常使用的键Redis 4.0 引入。选择建议纯缓存场景通常选择allkeys-lru如果担心冷数据被误淘汰可以选择allkeys-lfu。需要注意淘汰策略只有在内存达到maxmemory上限后才会触发。5. Redis 事务5.1 Redis 事务是什么如何使用Redis 事务通过MULTI、EXEC、DISCARD、WATCH等命令实现。MULTI开启事务。事务开启后的命令会进入队列不会立即执行。EXEC一次性、按顺序执行队列中的命令。DISCARD取消事务。WATCH监听一个或多个 key执行 EXEC 前如果被监听 key 发生修改事务会中止。5.2 Redis 事务支持回滚吗Redis 事务不支持传统关系型数据库那样的回滚。执行 EXEC 时如果某条命令执行出错已经执行的命令不会撤销后续命令还会继续执行。只有语法错误会在入队阶段被发现此时整个事务会被拒绝执行。Redis 认为编程错误应在开发阶段被发现因此不提供运行时回滚。5.3 Redis 事务的原子性如何理解Redis 事务的原子性体现在命令会被顺序地、不被其他客户端命令打断地执行。但事务不会回滚出错的命令所以不是通常意义上“要么全成功、要么全失败”。6. Redis 主从复制6.1 什么是主从复制主从复制是指将一个 Redis 主节点的数据异步复制到一个或多个从节点从节点默认只读。可以通过replicaof旧版本为slaveof配置主从关系。6.2 为什么需要主从复制读写分离主节点负责写从节点分担读请求。数据冗余和高可用主节点故障后可从从节点恢复数据。容灾备份从节点可作为数据副本。6.3 主从复制的原理主要分为三个阶段全量复制从节点连接主节点后首次同步时主节点执行 BGSAVE 生成 RDB 快照发送给从节点从节点清空旧数据并加载快照。增量复制全量复制完成后主节点将后续写命令持续发送给从节点从节点逐个执行。命令传播主节点将每一条写命令同步给从节点保持数据一致。Redis 通过repl_backlog_buffer缓存最近一段时间内的写命令以便断线后能进行部分重同步避免每次断线都触发全量复制。6.4 主从延迟问题主从复制是异步的从节点数据通常落后主节点。若对一致性要求高读取时应读主节点或借助WAIT命令等待从节点完成同步。7. Redis 哨兵7.1 哨兵是什么Sentinel哨兵是 Redis 官方提供的高可用方案用于监控主从节点、在主节点故障时自动完成故障转移并通知客户端新的主节点地址。7.2 哨兵的核心能力监控持续检测主从节点是否正常。通知把节点状态变化通知给客户端或其他程序。自动故障转移主节点宕机后从从节点中选举新主节点并让其他从节点复制新主节点。配置提供者客户端可以询问哨兵获取当前主节点地址。7.3 主观下线与客观下线主观下线SDOWN单个哨兵认为某个节点不可达。客观下线ODOWN超过指定数量的哨兵都认为主节点不可达。只有当主节点被判定为客观下线后才会启动故障转移。7.4 故障转移流程多个哨兵就“主节点已下线”达成一致。哨兵之间选举一个 Leader 哨兵负责故障转移。Leader 哨兵从健康的从节点中按优先级、复制偏移量等条件选出一个新主节点。让旧主节点的其他从节点改为复制新主节点。更新客户端配置旧主节点恢复后降级为从节点。8. Redis 集群8.1 为什么需要集群单实例内存、写入能力和连接数都有限。Redis Cluster 通过分片把数据分布到多台节点实现水平扩展同时提供自动故障转移能力。8.2 Cluster 的数据分片原理哈希槽Redis Cluster 将 key 空间划分为16384 个哈希槽hash slot。每个主节点负责一部分槽位。key 路由时使用CRC16(key) mod 16384计算槽号再找到对应节点。8.3 Cluster 的故障转移每个主节点都可以配置一个或多个从节点。当主节点不可用后其从节点会被选举为新主节点继续服务该主节点负责的槽位。8.4 集群的限制客户端需要支持 Redis Cluster 协议正确处理 MOVED、ASK 重定向。跨槽的多 key 操作通常不支持需要设计时避免把关联数据分散到不同槽。批量操作如KEYS、FLUSHALL等命令在集群中语义不同。9. 缓存穿透、击穿、雪崩9.1 缓存穿透概念请求的数据在缓存和数据库中都不存在缓存无法命中导致大量请求直接打到数据库。解决对不存在的数据也缓存空值并设置较短过期时间。使用布隆过滤器提前判断 key 是否可能存在。做好接口参数校验过滤非法请求。9.2 缓存击穿概念某个热点 key 在缓存中过期大量请求同时访问该 key瞬间打到数据库数据库压力骤增。解决使用互斥锁查询数据库前先尝试获取锁只有拿到锁的请求才回源查库并重建缓存。采用“逻辑过期”策略热点 key 逻辑上不过期由后台异步刷新。9.3 缓存雪崩概念大量缓存 key 在同一时间集中过期或者 Redis 服务本身宕机导致大量请求直接打到数据库。解决给 key 的过期时间增加随机偏移避免集中过期。对热点数据设置逻辑过期不设置固定过期时间。提升 Redis 高可用性使用主从、哨兵或集群。执行服务降级、限流保护数据库。9.4 三者区别穿透数据本身不存在。击穿单个热点 key 过期。雪崩大量 key 集中过期或 Redis 宕机。10. 缓存与数据库一致性10.1 什么是缓存一致性缓存与数据库一致性是指在更新数据库后如何保证缓存中的数据不会被读成旧值。常见方案是 Cache Aside旁路缓存模式。10.2 Cache Aside 模式读先读缓存命中直接返回未命中则回源数据库查到后回填缓存。写先更新数据库再删除缓存等待下次读请求重新加载。也可以改为“先删除缓存再更新数据库”但可能产生缓存脏数据问题。10.3 双写一致性常见策略先更新数据库再删除缓存最常用结合延迟双删进一步降低不一致概率。先删除缓存再更新数据库适合读多写少、一致性要求不高的场景但存在缓存击穿风险。延迟双删先删缓存再更新数据库延迟一段时间后再删一次缓存。基于 Binlog 异步更新通过 Canal 等工具订阅数据库变更异步删除或更新缓存解耦业务代码。真正需要强一致时应在业务层引入分布式锁或直接读数据库。11. Redis 分布式锁11.1 实现思路Redis 实现分布式锁的核心是SET key value NX PX millisecondsNX只有 key 不存在时才写入保证互斥。PX设置过期时间避免持锁进程崩溃后死锁。value写入唯一标识如 UUID 线程号释放时校验。11.2 安全释放锁释放锁前必须校验 value 是否属于当前请求校验和删除要在 Lua 脚本中原子完成避免误删他人的锁if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end11.3 RedLock 和常见问题单节点 Redis 可能因宕机导致锁失效RedLock 主张在多个独立 Redis 节点上分别加锁多数节点加锁成功才算获得锁。实践中RedLock 对时钟依赖强、实现复杂通常更注重锁过期时间设置和业务幂等。防止锁误删使用唯一 value 校验。防止自动过期锁持有时间要覆盖业务执行时间或使用续期机制。可重入问题简单 setnx 不支持可重入需要结合 Hash 或多个锁信息实现。12. 常见实战场景与性能优化12.1 典型应用场景缓存热点数据缓存、对象缓存、接口结果缓存。计数器使用 INCR、DECR 实现文章阅读量、库存计数。排行榜使用 ZSet 实现实时排行榜。分布式锁保证分布式场景下的互斥与幂等。消息队列使用 List、Stream 实现简单队列。限流基于 key 过期时间和计数器实现接口限流。12.2 常见性能优化手段避免 bigkey单个 key 过大会消耗更多内存和网络带宽也容易阻塞 Redis。应拆分大对象避免整存大 list、hash。避免 hotkey热点 key 访问集中可用本地缓存、多副本分摊。使用 Pipeline批量命令减少网络 RTT。使用连接池复用连接避免频繁创建连接。禁用危险命令生产环境禁用KEYS使用SCAN。合理设置过期时间避免内存过度膨胀。根据场景选择合适数据类型能存 Hash 就不存多个 String节省内存。13. 总结Redis 面试题通常围绕“快在哪里、底层数据结构是什么、如何持久化、如何保证高可用、如何应对大流量缓存问题”展开。复习时建议先建立整体框架再逐章复习主要机制并结合真实业务场景说明方案取舍这样回答会更有深度。