侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1824 篇文章
  • 累计收到 0 条评论

如何保证分布式缓存与数据库的数据一致性?

2025-12-12 / 0 评论 / 4 阅读

题目

如何保证分布式缓存与数据库的数据一致性?

信息

  • 类型:问答
  • 难度:⭐⭐

考点

缓存更新策略,数据一致性设计,分布式系统故障处理

快速回答

保证分布式缓存与数据库数据一致性的核心策略包括:

  • 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等算法解决冲突