1. 基础策略:先操作数据库 vs 先操作缓存
1.1 Cache-Aside(旁路缓存)
- 读流程:
- 先读缓存,命中则返回。
- 未命中则读数据库,写入缓存。
- 写流程:
- 更新数据库。
- 删除缓存(非更新,避免并发导致脏数据)。
- 优点:简单易实现,适合读多写少场景。
- 缺点:
- 短暂不一致:在“更新数据库 → 删除缓存”之间,若有读请求可能读到旧数据并回填缓存。
- 解决方案:设置缓存较短的TTL,或结合延迟双删。
1.2 Write-Through/Write-Behind
- Write-Through:
- 写操作同时更新数据库和缓存,由缓存层保证原子性。
- 适用场景:强一致性要求,但性能较低。
- Write-Behind:
- 写操作先更新缓存,异步批量写入数据库。
- 适用场景:高吞吐写场景,但存在数据丢失风险(如缓存宕机)。
2. 应对高并发场景的进阶方案
2.1 延迟双删
- 流程:
- 删除缓存。
- 更新数据库。
- 延迟一定时间(如500ms)后再次删除缓存。
- 目的:清除在“更新数据库期间”可能被回填的旧缓存。
- 代码示例:
java
public void updateData(Data data) { redis.delete(data.getId()); // 第一次删除 db.update(data); // 更新数据库 scheduledExecutor.schedule(() -> redis.delete(data.getId()), 500, TimeUnit.MILLISECONDS // 延迟双删 ); }
2.2 基于消息队列的最终一致性
- 流程:
- 更新数据库。
- 发送消息到MQ,通知缓存更新/删除。
- 消费者异步处理缓存操作。
- 优点:解耦数据库与缓存操作,提高系统可靠性。
- 关键点:
- 消息可靠性:确保消息不丢失(生产者确认+消费者手动ACK)。
- 幂等性处理:避免重复消费导致缓存多次更新。
java
// 生产者(数据库操作后发送消息) @Transactional public void updateData(Data data) { db.update(data); mqSender.send("cache-update-topic", data.getId()); } // 消费者(处理缓存删除) @RabbitListener(queues = "cache-update-topic") public void handleCacheUpdate(String dataId) { redis.delete(dataId); }
3. 强一致性方案
3.1 分布式锁
- 流程:
- 写操作前获取分布式锁。
- 更新数据库并删除缓存。
- 释放锁。
- 优点:避免并发写导致的数据不一致。
- 缺点:性能下降,需权衡锁粒度。
java
public void updateDataWithLock(Data data) { String lockKey = "lock:" + data.getId(); if (redisLock.acquire(lockKey, 3)) { // 获取锁 try { db.update(data); redis.delete(data.getId()); } finally { redisLock.release(lockKey); // 释放锁 } } }
3.2 数据库与缓存事务
- 适用场景:支持事务的缓存(如Redis事务、某些NewSQL数据库)。
- 流程:
sql
BEGIN; UPDATE db_table SET ... WHERE id=1; -- 数据库操作 EXEC redis.DEL cache_key; -- 缓存操作(伪代码,需中间件支持) COMMIT;
- 局限性:多数缓存不支持与数据库的跨系统事务。
4. 特殊场景处理
4.1 缓存穿透
- 问题:频繁查询不存在的数据,绕过缓存直接击穿数据库。
- 解决方案:
- 缓存空值:对查询结果为空的Key,缓存短时间(如2分钟)的空值。
- 布隆过滤器:拦截无效请求。
4.2 缓存雪崩
- 问题:大量缓存同时过期,导致请求涌入数据库。
- 解决方案:
- 随机TTL:在基础过期时间上增加随机值(如30分钟±5分钟)。
- 热点数据永不过期:通过后台任务异步更新缓存。
5. 监控与兜底措施
- 实时监控:
- 缓存命中率、数据库QPS、延迟双删的执行情况。
- 兜底策略:
- 主动刷新:数据库变更后,通过Binlog监听(如Canal)触发缓存更新。
- 降级方案:缓存故障时直接读数据库,并记录日志告警。
总结:方案选型建议
场景 | 推荐方案 |
---|---|
读多写少,容忍短暂不一致 | Cache-Aside + 延迟双删 |
写多读少,最终一致性 | 消息队列异步处理 |
强一致性要求 | 分布式锁 + 数据库事务 |
高可用性优先 | Write-Behind + 定期持久化 |
通过组合以上策略,例如“Cache-Aside + 延迟双删 + 消息队列监控”,可在保证性能的同时最大限度减少不一致窗口。实际应用中需结合业务特点(如金融系统需强一致性,电商促销可接受短暂不一致)进行调优