常见缓存更新策略
| 策略 | 说明 | 一致性 | 维护成本 | 业务场景示例 |
|---|---|---|---|---|
| 内存淘汰 | 利用Redis内存淘汰机制,内存不足自动淘汰数据,下次查询更新缓存 | 差 | 无 | 低一致性需求(如店铺类型查询缓存) |
| 超时剔除 | 给缓存数据设TTL,到期自动删除,下次查询更新缓存 | 一般 | 低 | - |
| 主动更新 | 改数据库同时更新缓存,编写业务逻辑维护 | 好 | 高 | 高一致性需求(如店铺详情查询缓存,可配合超时剔除兜底) |
主动更新策略
主动更新策略主要有以下几种:
- Cache Aside Pattern:缓存调用者在更新数据库同时更新缓存。
- Read/Write Through Pattern:缓存与数据库整合为服务,由服务维护一致性,调用者无需关心缓存一致性。
- Write Behind Caching Pattern:调用者仅操作缓存,其他线程异步将缓存数据持久化到数据库,保证最终一致。
由于后两种的服务维护相对比较复杂,而且第三种会使缓存与数据库不一致,在缓存宕机时数据无法恢复,所以一般选择第一种
操作缓存与数据库需考虑的问题
- 缓存操作选择:
- 更新缓存无效写操作多(不推荐);
- 删除缓存,更新数据库时让缓存失效,查询时再更新(推荐)。
- 操作一致性保障:
- 单体系统可将缓存与数据库操作放一个事务
- 分布式系统利用TCC等分布式事务方案。
- 操作顺序:有两种方式,先删除缓存再操作数据库,或先操作数据库再删除缓存。
操作顺序
- 先删缓存:由于操作数据库是个耗时比较长的操作,所以先删缓存再操作数据库很有可能别的线程会在操作数据库期间发生读操作,此时会把旧数据写入缓存从而导致不一致问题
- 先操作数据库:在操作数据库并删缓存时,有可能别的线程可能会进入到读完数据库旧数据并更新缓存的步骤,此时就会发生数据不一致。但是一般数据库写操作会比读操作耗时更长,所以发生这种情况的概率比较小。为了进一步提升数据一致性,可以给缓存再加上过期时间
缓存更新策略最佳实践总结
- 低一致性需求:用Redis内存淘汰机制,内存不足自动淘汰数据,下次查询更新缓存。
- 高一致性需求:主动更新 + 超时剔除兜底
- 读操作:缓存命中直接返回;未命中查数据库,写入缓存并设超时时间。
- 写操作:先写数据库,再删缓存;确保数据库与缓存操作原子性(如单体用事务、分布式用TCC等) 。
缓存穿透
- 概念:客户端请求的数据在缓存和数据库中都不存在,缓存无法生效,请求全打数据库。
- 解决方案
- 缓存空对象:
- 优点:实现简单、维护方便。
- 缺点:额外内存消耗;可能短期数据不一致。
- 布隆过滤:
- 优点:内存占用少,无多余 key 。
- 缺点:实现复杂;存在误判可能。
- 缓存空对象:
缓存穿透总结
- 产生原因:用户请求的数据在缓存和数据库中均不存在,却持续发起这类请求,给数据库造成巨大压力。
- 解决方案:
- 缓存null值
- 布隆过滤
- 增强ID复杂度,避免被猜测规律
- 做好数据基础格式校验
- 加强用户权限校验
- 做好热点参数限流
缓存雪崩
- 定义:同一时段大量缓存key同时失效或Redis服务宕机,致大量请求打数据库,造成巨大压力。
- 解决方案:
- 给不同Key的TTL加随机值,分散失效时间。
- 用Redis集群提升服务可用性。
- 给缓存业务加降级限流策略,控制流量。
- 为业务添加多级缓存,分担压力。
缓存击穿(热点Key问题)
- 定义:高并发访问、缓存重建复杂的key失效,致大量请求瞬间冲击数据库。
- 解决方案:
- 互斥锁:控制并发,只让一个请求重建缓存,其他等待。
- 逻辑过期:给缓存设逻辑过期时间,过期后异步重建,期间仍返回旧值。
| 方案 | 优点 | 缺点 |
|---|---|---|
| 互斥锁 | 无额外内存消耗;保证一致性;实现简单 | 线程需等待,性能受影响;有死锁风险 |
| 逻辑过期 | 线程无需等待,性能好 | 不保证一致性;有额外内存消耗;实现复杂 |