题目
如何保证分布式缓存与数据库的数据一致性?
信息
- 类型:问答
- 难度:⭐⭐
考点
缓存更新策略,数据一致性设计,分布式系统故障处理
快速回答
保证分布式缓存与数据库数据一致性的核心策略包括:
- Cache-Aside模式:读时加载缓存,写时更新数据库并失效缓存
- 双写策略:更新数据库后同步更新缓存(需事务保证)
- 补偿机制:通过消息队列实现最终一致性
- 过期时间兜底:设置合理的TTL防止长期不一致
关键原则:根据业务场景选择合适的一致性级别(强一致/最终一致),优先考虑失效而非更新缓存。
解析
一、原理说明
分布式缓存与数据库的数据不一致主要源于:1)并发读写导致脏数据 2)操作失败未回滚 3)网络延迟。典型场景如:
- 写后读不一致:线程A更新DB后未及时失效缓存,线程B读取到旧缓存
- 并发写冲突:两个线程同时更新同一条数据,缓存更新时序错乱
二、核心策略与代码示例
1. Cache-Aside(旁路缓存)
// 读操作示例
public User getUser(String id) {
User user = cache.get(id);
if (user == null) {
user = db.query("SELECT * FROM users WHERE id = ?", id);
if (user != null) {
cache.set(id, user, 300); // 设置TTL
}
}
return user;
}
// 写操作示例
@Transactional
public void updateUser(User user) {
db.update(user); // 先更新数据库
cache.delete(user.getId()); // 再失效缓存
}优点:简单通用,读多写少场景高效
风险:并发时可能短暂不一致(如A更新DB后B读旧缓存)
2. 双写策略(Write-Through)
// 结合事务的同步更新
@Transactional
public void updateUser(User user) {
db.update(user);
cache.update(user); // 同步更新缓存
// 需保证cache.update与db.update在同一事务
}适用场景:强一致性要求高的业务(如金融账户)
挑战:分布式事务成本高,可用性下降
3. 最终一致性(消息队列)
// 数据库更新后发送事件
@Transactional
public void updateUser(User user) {
db.update(user);
messageQueue.send("cache_invalidate", user.getId());
}
// 消费者处理
public void handleMessage(String id) {
cache.delete(id); // 异步失效缓存
// 需实现幂等性和重试机制
}三、最佳实践
- 优先失效而非更新:直接更新缓存易引发并发冲突,失效更安全
- 设置二级兜底:缓存值带版本号或时间戳,读取时校验
- 读写分离设计:写请求直连数据库,读请求走缓存
- 熔断降级:缓存故障时自动切到DB,避免雪崩
四、常见错误
- 先更新缓存后更新DB:DB失败会导致永久不一致
- 无过期时间:缓存永久存在可能传播脏数据
- 忽略重试:缓存失效失败时无补偿机制
- 过度强一致:在可接受延迟的场景使用分布式事务造成性能瓶颈
五、扩展知识
- 一致性哈希:动态扩缩容时减少缓存失效范围
- 布隆过滤器:防止缓存穿透导致DB压力
- 多级缓存:本地缓存+分布式缓存组合(如Caffeine+Redis)
- 数据版本控制:使用Vector Clock等算法解决冲突