应用场景

Redis 最常用于缓存和分布式锁,理解这两个场景的核心问题及解决方案是面试重点。

缓存设计 ⭐⭐⭐⭐⭐

缓存架构

Client → 应用服务器 → Redis(缓存) → MySQL(数据库)

      1. 查询缓存
      2. 缓存命中 → 返回
      3. 缓存未命中 → 查 DB → 写缓存 → 返回

缓存穿透 ⭐⭐⭐⭐⭐

问题

查询一个不存在的数据,缓存和数据库都没有,导致每次请求都打到数据库。

请求不存在的 user_id = -1
→ Redis 没有
→ MySQL 没有
→ 大量请求直接打到 MySQL
→ 数据库压力暴增

解决方案

方案1:缓存空值

优点:简单 缺点:占用内存;恶意攻击大量不同 ID 仍会缓存大量空值

方案2:布隆过滤器(推荐)

优点:内存占用小(1亿数据约 12MB) 缺点:有误判率(可能存在实际不存在)


缓存击穿 ⭐⭐⭐⭐⭐

问题

热点 key 过期,瞬间大量请求打到数据库。

解决方案

方案1:互斥锁(推荐)

优点:保证只有一个请求打到数据库 缺点:其他请求需要等待

方案2:热点数据永不过期

优点:不阻塞请求,保证可用性 缺点:短时间内返回旧数据


缓存雪崩 ⭐⭐⭐⭐⭐

问题

大量 key 同时过期Redis 宕机,导致请求全部打到数据库。

解决方案

防止同时过期

高可用架构

  • 主从 + 哨兵:自动故障转移

  • Redis 集群:分片 + 高可用

限流降级

多级缓存


缓存一致性 ⭐⭐⭐⭐⭐

双写一致性问题

更新数据库后,如何保证缓存和数据库一致?

4种更新策略

策略
问题

先更新缓存,再更新数据库

❌ 数据库更新失败,数据不一致

先更新数据库,再更新缓存

❌ 并发时,后完成的请求可能写入旧数据

先删除缓存,再更新数据库

❌ 更新 DB 期间,其他请求可能读到旧数据并写入缓存

先更新数据库,再删除缓存(推荐)

✅ 最佳方案,极小概率不一致

推荐方案:Cache Aside Pattern

延迟双删

解决极端并发场景下的不一致:


分布式锁 ⭐⭐⭐⭐⭐

基本实现

加锁

解锁(Lua 脚本保证原子性):

存在的问题

问题1:锁过期,业务未完成

解决方案自动续期(Redisson 实现)

问题2:主从架构下的锁丢失

Redlock 算法 ⭐⭐⭐⭐

原理:向多个独立的 Redis 实例(N 个,通常 5 个)加锁,超过半数成功才算加锁成功。

优点:解决主从架构的锁丢失问题 缺点:性能开销大,实现复杂

实际建议

  • 普通场景:单 Redis + Redisson

  • 强一致性要求:使用 ZooKeeper 或 etcd


其他应用场景

排行榜(ZSet)

计数器与限流

简单计数器

滑动窗口限流(ZSet):

消息队列(List / Stream)

简单队列(List)

Stream(推荐)


面试要点 ⭐⭐⭐⭐⭐

Q1: 缓存穿透、击穿、雪崩的区别?

  • 穿透:查询不存在的数据,缓存和 DB 都没有(布隆过滤器)

  • 击穿:热点 key 过期,大量请求打到 DB(互斥锁)

  • 雪崩:大量 key 同时过期或 Redis 宕机(随机过期时间 + 高可用)

Q2: 如何保证缓存和数据库一致性?

  • 推荐:先更新数据库,再删除缓存(Cache Aside Pattern)

  • 极端场景:延迟双删

Q3: Redis 分布式锁如何实现?

  • 加锁:SET key uuid NX EX 30

  • 解锁:Lua 脚本判断 uuid,保证原子性

  • 续期:Redisson 看门狗机制

Q4: Redlock 算法的原理?

  • 向 N 个独立 Redis 实例加锁

  • 超过半数成功且耗时 < 过期时间才算成功

  • 解决主从架构下的锁丢失问题

Q5: 布隆过滤器的误判率如何优化?

  • 增加位数组大小

  • 增加哈希函数个数

  • 实际需要权衡内存和误判率

Q6: 为什么不推荐先删缓存再更新数据库?

  • 更新 DB 期间,其他请求可能读到旧数据并写入缓存

  • 导致后续一直读到旧数据,直到缓存过期


参考资料

  1. 书籍推荐:《Redis 深度历险》

Last updated