Redis高配面试题20道

5 阅读10分钟

一、 基础与数据结构

1. 什么是 Redis?它的主要特点是什么?

  • 解答: Redis(Remote Dictionary Server)是一个基于内存的高性能 Key-Value 数据库。

  • 主要特点:

    • 速度快: 数据存在内存中,读写速度极快(单机 QPS 可达 10 万+)。
    • 数据类型丰富: 支持 String、List、Hash、Set、ZSet(有序集合),以及 Bitmaps、HyperLogLog、Geo 等高级结构。
    • 支持持久化: 可以将内存数据异步保存到硬盘中(RDB 和 AOF)。
    • 高可用与分布式: 支持主从复制、哨兵模式和 Cluster 集群,提供高可用方案。

2. Redis 为什么这么快?

  • 解答: 主要有三个原因:

    • 纯内存操作: 避免了磁盘 I/O 的耗时。
    • 单线程模型(核心网络处理): 避免了多线程上下文切换的开销,也不存在多线程竞争导致的锁问题。(注:Redis 6.0 引入了多线程处理网络请求,但核心的命令执行依然是单线程)。
    • I/O 多路复用: 采用了 epoll 机制,允许单线程同时处理多个客户端的网络连接,最大化网络吞吐量。

3. Redis 常用的数据类型及应用场景有哪些?

  • 解答:

    • String(字符串): 最基础的类型。常用于缓存对象、计数器(文章阅读量)、分布式锁。
    • Hash(哈希): 适合存储对象(如用户信息)。比起将整个对象序列化成 String,修改特定字段时性能更好。
    • List(列表): 双向链表。常用于消息队列(非强一致性要求)、最新动态展示(Timeline)。
    • Set(集合): 元素唯一且无序。常用于抽奖系统、共同好友推荐(交集、并集操作)。
    • ZSet(有序集合): 元素唯一且带有权重(Score)。常用于排行榜(如游戏积分榜、微博热搜)。

4. Redis 的单线程模型会有什么问题?有什么改进?

  • 解答: 单线程最大的问题是阻塞。如果执行某个非常耗时的命令(如 KEYS * 或删除超大 Key),会导致整个 Redis 服务卡顿,无法处理其他请求。
  • 改进: 从 Redis 4.0 开始引入了 UNLINK 命令进行异步删除;Redis 6.0 引入了网络 I/O 多线程,将网络读写和解析交由多个线程处理,但命令执行部分仍然保持单线程,以此提升网络并发能力。

二、 内存与持久化

5. Redis 的持久化机制有哪些?

  • 解答: 主要有两种:RDB(快照)和 AOF(追加文件)。

    • RDB: 在指定时间间隔内,将内存中的数据生成快照并保存到磁盘。优点是恢复速度快,文件体积小,适合全量备份;缺点是可能会丢失最后一次快照后的数据。
    • AOF: 将每一个写命令以追加的方式写入日志文件。优点是数据安全性更高(最多丢失一秒数据);缺点是文件体积大,恢复速度比 RDB 慢。Redis 4.0 引入了混合持久化机制,结合了二者的优点。

6. RDB 触发机制是怎样的?会阻塞主线程吗?

  • 解答: RDB 可以通过 SAVEBGSAVE 触发。

    • SAVE:在主线程执行,会严格阻塞 Redis,期间无法处理其他命令,生产环境严禁使用。
    • BGSAVE:主线程会 fork 出一个子进程来完成全量同步,主线程不会阻塞。但 fork 操作本身在数据量极大时可能会造成短暂阻塞。

7. Redis 的内存淘汰策略有哪些?

  • 解答: 当 Redis 内存达到设定的最大值(maxmemory)时,会触发淘汰机制。主要有 8 种,常见的分为两类:

    • 不淘汰: noeviction(默认配置),内存满了直接返回错误。
    • 按 LRU(最近最少使用)淘汰: allkeys-lru(所有键中挑选最少使用的淘汰)、volatile-lru(只在设置了过期时间的键中淘汰)。
    • 按 LFU(最不经常使用)淘汰: allkeys-lfuvolatile-lfu
    • 随机淘汰: allkeys-randomvolatile-random
    • 按过期时间淘汰: volatile-ttl(优先淘汰即将过期的键)。

8. Redis 怎么处理过期键?(过期策略)

  • 解答: Redis 结合了定期删除惰性删除两种策略:

    • 惰性删除: 客户端访问某个 Key 时,Redis 先检查它是否过期,如果过期就删除并返回空。这对 CPU 友好,但如果过期 Key 没被访问,会一直占用内存。
    • 定期删除: Redis 默认每秒执行 10 次,随机抽取部分设置了过期时间的 Key 进行检查并删除已过期的。这能有效释放内存,避免冷数据堆积。

三、 经典缓存问题

9. 什么是缓存穿透?如何解决?

  • 解答: 缓存穿透是指查询一个根本不存在的数据。由于缓存没命中,请求每次都会打到数据库,导致数据库压力剧增。

  • 解决方案:

    • 缓存空对象: 即便数据库返回空,也把这个空值缓存起来(设置较短的过期时间)。
    • 布隆过滤器(Bloom Filter): 在请求到达缓存前,先通过布隆过滤器判断数据是否存在。若判断不存在,则直接返回,不查数据库。

10. 什么是缓存击穿?如何解决?

  • 解答: 缓存击穿是指某一个热点 Key 在缓存过期的瞬间,同时有大量的并发请求打过来,导致数据库瞬间被压垮。

  • 解决方案:

    • 互斥锁(Mutex Key): 只允许一个线程去数据库查询数据并重建缓存,其他线程等待重建完成后再从缓存读取。
    • 逻辑过期: 不给热点 Key 设置物理过期时间,而是在 Value 中存入一个逻辑时间。发现逻辑过期后,异步开启一个线程去更新缓存,当前请求返回旧数据。

