RedisNotes

第 28 章:面试题精讲:50 个高频问题

zjc 于 2026-01-28 发布

这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 本章按“基础 -> 数据结构 -> 原理 -> 高可用 -> 分布式锁 -> 缓存架构 -> 性能运维 -> 场景设计”组织 50 道高频题。建议先遮住答案自己回答,再对照补充。

28.1 基础篇

Q1. Redis 是什么?有哪些典型用途?

Redis 是基于内存的键值数据库,支持 String、Hash、List、Set、ZSet、Stream 等结构,并提供持久化、复制、哨兵和 Cluster。典型用途包括缓存、会话、计数器、排行榜、分布式锁、限流、轻量队列和实时状态存储。

Q2. Redis 为什么快?

核心原因:数据在内存、高效数据结构、单线程命令执行避免锁竞争、IO 多路复用、简单协议 RESP、渐进式复杂度设计和 pipeline 批量传输。Redis 6 后网络 IO 可以多线程处理,但命令执行仍是主线程串行,保证单个节点上的命令原子性。

Q3. Redis 是单线程吗?

不准确。命令执行通常由主线程完成,但后台线程负责 RDB、AOF 刷盘、异步删除等任务;Redis 6 引入 IO 多线程负责读写网络协议。面试时可以强调“命令执行是单线程,进程不是单线程”。

Q4. Redis 中 DB 是物理隔离吗?

不是。不同 DB 只是同一个实例内的命名空间,共享内存、CPU、持久化、网络和淘汰策略。生产上核心与非核心业务应使用不同实例,而不是只依赖 SELECT

Q5. RESP 是什么?

RESP 是 Redis 序列化协议,客户端与服务端通过简单文本格式交互。RESP2 以 +-:$* 标识简单字符串、错误、整数、批量字符串和数组;RESP3 增加 map、double、bool、push 等类型。

Q6. 缓存和数据库的本质区别是什么?

缓存追求低延迟和吞吐,数据通常可丢、可重建;数据库追求完整性、一致性、事务和审计。缓存可以是数据库前面的投影,但不应替代事实源。

Q7. Redis 支持事务吗?和 MySQL 事务有什么区别?

Redis MULTI/EXEC 能把命令排队并顺序执行,期间不会被其他客户端命令插入,但执行中某条命令失败后不会回滚已执行命令。它更接近批量原子执行,不提供 MySQL 那样的完整隔离级别和回滚语义。

Q8. WATCH 的作用是什么?

WATCH 实现 CAS。事务执行前如果被 watch 的 key 发生变化,EXEC 返回 nil,事务放弃。适合秒杀扣减这类基于版本检查的小并发逻辑,复杂场景通常用 Lua 或数据库条件更新。

Q9. Lua 脚本原子吗?有什么风险?

Lua 脚本在单个 Redis 节点执行期间不会被其他命令插入,因此脚本内多命令原子。风险是脚本慢会阻塞节点,网络分区和主从切换仍可能带来业务层重复执行,脚本必须幂等且执行时间可控。

Q10. Redis 常见阻塞点有哪些?

大 key 读写、KEYSSMEMBERS 全量集合、大范围 ZRANGEHGETALL、复杂 Lua、SINTERSTORE 类聚合命令、AOF fsync 策略、RDB fork、交换内存、网络重传、大量连接和磁盘 IO 抖动。

28.2 数据结构篇

Q11. 五大基础类型的适用场景是什么?

String 存储缓存、计数器、分布式锁;Hash 存对象字段;List 存简单队列和最新列表;Set 存去重和共同关注;ZSet 存排行榜、延迟队列和时间线。

Q12. String 存对象和 Hash 存对象怎么选?

String JSON 适合整体读写、序列化后传输、字段更新少;Hash 适合局部字段更新、字段级 TTL 需求较少、需要读取部分字段。若 value 很大或字段很多,要拆分或压缩。

Q13. SDS 相比 C 字符串有什么优势?

SDS 保存长度,获取长度 O(1);支持动态扩容和空间预分配;二进制安全;可通过空闲空间减少内存重分配;部分类型支持惰性空间释放。

Q14. ZSet 为什么同时使用跳表和字典?

字典支持 member 到 score 的 O(1) 查找;跳表支持按 score 排序和范围查询 O(log N)。两者结合让单点查询和排序查询都快,代价是额外内存。

Q15. 为什么 ZSet 使用跳表而不是红黑树?

跳表实现更简单,范围查询和节点遍历方便,并可通过多层链表实现近似平衡。红黑树也能做到 O(log N),但实现和迭代复杂,Redis 选择了工程上更容易维护的结构。

Q16. List 的底层编码是什么?

旧版本主要使用 ziplist 和 linkedlist;Redis 7 引入 listpack 替代 ziplist 的一部分场景。小列表使用紧凑编码节省内存,变大后转换标准结构。

Q17. Hash 的小对象编码是什么?为什么省内存?

小 Hash 可使用 listpack/ziplist,字段连续存储并保留少量元数据,减少指针和单独分配开销。元素过多或过大后转为 hashtable,避免紧凑结构更新成本过高。

