【Java面试】你是怎么控制缓存的更新?
🔄 一、数据实时同步失效(强一致性)
原理:数据库变更后立即失效或更新缓存,保证数据强一致。
实现方式:
-
Cache Aside(旁路缓存):
- 读流程:读缓存 → 未命中则读库 → 回填缓存。
- 写流程:更新数据库 → 删除缓存(非更新)。
- 关键优化:
- 延迟双删:更新DB后删除缓存,延迟500ms再删一次,防止并发读导致的脏数据。
- 加锁防击穿:缓存未命中时,用分布式锁(如Redis
SETNX
)控制单线程读库回填。
- 适用场景:金融交易、库存扣减等强一致性场景。
-
Read/Write Through(读写穿透):
- 应用只操作缓存,由缓存服务同步更新数据库(如Ehcache + DB集成)。
- 优点:业务无侵入;缺点:写延迟高,依赖缓存服务可靠性。
⏱️ 二、数据准实时更新(最终一致性)
原理:数据库变更后异步更新缓存,延迟通常在毫秒~秒级。
实现方式:
-
消息队列解耦:
- 更新数据库 → 发MQ(如Kafka) → 消费者异步更新缓存。
- 关键设计:
- 消息顺序性:同一数据的更新需保证顺序消费(如Kafka分区键用数据ID)。
- 幂等处理:消费端校验版本号或唯一ID,防重复更新。
-
Binlog监听:
- 工具监听数据库Binlog(如Canal) → 解析变更事件 → 更新缓存。
- 优点:与业务解耦,适合多服务协作场景。
适用场景:电商商品详情页、社交动态等可容忍短暂不一致的场景。
⏰ 三、任务调度更新(最终一致性)
原理:定时任务批量同步数据库与缓存,延迟分钟~小时级。
实现方式:
-
增量同步:
- 任务扫描DB变更记录(如
update_time
字段),仅同步增量数据。 - 优化:分页查询 + 时间窗口滚动,避免全表扫描。
- 任务扫描DB变更记录(如
-
全量同步:
- 定期全量刷缓存(如每日凌晨),兜底数据一致性。
- 风险:大数据量时可能引发数据库负载陡增,需分批次执行。
适用场景:BI报表、排行榜等低频变更、高计算耗时的数据。
🔧 四、增量 vs 全量更新策略
策略 | 适用场景 | 技术要点 |
---|---|---|
增量更新 | 数据变更频繁,需高效同步 | 基于Binlog/更新时间戳,仅同步变更部分。 |
全量更新 | 数据量小或变更极少 | 定时全量扫描,配合缓存预热机制。 |
💡 五、选型建议与避坑指南
-
一致性要求:
- 强一致:实时同步 + 延迟双删(如支付系统)。
- 最终一致:消息队列/Binlog异步(如内容展示页)。
-
性能与风险平衡:
- 频繁写场景避免直接更新缓存,优先删除(防无效写入)。
- 热点数据用逻辑过期:缓存永不过期,后台异步更新数据。
-
兜底设计:
- 所有策略需叠加 TTL过期,防逻辑漏洞导致长期脏数据。
总结:
- 实时同步:强一致首选,但需应对并发问题(延迟双删 + 锁)。
- 准实时更新:平衡性能与一致性,依赖消息顺序与幂等。
- 任务调度:最终一致兜底,适合离线计算场景。
最终建议:90%场景用 Cache Aside + 异步补偿(如MQ),配合TTL过期,兼顾开发效率与稳定性。