若缓存数据丢失,有哪些应对方案
缓存数据丢失的几种典型场景:
- 常规失效:缓存数据由于达到设置的过期时间(TTL/TTI)而被正常淘汰。
- 内存淘汰:缓存因内存空间不足,根据淘汰策略(如LRU, LFU)清除了部分数据。
- 服务故障:缓存节点或整个集群发生故障、重启,导致内存中的数据全部丢失。
- 人为或程序错误:错误的操作(如
FLUSHALL)或程序BUG导致数据被误删除。
核心应对方案:旁路缓存
这是最基础也是最核心的应对策略,即经典的“旁路缓存模式”。当缓存数据丢失时,系统能够自动从后端数据源(通常是数据库)恢复数据。
流程:
- 应用请求数据时,首先访问缓存。
- 如果缓存在,则命中(Cache Hit),直接返回数据。
- 如果缓存不在,则未命中(Cache Miss),执行以下步骤: a. 从后端数据库查询数据。 b. 将从数据库查到的数据写回(Rebuild)到缓存中。为了避免新写入的数据马上又被淘汰,可以设置一个合理的过期时间。 c. 将数据返回给应用程序。
这个模式保证了即使缓存丢失,系统也能通过访问数据库来正常工作,并且能自动恢复缓存。但它在高并发场景下会引出更严重的问题,需要进阶方案来解决。
解决高并发下的恢复问题
单纯的Cache-Aside模式在高并发下,如果某个热点数据丢失,会导致大量请求瞬间全部打到数据库上,可能造成数据库崩溃。这种情况我们称之为“缓存击穿”或“缓存雪崩”。
1. 缓存击穿 (Cache Breakdown) - 针对单个热点Key
- 问题:一个非常热门的数据key在某一刻失效,导致海量并发请求同时访问这个key,都未命中,继而全部涌向数据库。
- 应对方案:
- 互斥锁 (Mutex Lock / Distributed Lock):当缓存未命中时,不是所有请求都去查数据库。而是先尝试获取一个锁(如Redis的
SETNX或Zookeeper的分布式锁)。- 只有第一个获取到锁的线程,才有资格去查询数据库并重建缓存。
- 其他线程获取锁失败后,不会去查数据库,而是等待一小段时间后,重新尝试从缓存中获取数据。
- 这样就保证了只有一个请求会落到数据库上,保护了后端。
- 互斥锁 (Mutex Lock / Distributed Lock):当缓存未命中时,不是所有请求都去查数据库。而是先尝试获取一个锁(如Redis的
2. 缓存雪崩 (Cache Avalanche) - 针对大量Key同时失效
- 问题:由于缓存节点宕机,或者大量key设置了完全相同的过期时间,在某一时刻同时失效,导致流量洪峰直接冲击数据库。
- 应对方案:
- 过期时间添加随机值:在基础过期时间上增加一个随机的偏移量(Jitter)。例如,原本10分钟过期的,可以设置为
10 * 60 + random(300)秒。这样可以把key的失效时间点分散开,避免集中失效。 - 搭建高可用缓存集群:使用如Redis Sentinel(哨兵)或Redis Cluster(集群)模式。当主节点宕机时,可以自动进行主备切换,虽然会有短暂的数据丢失(取决于主从同步策略),但能保证服务快速恢复,避免整个缓存服务不可用。
- 数据多级缓存:在应用内部(进程内)使用一层本地缓存(如Caffeine, Guava Cache),再配合远端的分布式缓存。即使远端缓存雪崩,本地缓存也能顶住一部分流量。
- 过期时间添加随机值:在基础过期时间上增加一个随机的偏移量(Jitter)。例如,原本10分钟过期的,可以设置为
3. 缓存穿透 (Cache Penetration) - 针对查询不存在的数据
- 问题:恶意请求或异常情况,查询一个数据库里根本不存在的数据。这会导致每次请求都未命中缓存,每次都去查数据库,并且永远无法重建缓存。
- 应对方案:
- 缓存空对象 (Cache Null Values):当从数据库查询一个不存在的数据时,仍然在缓存中为这个key存入一个特殊的“空值”(比如一个固定的字符串"null"),并设置一个较短的过期时间。这样,后续对该key的查询会直接命中缓存里的“空值”,而不会再访问数据库。
- 布隆过滤器 (Bloom Filter):将所有可能存在的数据key提前存入一个布隆过滤器中。当一个查询请求来时,先去布隆过滤器判断这个key是否存在。如果布隆过滤器判断不存在,就直接拒绝请求,根本不会去查缓存和数据库。这能有效拦截大量非法请求。
事前预防与系统性容错策略
除了被动恢复,更健壮的系统会采取主动的预防和容错措施。
1. 缓存预热 (Cache Pre-warming / Pre-loading)
- 做法:在系统启动时或在流量低谷期,提前将一些可预见的热点数据加载到缓存中。这样可以避免系统启动初期因大量缓存未命中而对数据库造成压力。
2. 数据持久化 (Persistence)
- 做法:开启缓存服务的持久化功能,如Redis的RDB(快照)和AOF(日志追加)。当缓存服务重启时,可以从磁盘上的持久化文件中快速恢复数据,减少冷启动时的数据丢失量。
3. 服务降级与熔断 (Degradation and Circuit Breaking)
- 做法:这是保护系统的最后一道防线。
- 降级:当监测到数据库压力过大或缓存服务大规模故障时,可以临时关闭一些非核心业务的缓存读写,甚至直接返回一些默认值或静态页面,保证核心业务的稳定。
- 熔断:在应用层面集成熔断器(如Hystrix, Sentinel)。当访问数据库的失败率达到一定阈值时,自动熔断,后续请求在一段时间内不再访问数据库,直接返回错误或降级数据,待数据库恢复后再慢慢放开流量。