11. 什么是缓存雪崩?如何解决?

  • 解答: 缓存雪崩是指在某一个时间段,大批量的 Key 同时过期,或者 Redis 宕机,导致大量请求直接打到数据库,引发数据库崩溃。

  • 解决方案:

    • 过期时间打散: 给不同 Key 的过期时间加上一个随机值,避免集中失效。
    • 高可用架构: 使用 Redis Sentinel 或 Cluster 保证 Redis 集群的高可用。
    • 限流降级: 数据库压力过大时,实施服务降级,牺牲部分用户体验保护核心系统。

四、 并发与一致性

12. 如何保证缓存与数据库双写时的数据一致性?

  • 解答: 最经典的方案是 Cache Aside Pattern(旁路缓存模式)

    • 读: 先读缓存,命中则返回;未命中则查数据库,将数据放入缓存后返回。
    • 写: 先更新数据库,再删除缓存
    • 为什么不是更新缓存? 相比删除,更新缓存的并发控制极难,且会造成无效计算。
    • 极端一致性需求: 如果需要强一致性,可以引入延迟双删策略,或者使用中间件订阅数据库的 Binlog(如 Canal)去异步更新/删除 Redis。

13. Redis 如何实现分布式锁?

  • 解答: 最基本的实现是使用 SET resource_name my_random_value NX PX 30000 命令。

    • NX: 表示不存在才创建(加锁)。
    • PX: 设置过期时间(防止客户端宕机导致死锁)。
    • 解锁: 必须使用 Lua 脚本保证原子性,判断 Value 是自己设置的那个随机值才执行删除(防止误删别人的锁)。
    • 进阶: 在生产环境中,通常推荐使用 Redisson 框架,它内置了看门狗机制(Watchdog) ,可以自动为未执行完业务的锁续期。

14. Redis 事务有什么特性?支持回滚吗?

  • 解答: Redis 通过 MULTIEXECDISCARDWATCH 实现事务。

    • 它将多个命令打包,按顺序在单线程中执行,期间不会被其他客户端打断。
    • 不支持回滚: 这是一个核心考点。如果事务中的某个命令在执行时出错(如对 String 执行 HASH 操作),Redis 会继续执行后续命令,不会回滚之前的操作。Redis 认为错误是编程逻辑引起的,为了追求高性能,放弃了复杂的回滚机制。

五、 高可用与集群架构

15. Redis 主从复制的原理是什么?

  • 解答:

    • 全量复制: 从节点第一次连接主节点时,主节点会执行 bgsave 生成 RDB 文件发送给从节点,从节点加载 RDB。在此期间产生的新写命令会放在缓冲队列中,后续再发给从节点。
    • 增量复制: 断线重连后,主节点只需把断开期间的写命令(根据偏移量 offset 在 repl_backlog_buffer 中查找)发给从节点即可。

16. 什么是 Redis 哨兵(Sentinel)?

  • 解答: 哨兵是 Redis 高可用的解决方案,主要解决主从复制中需要人工介入故障转移的问题。

    • 主要功能: 监控(检查主从是否健康)、自动故障转移(主节点挂了,自动将某个从节点提升为主节点)、通知(将新的主节点信息告诉客户端)。

17. Redis Cluster 集群的原理是什么?

  • 解答: Redis Cluster 是一种去中心化的集群架构,解决了单机内存和并发的瓶颈。

    • 它引入了**哈希槽(Hash Slot)**的概念,共有 16384 个槽。
    • 集群将这些槽平均分配给各个节点。当客户端存取 Key 时,Redis 会根据 CRC16 算法计算出结果再对 16384 取模,找到对应的槽位,从而将请求路由到负责该槽位的节点上。

18. 什么是脑裂(Split-Brain)现象?Redis 如何解决?

  • 解答: 脑裂是指因为网络分区,导致主节点和从节点/哨兵之间断开,哨兵选举出了新主节点,但旧主节点仍能接收客户端请求。网络恢复后,旧主节点变成从节点同步新主节点数据,导致脑裂期间写入旧主节点的数据全部丢失。
  • 解决方案: 调整 min-replicas-to-writemin-replicas-max-lag 参数。要求主节点至少有 N 个从节点,且主从延迟不能超过 M 秒,否则主节点拒绝写入操作,从而有效降低数据丢失的风险。

六、 高级特性与底层原理

19. Redis 中的跳跃表(Skip List)是什么?

  • 解答: 跳跃表是 ZSet(有序集合)的底层数据结构之一。

    • 由于普通的有序链表查询效率低,跳跃表在链表的基础上增加了多级索引(类似于二分查找的思想),通过在上一层索引跨越多个节点,大大提高了查询效率。其时间复杂度与红黑树类似,为 O(log N),但实现比红黑树简单很多。

20. 什么是大 Key(Big Key)问题?如何排查和解决?

  • 解答: 大 Key 不是指 Key 名字长,而是指 Value 过大(如一个 String 大于 10KB,或者一个 Hash/List 包含了上万个元素)。

    • 危害: 读写大 Key 会占用大量带宽和 CPU,导致 Redis 阻塞,甚至引发网络拥塞。
    • 排查: 使用 redis-cli --bigkeys 命令扫描,或者通过分析 RDB 文件。
    • 解决: 拆分数据(比如将一个大 Hash 拆分成多个小 Hash);清理时使用 UNLINK 异步删除,避免主线程卡死。