很多准备面试的朋友一提到 Redis第一反应都是“简单背一背就完事了”。结果真到了现场面试官一句“Redis为什么这么快”就能把人问住再追问一句“那它单线程为什么还能这么快”很多人就开始语无伦次了。其实Redis相关的面试题远不是背概念这么简单它背后是一整套“存储系统设计”的取舍逻辑内存、IO模型、持久化、高可用、一致性每一块都能问得很深。这篇文章我把高频的Redis面试题整理成一条完整的复习线索从数据结构、持久化到缓存三兄弟、高可用架构、分布式锁再到日常运维里常见的大Key、热Key和阻塞问题适合正在准备面试的同学照着过一遍也适合刚接手Redis项目的朋友拿来当自查清单。1. 先搞清楚面试官到底想考什么1.1 一个开场问题带出全部主线Redis为什么快这是几乎所有Redis面试都会遇到的第一题。回答的时候不要把“快”当成一个孤立结论要拆成几层来讲。首先是纯内存操作。Redis把数据放在内存里读写基本就是内存寻址级别这比磁盘上的随机IO快了几个数量级这是“快”的地基。其次是单线程模型配合IO多路复用避免了多线程常见的锁竞争和线程上下文切换开销。很多人不理解“单线程为什么还快”核心就在于Redis把“慢”的操作——网络IO等待——交给内核的多路复用机制去监听主线程只在事件就绪时才去处理CPU不会空转在等待上。再者是底层数据结构专门为高频访问做了优化比如SDS字符串、跳表、哈希表这些在读写路径上都尽量避免不必要的内存拷贝。最后还有渐进式rehash、对象共享这些小优化。如果只回答“因为内存快”这题最多拿一半分。面试官真正想听的是你能不能用一条主线把这些串起来单线程 多路复用 内存访问 精简的数据结构四个点彼此配合才构成了Redsi高性能的完整解释。1.2 把复习内容拆成四个层面别只背答案以我观察到的面试风格Redis问题基本围绕四个层面展开。应用层五种基础类型的用法、适用场景、过期策略怎么设。原理层底层编码、持久化方式、单线程模型到底怎么跑。高可用层主从复制、哨兵、集群分片是怎么协作的。权衡层性能和可靠性怎么取舍分布式锁是否绝对安全缓存一致性要不要强保证。不同年限的候选人侧重点完全不同。如果是一到三年经验面试官主要考应用层和原理层如果是高级岗位一定会在权衡层反复追问比如“你这么解决缓存穿透代价是什么”“分布式锁真的安全吗”。准备面试时别只背“标准答案”每个方案都想一下它的适用边界和缺点这才是让面试官觉得你“用过、想过”的关键。2. 数据结构与底层细节别停留在背类型2.1 五种基础类型的使用场景Redis有五种最基础的类型但面试不是考你把名字说全而是考你能不能根据业务场景讲清楚为什么选它。我整理了一张表基本够应付大多数场景题。类型本质典型场景string字符串或整数缓存、计数器、分布式锁、对象序列化hash字段-值映射存储对象如用户信息支持单独更新某个字段list双向链表或压缩列表消息队列、拉取时间线、最新列表set无序集合去重、标签、交集并集运算zset有序集合排行榜、延迟队列、滑动窗口要特别注意hash和string存对象的区别。hash可以只更新某一个字段不用把整个对象取出来再写回去但如果对象本身很小、更新频率低string配合序列化可能更简单高效。面试里让你“设计一个排行榜”大部分人第一反应是zset但如果面试官追问“分数相同怎么排序”就要能答出zset的排序规则——默认按分数升序分数相同再按字典序。这些细节才是加分点。另外现在面试也会聊到扩展类型比如HyperLogLog做基数统计、GEO做附近的人、Stream做消息队列。不用背太深但至少要知道在什么场景下会用它们因为它们体现了Redis“不止缓存”的定位。2.2 底层编码与SDS、跳表的细节面试到了中间阶段面试官往往会把话题引向底层“string的底层是什么”“zset为什么用跳表不用红黑树”。这些问题需要提前准备。string的底层是SDS简单动态字符串。和C原生字符串比它有几个很关键的优势O(1)获取长度不用遍历不会出现缓冲区溢出因为API会检查剩余空间并自动扩容二进制安全可以存包含空字符的数据减少了修改字符串时的内存重分配次数因为有预分配机制。最好用的面试表达是“SDS就是为Redis的高性能字符串操作服务的它把长度、容量都变成了结构体字段而不是靠字符串结尾符去判断。”zset底层是跳表。面试官几乎必问“为什么用跳表而不是红黑树”。跳表和红黑树的时间复杂度在查找上都接近O(logN)但跳表实现起来简单节点按顺序链接做范围查询特别自然——直接从头节点往后遍历就行红黑树做范围查询需要中序遍历代码复杂度高很多。Redis还需要支持按分值区间快速定位、按排名取元素跳表和哈希表配合恰好能覆盖这些操作。面试时能说出“实现简单、范围查找友好”这两点基本就够。还有两个底层细节也高频出现。一个是压缩列表ziplist和quicklistlist、hash、zset在元素少时会用压缩结构存储省内存元素变多或单个元素变大后才转为更复杂结构。另一个是哈希表的渐进式rehash当哈希冲突多了Redis不会一次性把所有桶重新映射而是分批把老桶的数据往新桶迁移避免大字典扩容时阻塞主线程。3. 持久化机制高频考RDB和AOF3.1 RDB快照是怎么工作的RDB是把内存数据以二进制快照形式写入磁盘。面试要答出两个核心命令save和bgsave。save是主进程直接做快照会阻塞所有请求生产环境基本不用bgsave是fork一个子进程出来做快照父进程继续处理请求。这里的考点是fork和COW写时复制。fork出来的子进程刚开始和父进程共享同一份内存页不需要把整个内存拷贝出来所以fork很快。真正开内存的地方在父进程处理写请求时父进程一旦要修改某个内存页就会把那一页复制一份保证子进程看到的是fork时刻的稳定数据。代价是如果fork之后写入非常频繁父进程复制内存页的开销会很大内存也可能短暂上升。这个问题值得在面试时主动说出来面试官会觉得你理解得比八股文深。RDB的优点是恢复快文件紧凑适合做冷备份和灾难恢复。缺点也很明显它会丢数据因为从上次快照到宕机之间的写操作不在文件里。所以RDB一般配合AOF一起用或者从库上开RDB做备份。3.2 AOF追加日志和重写机制AOF记录的是Redis收到过的每一条写命令以追加方式写入日志。面试重点有三个写回策略、文件膨胀、重写。写回策略通过配置appendfsync控制。always是每条命令都落盘最安全但性能最差everysec是每秒刷一次盘最多丢一秒数据性能和安全相对平衡no让操作系统决定什么时候落盘性能最好但可能丢大量数据。大部分场景用everysec就够了这也是默认配置。AOF最大的问题是文件会越来越大。重写机制解决的就是这个通过读取当前内存状态生成一组能重建这份数据集的最小命令集合而不是在旧文件后面一点点追加。重写过程同样fork子进程来做父进程把重写期间新产生的写命令缓存在一个专门buffer里子进程完成后再追加进去。面试时能说出“重写并不是整理旧AOF文件而是根据当前数据重新生成一个新文件”这句话说明你真的理解重写。3.3 混合持久化和常见追问如果面试官继续追问“重启时到底加载哪个文件”答案是优先AOF。因为AOF一般比RDB保存的数据更完整。新版Redis还支持混合持久化把RDB快照作为AOF文件的开头后面再跟一段AOF增量日志。这样重启后先加载RDB快速恢复再用增量日志补上最近的数据既快又能减少丢失量。还有一个很常见的追问“Redis持久化能保证完全不丢数据吗”答案是不能即使开了AOF everysec极端情况也会丢一秒左右的数据。如果业务要求更严格要么调整策略为always接受性能损失要么从架构层面做双写或手动补偿。面试里承认“做不到完美但可以用多种手段逼近”比拍胸脯说“绝对不丢”要可信得多。4. 缓存穿透、击穿、雪崩必考组合题4.1 三个问题怎么区分我见过太多人把穿透和击穿说反。穿透是请求一个缓存里不存在、数据库里也不存在的key导致每次请求都穿过缓存层直接打到数据库。击穿是缓存里某个热点key正好过期同一时刻大量请求涌进来全部打到数据库。雪崩是大量key一起过期或者缓存节点整体不可用导致数据库瞬间被压垮。问题触发条件核心表现缓存穿透key在缓存和数据库都不存在每次请求都穿透到DB缓存击穿单个热点key过期瞬间大量并发打同一个DB key缓存雪崩大量key同时过期或节点宕机大面积请求打到DB回答的时候建议先画一个场景比如“用户传了一个不存在的商品ID”再展开方案。这样面试官不用猜你脑中的模型你说的每句话都能对上号。4.2 各自的应对手段缓存穿透的经典解法有三个第一把null值也缓存起来设置较短的过期时间防止大量无效请求穿透第二使用布隆过滤器在缓存前先判断key是否可能存在不存在就直接返回第三接口层做基本参数校验明显非法的请求直接拦截。布隆过滤器有误判率它只能保证“不在集合里的肯定不在”但可能把不存在的误判成存在所以并不能完全取代空值缓存。缓存击穿的核心解法是互斥锁。缓存过期后只允许一个线程去数据库回源其它线程等待这个线程把缓存重建好后再读取。可以用Redis的分布式锁实现也可以用本地锁视应用节点数量而定。另外比较实用的是热点key“逻辑过期”方案缓存不设置物理过期时间而是把过期时间存在value里访问时发现逻辑上要过期了再异步去刷新请求返回旧值兜底这样不会在刷新期间出现空窗期。缓存雪崩的应对可以从三个角度展开。缓存角度过期时间加随机值避免同时过期。架构角度加多级缓存本地缓存扛住第一波降低对Redis的依赖。数据库角度做限流、熔断、降级高峰期让数据库压力可控。很多人只答“过期时间加随机”应对单点故障导致的雪崩就没覆盖到所以一定要提到高可用部署和降级方案。5. 过期删除与内存淘汰容易混淆的两个机制5.1 过期键是怎么被删的Redis删除过期key采用的不是单一策略而是“定期删除 惰性删除”组合。惰性删除很好理解每次访问key的时候检查它是否过期过期就删掉再返回空。好处是不用额外占用CPU坏处是如果很多key一直不被访问它们会一直占着内存。定期删除就是为了弥补这一点Redis每隔一段时间随机抽一批设置了过期时间的key把其中过期的删掉。它不是全量扫描而是随机抽查所以即使定期删了仍然可能有少量过期key残留等下次被访问时再惰性删除。这里有个加分知识点主从架构下过期key的删除是主库主导的。主库发现key过期后删除它并把这个删除操作传播给从库从库不会自己主动删过期key。所以从库读可能会读到已经过期但还没收到删除命令的数据在强一致要求下需要考虑这个问题。5.2 内存淘汰策略怎么选当Redis内存用到maxmemory上限时会走淘汰策略。面试的回答重点是几个常用策略noeviction不淘汰任何人写请求会直接报错allkeys-lru从全部key里按最近最少使用淘汰volatile-lru只从设置过期时间的key里淘汰allkeys-random随机淘汰volatile-ttl优先淘汰剩余存活时间最短的key还有LFU相关的策略比如allkeys-lfu按访问频率淘汰。面试里容易被难住的问题有两个。第一个“LRU和LFU有什么区别”LRU看最近一次访问时间LFU看一段时间的访问频率。如果某个key在过去是一个热点但现在已经不热了LRU可能因为它最近刚被访问而保留LFU则会逐渐降权。对业务长期稳定的热点LFU更准。Redis的LRU其实是近似LRU它不会维护全量访问时间链表而是抽样后比较减少内存占用。第二个“如果设置的是noeviction从库会怎么样”这个提醒一下从库的淘汰行为受主库控制主库淘汰key后会向从库发送删除命令从库一般不会自行淘汰。不过实际项目中没人会长期用noeviction除非把Redis当纯缓存以外的重要存储用这时宁可不写也不能丢数据。6. 主从、哨兵、Cluster高可用三件套6.1 主从复制与同步细节主从复制的面试题一般从“slave怎么同步master的数据”开始。全量同步流程是从库发送psync给主库带上上一次的复制ID和偏移量主库发现对不上就返回fullresync并触发bgsave生成RDB快照把快照发给从库从库清空旧数据加载快照主库同时把快照期间新收到的写命令放进复制缓冲区从库加载完RDB后再补发这些增量命令完成对齐。部分重同步是更精细的考点如果从库只是短时间断线重连后不需要再全量同步只要主库的复制积压缓冲区里还存有从库缺失的命令就把这部分增量发过去避免反复传大快照。如果断线时间太长积压缓冲区已经被覆盖那就只能再全量同步。复制是异步的这一点很容易被忽略。主库写完一条命令后立即返回成功不会等从库确认。这样性能高但主从切换时可能丢数据。如果面试官问“主从延迟怎么办”可以从读场景接受轻微延迟、关键操作强制读主库、或者监控复制积压和偏移量这几个方面来答。6.2 哨兵是怎么做故障转移的哨兵解决的是主库挂了怎么自动切换的问题。它有三个职责监控、通知、自动故障转移。面试时重点说清楚两个概念主观下线和客观下线。单个哨兵发现ping主库超时先标记主观下线。为了避免网络抖动误判需要多个哨兵之间达成一致当超过quorum数量的哨兵都认为主库下线时才标记客观下线触发故障转移。这个设计本质上是把“单点判断”变成“分布式投票”降低误判概率。故障转移第一步是选出新主库。选新主的规则一般按优先级、复制偏移量、runid大小排序。随后哨兵会通知所有从库去复制新主再通知客户端更新主库地址。这里有一个值得准备的坑旧主库在断网期间可能还在接收写请求网络恢复后它已经变成新集群里的从库这部分多余的数据会被丢弃也就是脑裂丢数据。生产中可以通过配置min-replicas-to-write让旧主库在从库失联时拒绝写入降低脑裂风险。6.3 Cluster集群和哈希槽Cluster解决的是数据量大到单机撑不住时的水平扩展问题。它的核心是16384个哈希槽每个key通过CRC16算法算出一个数字再对16384取模决定落在哪个槽槽再被分配给不同的节点。所以Cluster存数据时不是一个key对应固定节点而是一个key对应一个槽槽才是迁移的最小单位。有两个细节面试者常忽略。一是客户端访问Cluster时如果请求的key不在当前节点会收到MOVED错误客户端要根据响应里的地址去正确的节点访问如果是槽正在迁移可能收到ASK错误数据迁完前需要临时去目标节点找。二是用hash tag可以把多个key放到同一个槽里从而在集群模式下支持多key操作比如{mall:cart}.{user123}这样的key只有花括号里的内容参与哈希计算。面试官如果问“Cluster和哨兵模式怎么选”答案是看容量和扩展需求。哨兵只是高可用数据都在单主节点上Cluster是分片存储可以把写入压力分摊到多个节点。数据量和写入吞吐已经超过单机能力就上Cluster只是想要自动切换哨兵就够了。7. 分布式锁答得深的人很少7.1 单实例分布式锁的基本实现先答最基础、最常用的一种。用SET key value NX PX 30000加锁NX表示只有当key不存在时才设置成功PX设置过期时间。value要放一个全局唯一标识比如UUID或者业务请求ID目的是释放锁时确认“这把锁是自己加的”。释放锁不能简单用DEL因为存在一个经典问题线程A持有锁代码执行时间超过过期时间导致锁自动释放线程B拿到锁这时A执行完了调用DEL把B的锁误删。解决方式是先用Lua脚本判断value是否等于自己的唯一标识相等才删除保证“判断删除”是一个原子操作。if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end很多人提到SETNX加锁后就结束了其实面试官想听到你考虑过期时间续期、误删旧锁、阻塞太长怎么办。一个经验做法是不要在业务里写死一个很小的过期时间而是用一个后台线程定期给锁续期业务结束后主动释放。Redis官方也有现成客户端组件的看门狗机制原理类似。7.2 分布式锁的边界和Redlock争议如果面试官继续深挖会问“单实例分布式锁挂在机器宕机了怎么办”。这时可以引到Redlock对多个独立Redis节点依次加锁只有大部分节点都加锁成功并且总耗时小于锁有效期才算成功获取锁。它通过多数派来抵抗单节点故障。但要诚实指出Redlock并不是“绝对可靠”。时钟跳跃、进程GC暂停、网络分区都会让多个客户端同时认为自己持锁这个在现实中没办法完全消除。社区对这个方案一直有争议实际工程里更稳妥的做法是能接受极小概率锁冲突的业务单实例锁足够需要更强保证时考虑使用具备共识算法的分布式协调组件而不是自己造Redlock轮子。面试里最忌讳的是把分布式锁说得像“绝对互斥”。只要你能说清楚它“在大多数场景下可用但在极端故障下可能失效所以业务侧要做好防护”面试官会明显认可你的判断力。8. 事务与Lua脚本原子性不等于回滚8.1 MULTI和EXEC的坑点Redis事务用MULTI、EXEC、DISCARD、WATCH控制。MULTI开始后命令不会立刻执行而是进入队列EXEC时一次性按顺序执行。如果队列里某条命令出现语法错误EXEC会直接报错整组命令不会执行但如果是运行时错误比如对一个字符串执行了list操作其他命令依然会照常执行不会回滚。所以面试时不要用“事务就是要么全部成功要么全部失败”这种话来描述Redis事务。它更像“一批命令的批量执行”使用WATCH可以实现乐观锁在MULTI之前WATCH一个key如果该key在EXEC之前被其他客户端修改事务会被取消。适合做类似“先检查再更新”的场景比如防止库存超卖。8.2 Lua脚本的原子执行Lua脚本是Redis开发里避不开的话题。用EVAL执行一段Lua脚本Redis会保证脚本执行期间不会被其他命令插入中间不会发生其他客户端请求的处理。这比事务的“批量执行”语义更实用因为它可以用来完成多个步骤的原子操作比如先判断再扣减、先取后删。很多高并发场景的库存扣减、限流计数都是这么做的。脚本还可以减少网络往返把多个命令放进一次调用。要注意的是脚本里的key必须通过KEYS数组传进去不能直接在脚本字符串里写死key否则集群模式下Redis无法确定这些key分布在哪个槽会直接报错。这也是生产环境踩坑的高频区。面试如果聊到事务和Lua最后可以主动提一句“大部分需要事务语义的场景我更倾向用Lua脚本来实现”因为脚本的原子性更清晰缺点是开发者需要维护脚本代码。9. 日常排查与实战经验临场加分项9.1 大Key问题的排查和治理大Key通常指单个value特别大比如一个string超过几百KB或者一个hash、set里有几十万个元素。危害不只在内存占用更严重的是访问和删除都会造成阻塞。比如用DEL删一个几百万成员的set主线程会长时间卡住大盘直接报警用GET一次拉一个大key网络和内存都压力巨大。排查手段最常用的是redis-cli --bigkeys它会扫描所有key并按类型输出占用靠前的key。生产环境跑这个命令建议放在低峰期因为会有遍历开销。发现大Key之后处理思路有几种拆成多个小key把集合按维度拆分压缩value比如用序列化压缩或者只存必要字段对不常访问的历史数据考虑淘汰或迁移到冷存储。删除大Key还有一个反直觉的技巧不要用DEL要使用UNLINK或者分批删除。UNLINK是异步删除主线程把释放内存的工作交给后台线程不会卡住主线程。如果客户端版本太老不支持UNLINK批量场景可以用SCAN配合逐批删除。9.2 热Key带来的倾斜问题热Key是指某个key被高频访问超过单节点的承载能力导致集群里个节点CPU高、网络打满其他节点却很空闲。这就是数据倾斜和“慢查询”不是一类问题。排查热Key可以借助Redis自带的hotkeys参数或者用MONITOR命令统计高频命令但MONITOR本身有性能开销线上慎用。处理方式常见的有几种本地缓存扛一部分流量把热门数据在应用内存里放一层把同一个key复制成多个带后缀的副本比如加1、2、3后缀分散到不同节点读写分离让读请求分散到从库。热Key问题没有一个通用解核心思路就是“让同一份热点数据能出现在多个地方而不是全部压在同一个节点”。9.3 慢查询和阻塞问题的排查思路面试最后聊到“你遇过Redis什么故障”很多人会说慢查询、阻塞但答得比较泛。可以这样说先用SLOWLOG GET查看慢日志结合slowlog-log-slower-than阈值定位慢命令然后看那些明显会阻塞的命令有没有混到线上比如KEYS *、HGETALL大hash、SORT、FLUSHALL。生产里不应该出现KEYS *应该用SCAN游标遍历。还要关注内存碎片率MEMORY FRAGMENTATION RATIO过高说明大量过期key清理后内存没完全还给系统可以计划重启或用jemalloc配置优化。如果是业务侧的排查我习惯先把问题归类CPU较高就先看热Key和大Value延迟抖动就看慢查询和fork次数连接数暴涨就看客户端连接配置和阻塞命令。把方向定位到具体那一类再开工具细查比盲目使用命令要高效得多这类实战经验在面试中很加分。最后再分享一个我自己准备这类面试的体会单纯背题很容易死在“再深入一点”的追问里。Redis的很多设计其实都在回答两个核心问题——怎么不让主线程被阻塞怎么尽量不丢数据。拿这两个问题当主线去套知识点你会发现回答任何一道题都能自然延伸而不是一个词一个词往外蹦。
阅读完成 · 觉得有帮助?