【面试题】缓存先删漏洞解决策略(示例代码)
缓存并发更新问题及解决方案总结
🔍 问题分析
场景描述:
- 用户A执行"先删缓存 → 更新数据库"操作
- 在删除缓存后、更新数据库前,用户B发现缓存缺失
- 用户B读取数据库旧数据并写入缓存
- 导致缓存中一直是脏数据
核心问题: 读写并发时的数据不一致
💡 解决方案
1. 🕒 延迟双删策略
// 写操作流程
1. 删除缓存
2. 更新数据库
3. 延迟500ms-1s
4. 再次删除缓存
优点: 实现简单,能清理大部分脏数据
缺点: 延迟时间难以精确控制
2. 🔒 分布式锁方案
// 写操作加锁
Lock lock = distributedLock.getLock(key);
try {lock.lock();cache.delete(key);database.update(data);
} finally {lock.unlock();
}// 读操作也加锁(防缓存击穿)
Data data = cache.get(key);
if (data == null) {Lock lock = distributedLock.getLock(key);// 双重检查后从DB读取
}
优点: 强一致性保证
缺点: 性能损耗较大
3. 📊 版本号控制
// 缓存值带版本号
class CacheValue {Data data;long version;
}// 读操作检查版本
if (cacheValue.version < currentVersion) {// 重新从DB加载
}
优点: 轻量级解决方案
缺点: 需要维护版本信息
4. 📨 消息队列异步处理
// 更新后发送延迟消息
cache.delete(key);
database.update(data);
messageQueue.sendDelayMessage(deleteMsg, 500);
优点: 解耦,可靠性高
缺点: 系统复杂度增加
5. 📝 Binlog监听同步
// 监听数据库变更事件
@EventListener
void onDatabaseChange(Event event) {cache.set(event.getKey(), event.getNewData());
}
优点: 数据最终一致性
缺点: 技术门槛较高
🎯 推荐方案
一般业务场景:
延迟双删 + 基础防击穿
public void updateData(Data data) {cache.delete(key); // 第一次删除database.update(data); // 更新DBasyncDelayDelete(key, 500); // 异步延迟删除
}
强一致性场景:
分布式锁 + 版本控制
public void updateData(Data data) {Lock lock = distributedLock.getLock(key);try {lock.lock();cache.delete(key);database.update(data);// 可选立即写入新缓存} finally {lock.unlock();}
}
📊 方案对比
| 方案 | 一致性 | 性能 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 延迟双删 | 最终一致 | 高 | 低 | 一般业务 |
| 分布式锁 | 强一致 | 中 | 中 | 金融、交易 |
| 版本控制 | 最终一致 | 高 | 中 | 高并发读 |
| 消息队列 | 最终一致 | 中 | 高 | 大型系统 |
| Binlog同步 | 最终一致 | 高 | 高 | 数据同步 |
💎 总结
- 根据业务需求选择合适方案,没有银弹
- 一般场景推荐延迟双删,平衡性能与一致性
- 强一致场景使用分布式锁,牺牲性能保证正确性
- 组合使用多种方案往往能获得更好效果
选择方案时要综合考虑业务重要性、并发量、技术成本等因素,找到最适合的平衡点。