Q18. Bitmap 适合什么场景?

签到、活跃用户、权限位、布尔状态。存储 N 天状态约 N/8 字节。适合稠密整数 ID,如果 ID 稀疏会浪费内存,需要映射或换 Roaring Bitmap。

Q19. HyperLogLog 的原理和误差是多少?

HyperLogLog 通过哈希值前导零的最大值估算基数,标准误差约 0.81%。它内存极小,但不保存元素,也不能取出成员,适合 UV 这类近似去重计数。

Q20. GEO 的底层原理是什么?

Redis GEO 基于 ZSet,将经纬度编码为 52 bit 的 geohash 作为 score。支持范围查询和距离计算,适合附近的人、附近门店等中小规模场景;复杂地理分析可用专业 GIS 或搜索引擎。

28.3 内存与过期删除篇

Q21. Redis 如何删除过期 key?

结合惰性删除和定期删除。访问 key 时检查过期则删除;后台周期性随机采样过期字典并删除。Redis 6 后还支持异步释放内存,避免删除大 key 阻塞主线程。

Q22. 定期删除为什么要限制时间?

过期 key 很多时,如果一直删除会占用 CPU 并阻塞正常命令。Redis 通过采样数量、删除比例和时间上限做自适应,把清理任务摊开执行。

Q23. maxmemory 达到后会发生什么?

写入命令根据 maxmemory-policy 决定是否拒绝或淘汰 key。默认行为可能是 noeviction,写命令报错;也可选 LRU、LFU、random、TTL 相关策略。

Q24. LRU 和 LFU 的区别?

LRU 淘汰最近最少使用,可能被偶发扫描污染;LFU 淘汰访问频率低的 key,更适合长期热点。Redis 使用抽样近似 LRU/LFU,而不是全局精确双向链表。

Q25. allkeys-lru 和 volatile-lru 怎么选?

纯缓存实例可用 allkeys-lru,所有 key 都可淘汰;实例中混有锁、队列、状态等不可丢数据时不能用 allkeys 策略,应拆实例或谨慎设置 volatile-*noeviction

Q26. 什么是内存碎片?如何观察和处理?

碎片来自分配器空洞和大量不同大小对象的创建删除。观察 INFO memorymem_fragmentation_ratio。适度升高可接受,过高可分析大 key 和淘汰,重启或使用 activedefrag 需评估负载和版本。

Q27. 大 key 的危害和处理方式?

大 key 导致网络阻塞、序列化慢、内存集中、删除阻塞、迁移慢。处理方式是拆分结构、压缩、分页、缩短 TTL、使用 SCAN 定位、异步删除和治理业务模型。

Q28. 热点 key 的危害和处理方式?

热点 key 使 Cluster 单分片 CPU、带宽或 QPS 过高。处理方式是本地缓存、key 副本拆分、读写分离、限流、静态化、CDN 和业务错峰。

28.4 持久化与高可用篇

Q29. RDB 和 AOF 的区别?

RDB 是某个时刻的二进制快照,文件小、恢复快,但可能丢失上次快照后的数据;AOF 记录写命令,丢失窗口取决于刷盘策略,文件更大、恢复更慢。Redis 7 使用 multipart AOF,base 和 incr 文件分离。

Q30. AOF 三种刷盘策略是什么?

always 每条命令刷盘,最可靠但性能最低;everysec 每秒刷盘,通常推荐;no 交给操作系统,性能最好但丢失窗口更大。

Q31. AOF 重写为什么存在?

AOF 会记录多次修改同一 key 的历史命令,文件越来越大。重写根据当前内存状态生成等价的最小命令集,降低文件体积和恢复时间。重写由子进程执行,期间新写入进入重写缓冲区。

Q32. 混合持久化是什么?

AOF 重写文件前半部分使用 RDB 格式,之后的增量使用 AOF 命令。恢复速度和数据完整性取得平衡,Redis 7 中通常通过 aof-use-rdb-preamble 控制。

Q33. fork 子进程时为什么会影响主进程?

fork 需要复制页表,实例内存很大时耗时增加。写时复制期间如果页被大量修改,会额外消耗内存,还可能造成 latency 和内存压力。

Q34. 主从复制的流程是什么?

从节点发送 PSYNC;无法增量同步时执行全量同步,主节点 bgsave 生成 RDB 并发送,同时积累写缓冲区;从节点加载 RDB 后继续应用增量命令。断线后可根据 replid 和 backlog 尝试部分重同步。

Q35. replid 和 replication backlog 的作用?

主节点有 replid 和复制偏移量,backlog 保存最近的写命令。从节点携带自己的 replid 和 offset,若仍匹配且 offset 在 backlog 范围内,可只补增量,否则全量同步。

Q36. 哨兵如何判断主节点故障?

单个哨兵 ping 超时认为主观下线;达到 quorum 个哨兵同意后升级为客观下线;哨兵之间选举 leader 执行故障转移。down-after-millisecondsquorum 要根据网络状况设置。

