面试题答案
一键面试读写策略
- 先更新数据库,再删除缓存:写操作时先更新数据库,成功后再删除 Redis 缓存。读操作时,先从 Redis 读取,若不存在则从数据库读取并回写 Redis。这样能保证在写操作后,下次读操作会从数据库获取最新数据并更新缓存。但在并发场景下可能存在短暂不一致,如在更新数据库和删除缓存之间有读请求,会读到旧缓存数据。
- 先删除缓存,再更新数据库:写操作先删除缓存,再更新数据库。读操作与上述类似。这种策略同样存在并发问题,如删除缓存后,还未更新数据库时,有读请求,会将旧数据重新写入缓存。可以通过设置缓存过期时间来降低不一致的时间窗口。
避免缓存穿透
- 布隆过滤器:在查询 Redis 前,先通过布隆过滤器判断数据是否存在。布隆过滤器能高效判断一个元素是否在集合中,若不存在则直接返回,不再查询数据库,减少数据库压力。但布隆过滤器存在误判率,即可能将不存在的数据误判为存在,不过误判率可通过调整参数控制。
- 空值缓存:当查询数据库发现数据不存在时,也将空值写入 Redis 缓存,并设置较短过期时间。下次查询同样数据时,直接从缓存获取空值,避免重复查询数据库。
避免缓存雪崩
- 设置不同过期时间:对 Redis 中的缓存数据设置不同的过期时间,避免大量缓存同时过期。可以在原有过期时间基础上,增加一个随机值,让过期时间分散开来。
- 使用熔断降级:当 Redis 大量请求失败,达到一定阈值时,开启熔断机制,直接返回兜底数据或错误提示,避免大量请求压垮数据库。同时记录相关日志,方便后续排查问题。
- 搭建多级缓存:可以采用本地缓存(如 Guava Cache)与 Redis 结合的方式。本地缓存作为一级缓存,先从本地缓存读取,若不存在再从 Redis 读取。这样在 Redis 出现问题时,本地缓存仍能提供部分数据服务,减轻数据库压力。
避免缓存击穿
- 互斥锁:在查询 Redis 未命中时,使用互斥锁(如 Redis 的 SETNX 命令实现),只允许一个线程去查询数据库并回写缓存,其他线程等待。获取锁的线程查询数据库并更新缓存后,释放锁,其他线程再从缓存获取数据。这种方式可能会导致大量线程等待,影响性能。
- 热点数据永不过期:对于热点数据不设置过期时间,保证一直从缓存获取数据。同时后台开启异步线程定时更新数据,或者在数据变化时主动更新缓存,确保数据一致性。