面试问题

什么是缓存击穿、穿透、雪崩?区别是什么?如何解决?

  • 穿透:请求永远不会命中缓存
    • 请求的数据缓存中没有,数据库也没有,每次请求都会打到数据库
    • 查不存在的用户ID,如 ID=-1
    • ① 参数校验 ② 布隆过滤器 ③ 缓存空值
      • 启动时将数据库中所有合法的 ID 加入布隆过滤器;
      • 每次查询前先判断:ID 是否在布隆过滤器中?
      • 如果不在 → 直接返回,不查缓存也不查库;

如果在 → 走正常的缓存查询流程。

  • 击穿:缓存偶尔失效后瞬间打爆后端
    • 某个热点数据突然过期,大量请求同时查询,穿透缓存打爆数据库
    • 秒杀商品库存刚好过期
    • ① 加互斥锁 ② 提前续期 ③ 永不过期+异步更新
  • 雪崩:大量缓存同时失效导致系统崩溃
    • 大量缓存同一时间过期,所有请求打到数据库,系统瞬间崩溃
    • 定时批量缓存设置相同过期时间
    • ① 加随机过期时间 ② 热点预热 ③ 多级缓存

      布隆过滤器是一种概率型数据结构,可以用来判断一个元素一定不存在/可能存在

Redis的过期删除策略?

  • 惰性删除:访问key时,才检查他是否过期,如果过期就删除 | 但可能留下大量垃圾
  • 定期删除:每隔一段时间随机抽取部分 key 检查过期并删除(如每秒检查 20 个)
  • 主动淘汰:当内存不足时触发内存淘汰机制

什么是基于 Redis 构造分布式锁?

  • 本质:利用 Redis 的 SET key value NX EX 原子命令,保证同一时刻只有一个线程/服务获取到锁,从而控制并发访问共享资源(如库存扣减、优惠券发放)。
1
2
// 尝试加锁
SET lock_key my_value NX EX 10
  • NX:只在 key 不存在时设置(互斥)
  • EX 10:自动过期(防死锁)
  • my_value:用 UUID 表示“我是谁”,用于后续解锁校验

为什么用 Lua 脚本做分布式锁?

加锁与解锁需要“判断 + 删除”是一个原子操作。用普通 Redis 命令不能保证原子性(可能判断完刚要删,锁就被别人改了)。
Redis 支持执行一整段 Lua 脚本,这段脚本在 Redis 内部一次性、原子性执行,所以我们用它封装加锁或解锁逻辑,确保线程安全。

1
2
3
4
5
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end

缓存一致性如何实现?

  • 先更新库,再删除缓存,设置延迟双删。

持久化

  • 虽然redis是内存数据库,但是提供了两种方式进行持久化,默认的是RDB,AOF需要自行设置配置开启;

AOF持久化

  • AOF的步骤是,当一条写命令成功执行后,把这条命令追加到一个文件里,然后重启redis的时候,redis先去这个文件里读取命令并执行,就相当于恢复了数据。
    • 避免额外检查开销
    • 不会阻塞当前写操作命令的执行:因为只有命令执行成功后才会被写入

AOF写回策略

  • Always:执行完命令同步将AOF日志数据写回硬盘
  • Everysec:执行完命令后就将写操作写入到缓冲区,然后每隔一秒将缓冲区的数据写入到硬盘
  • No:转由操作系统写

AOF重写机制

  • 随着数据库使用的越来越多,AOF文件的体积变大,就需要重写机制,只会保存最新的一条更新数据
  • 比如使用:set name lcc 然后再使用 set name lccc
  • 更新的时候只会记住后面的lccc,因为之前的命令已经没有意义了。

RDB持久化

  • RDB是快照,每次执行快照,把内存里的所有数据都记录到硬盘里,所以如果频繁执行快照,就会导致Redis的性能下降,如果设置时间过长,然后恰好宕机,就会丢失这一段时间里的所有数据