Q37. 哨兵故障转移选择从节点的规则是什么?

会过滤不健康或长时间断连的节点,再根据优先级、复制偏移量和 runid 排序。优先级小者优先;偏移量大者优先;最后比较 runid。执行转移时会升级从节点并让其他从节点复制新主。

Q38. Redis Cluster 如何分片?

共有 16384 个 slot,CRC16(key) mod 16384 计算 key 所属槽。节点负责部分槽,客户端可按 slot 映射直连目标节点。hash tag 可以强制相关 key 落在同一槽。

Q39. MOVED 和 ASK 的区别?

MOVED 表示槽已经归属新节点,客户端应更新 slot 映射;ASK 表示槽正在迁移,当前请求可先向目标节点发送 ASKING 再执行,属于临时重定向。

Q40. Cluster 模式有哪些限制?

多 key 命令、事务和 Lua 通常要求 key 在同一槽;数据库只有 DB0;客户端需要感知集群拓扑;reshard 期间有迁移复杂度;节点太少无法满足高可用副本分布要求。

28.5 分布式锁与缓存篇

Q41. Redis 分布式锁怎么实现?

使用 SET key uniqueValue NX PX ttl 加锁,业务结束后通过 Lua 比较 value 并删除,避免误删别人的锁。执行时间可能超过 TTL 时使用 Redisson watchdog 续期,同时业务必须有幂等兜底。

Q42. 为什么要保存唯一 value?

如果客户端 A 过期后释放锁,客户端 B 已获取锁,A 直接 DEL 会把 B 的锁删掉。Lua 脚本先比较唯一 value 再删除,可以降低误删概率。

Q43. Redlock 适合什么场景?

Redlock 在多个独立 Redis 节点上加锁,多数成功并在有效期内才算成功。它试图减少单实例故障影响,但不能解决时钟跳变、长时间 GC、网络分区下的所有安全性问题。多数业务用单主锁加业务幂等更务实。

Q44. 缓存穿透是什么?如何防护?

请求不存在的数据,缓存和数据库都无法命中。防护包括参数校验、空值缓存、布隆过滤器、风控限流和恶意请求拦截。空值 TTL 应较短。

Q45. 缓存击穿和雪崩的区别?

击穿是单个热点 key 过期瞬间大量请求回源;雪崩是大量 key 同时失效或 Redis 整体不可用导致数据库压力骤增。击穿用互斥加载、逻辑过期和热点不过期;雪崩用 TTL 抖动、多级缓存、限流降级和高可用架构。

Q46. 先更新数据库再删缓存是否保证一致?

不保证强一致,只是出错概率较低的通用方案。仍存在读请求未命中后加载旧值、删除失败、主从延迟等问题。需要 TTL 兜底、失败重试、binlog 失效或版本号校验。

Q47. 延迟双删的适用边界?

延迟双删用于降低“先删缓存后更新库”或读写并发造成旧值回填的概率。延迟时间难以精确选择,且不能保证强一致。适合秒级最终一致业务,不适合账务等强一致场景。

Q48. 如何设计缓存降级?

缓存故障时按业务重要性处理:本地缓存返回旧值、返回静态兜底数据、非核心字段隐藏、核心接口限流排队,数据库回源必须有限流。降级策略要预先配置并演练。

28.6 性能运维与场景设计篇

Q49. Redis 延迟突然升高,如何排查?

按顺序看:慢日志、latency monitor、命令统计、大 key、热点 key、客户端连接和超时、网络重传、CPU、内存和 swap、RDB/AOF 活动、主从复制状态、集群迁移和故障切换记录。不要一上来就重启。

Q50. 设计一个秒杀库存系统,你会怎么做?

分层防护:前端静态化和按钮控制;网关限流和黑名单;应用校验活动状态与资格;Redis Lua 原子完成重复判断和库存扣减;Kafka 异步创建订单;数据库唯一键和条件库存更新兜底;订单超时回补库存;活动后对账 Redis、消息和数据库;全链路监控和降级预案。

28.7 回答框架建议

面试中回答 Redis 问题可以使用四步:

结论:先给出明确答案
原理:说明 Redis 内部机制或时序
边界:指出不适用场景或失败窗口
实践:给出生产上的防护和监控

例如回答持久化时,不要只说“RDB 快、AOF 不丢”,而要补充:RDB 有快照间隔的丢失窗口,AOF everysec 也可能丢一秒;生产要根据 RPO 选择策略,并用备份和故障演练验证恢复。

28.8 本章小结

28.9 思考题

  1. 你所在业务中哪些 Redis 数据是可丢的?哪些是不可丢的?分别如何验证?
  2. 如果面试官追问“Redis 能不能保证分布式锁绝对安全”,你的最终答案是什么?
  3. 如何向架构师解释“缓存强一致很难,但可以做可审计的最终一致”?
  4. 生产实例 CPU 100% 且命令 QPS 不高,你会优先排查哪些原因?
  5. 设计一个 10 万 QPS 的用户资料缓存,你会输出哪些容量、命中率和降级指标?