# 54. 如何设计 Redis 缓存失效与更新机制,避免过期缓存引发问题?

# 标准答案

设计 Redis 缓存失效与更新机制时,关键目标是确保数据一致性并避免缓存穿透、缓存击穿和缓存雪崩等问题。常见策略包括设置合理的缓存过期时间、采用定时更新或延迟双删策略、使用版本号或标记来控制缓存更新、以及合理的缓存淘汰策略。此外,可以结合应用层的缓存更新逻辑(如写入更新缓存、定期清理缓存等)来避免缓存失效带来的问题。

# 答案解析

# 1️⃣ 缓存失效与过期引发的问题

在实际应用中,缓存失效是一个常见问题,通常带来以下几种影响:

  • 缓存穿透:如果缓存数据不存在,系统会直接查询数据库,导致过多的数据库查询请求。尤其是在数据量较大的场景下,频繁的数据库访问会大大增加数据库压力。

  • 缓存击穿:当一个缓存项的过期时间相同,多个请求在缓存失效的瞬间同时请求数据库,会导致高并发的数据库访问,造成数据库压力骤增。

  • 缓存雪崩:如果多个缓存项在相同时间失效或被清理,导致大量的请求打到数据库上,会形成大规模的数据库压力,甚至可能引发系统崩溃。

# 2️⃣ 设计缓存失效与更新机制

# 2.1 设置合理的过期时间与策略

为了避免缓存失效带来的问题,可以根据不同业务场景设计合理的缓存过期时间,防止缓存大量数据在同一时间失效。

  • 过期时间随机化:为了避免缓存击穿和雪崩,可以为不同的缓存项设置不同的过期时间,或者在设定缓存过期时间时,加入随机的延迟(例如,在 1 小时基础上随机加上 1 分钟到 10 分钟的时间)。这样能避免多个缓存同时过期的情况,分散请求压力。

  • 合理的 TTL(Time-to-Live)设置:根据业务场景的特点,合理设置缓存的 TTL 值。例如,对于频繁变动的数据可以设置较短的过期时间,而对于不常更新的数据可以设置较长的 TTL,避免频繁失效。

# 2.2 采用双重删除策略(延迟双删策略)

当更新缓存时,如果缓存已经失效,直接查询数据库获取数据并重新缓存是常见的做法。但这容易导致缓存失效后高并发访问数据库时出现缓存和数据库不一致的情况,采用双重删除策略可以有效避免这一问题。

  • 延迟双删:在更新缓存时,可以执行两次删除操作。第一次删除缓存,更新数据库后重新设置缓存,第二次删除缓存的目的在于确保数据更新后再清理失效缓存。这样即使第一次删除缓存时缓存数据还没完全更新,也能在第二次删除时再次触发数据更新,从而减少缓存失效期间可能的错误读取。

# 2.3 使用版本号或者标记来控制缓存更新

可以在缓存的数据中加入版本号或标记,当缓存过期时,通过版本号来判断缓存是否需要更新。具体做法如下:

  • 版本号控制:在每个缓存的值中添加一个版本号或时间戳,读取缓存时检查该版本号是否为最新。如果版本号不一致或缓存过期,重新从数据库加载数据并更新缓存。

  • 标记位控制:设置标记位来标记数据是否过期或是否正在被更新。例如,设立“正在更新”标志,防止多个请求在缓存失效时同时访问数据库。这样可以减少数据库的重复访问。

# 2.4 采用后台异步更新缓存

对于频繁变动的数据,不建议直接通过请求获取缓存的方式来更新缓存。可以采用异步更新机制,在数据更新后,通过后台定时任务或者事件驱动的方式更新缓存。

  • 异步更新:通过应用层的异步任务框架(如消息队列、定时任务等)定期清理缓存或更新缓存数据。通过这种方式,可以将缓存更新与业务请求的实时性隔离,减少因缓存失效而对数据库的直接压力。

  • 异步预热:对于大规模的缓存数据,采用缓存预热机制,即在系统启动时或通过定时任务预先加载重要数据到缓存中,减少缓存失效时的性能损失。

# 2.5 缓存穿透防范

为了防止缓存穿透(请求的数据在缓存中不存在,直接查询数据库),可以采取以下防护措施:

  • 空值缓存:当查询不到有效数据时,可以将返回结果为空的数据(如 null)缓存一定时间。这样即使某个缓存项不存在,也不会对数据库造成过多的请求。

  • 布隆过滤器:布隆过滤器是一种空间效率高、判断数据是否存在的算法,适用于防止缓存穿透。当查询的数据在布隆过滤器中不存在时,直接返回空值,避免查询数据库。

# 2.6 采用合理的缓存淘汰策略

当 Redis 的内存达到上限时,需要通过缓存淘汰策略清理不常访问的缓存数据。常见的淘汰策略包括:

  • LRU(Least Recently Used):淘汰最近最少使用的数据。
  • LFU(Least Frequently Used):淘汰使用频率最低的数据。
  • TTL 淘汰:直接根据缓存项的 TTL 过期时间进行淘汰。

通过这些策略,确保缓存中的热点数据不会因为内存不足被删除,同时减少缓存击穿和雪崩的风险。

# 深入追问

🔹 在高并发场景下,如何保证 Redis 缓存失效与更新机制的高效性和准确性?
🔹 缓存更新时,如何避免由于网络延迟或故障引发的不一致性问题?
🔹 在使用布隆过滤器防止缓存穿透时,如何权衡性能与内存开销?

# 相关面试题

🔹 如何设计 Redis 缓存更新机制来避免缓存击穿?
🔹 Redis 缓存雪崩的原因和解决方案有哪些?
🔹 Redis 缓存穿透的解决方案及其实现方式?

# 总结

  1. 避免缓存失效引发的问题:通过合理的过期时间设计、双重删除策略、版本号控制等方式,确保缓存失效时能顺利更新和避免数据库压力。
  2. 缓存更新机制:结合异步更新、后台定时任务等机制,减少对数据库的直接压力,并通过缓存预热、缓存穿透防护等手段,确保系统稳定性。
  3. 高并发优化:在高并发场景下,合理的缓存更新与失效设计是保证性能和数据一致性的关键。