缓存与数据库数据一致性 详解

海哥  金牌会员 | 2024-12-4 13:36:38 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 826|帖子 826|积分 2478

缓存与数据库数据一致性详解

在分布式系统中,缓存(如 Redis、Memcached)与数据库(如 MySQL、PostgreSQL)一起使用是提高系统性能的常用方法。然而,缓存与数据库可能因更新时序、操纵失误等缘故原由出现数据不一致的问题,导致数据读取异常,影响用户体验和业务逻辑的正确性。

1. 缓存与数据库数据不一致的缘故原由


  • 先更新数据库,再删除缓存

    • 更新数据库后,如果删除缓存的操纵失败或延长,缓存仍会返回旧数据。

  • 先删除缓存,再更新数据库

    • 缓存删除后,其他并发哀求可能从数据库读取旧数据并重新写入缓存,导致缓存出现脏数据。

  • 缓存逾期

    • 缓存中数据逾期后,新的哀求直接访问数据库,但可能未正确写入或更新缓存。

  • 分布式系统中的延长与故障

    • 在分布式环境中,网络延长、服务故障、节点不一致等问题会导致数据同步失败。

  • 异步更新

    • 使用异步任务更新缓存时,任务执行延长或失败会导致缓存与数据库数据不同步。


2. 数据一致性要求

数据一致性可以分为以下三种场景:

  • 强一致性

    • 数据写入数据库后,缓存必须立刻更新或删除,确保读取到的数据与数据库一致。
    • 适用场景:金融系统、订单系统等对一致性要求极高的业务。

  • 最终一致性

    • 数据更新后,答应短时间内数据不一致,但最终状态必要保持一致。
    • 适用场景:电商商品库存、用户行为分析等。

  • 弱一致性

    • 数据更新后,不保证缓存与数据库的数据一致性。
    • 适用场景:对一致性要求不高的业务,如热点排行榜等。


3. 缓存与数据库一致性的办理方案

3.1 经典计谋:Cache Aside(旁路缓存模式)

流程


  • 读操纵

    • 先从缓存读取数据,如果缓存命中则返回数据;
    • 如果缓存未命中,则从数据库读取数据,并将效果写入缓存。

  • 写操纵

    • 更新数据库后,删除对应的缓存数据。

优点



  • 简朴易实现;
  • 减少了写缓存的复杂性,避免数据库和缓存操纵的顺序辩论。
缺点



  • 仍可能出现先更新数据库再删除缓存导致的不一致问题。

3.2 延时双删计谋

流程


  • 删除缓存:更新数据库之前,先删除缓存中的数据。
  • 更新数据库:更新数据库中的数据。
  • 二次删除:等待肯定延时后,再次删除缓存。
伪代码实现

  1. // 更新逻辑
  2. redis.del("key");  // Step 1: 删除缓存
  3. updateDatabase(data);  // Step 2: 更新数据库
  4. Thread.sleep(500);  // Step 3: 延迟一定时间
  5. redis.del("key");  // Step 4: 再次删除缓存
复制代码
优点



  • 有用办理先删缓存再更新数据库引起的并发脏数据问题。
缺点



  • 延时选择的时间需合理,太短可能无效,太长会影响性能;
  • 依靠数据库事件的准确性。

3.3 读写一致性控制

流程


  • 数据写入或更新时,先删除缓存;
  • 设置一个短时间内的“读屏障”,在此期间内,读取数据库的最新数据,而不是缓存的数据。
实现方式



  • 设置一个标记位,表示某数据正在更新,克制读取缓存。
  • 示例:
    1. String lockKey = "lock:key";
    2. if (redis.get(lockKey) == null) {
    3.     data = redis.get("key");
    4.     if (data == null) {
    5.         data = database.query("key");
    6.         redis.set("key", data, 60);
    7.     }
    8. } else {
    9.     data = database.query("key");
    10. }
    复制代码
优点



  • 能够在数据更新时有用屏蔽脏数据读取。
缺点



  • 增加了系统复杂性;
  • 必要合理设计“屏障时间”。

3.4 分布式锁控制一致性

流程


  • 更新数据库和缓存时加分布式锁,确保同一时间只有一个线程能操纵缓存和数据库。
  • 锁释放后,其余线程才能进行读写操纵。
实现方式



  • 使用 Redis 的分布式锁:
    1. boolean lock = redis.setnx("lock:key", "1", 30);  // 获取分布式锁
    2. if (lock) {
    3.     updateDatabase(data);  // 更新数据库
    4.     redis.del("key");  // 删除缓存
    5.     redis.del("lock:key");  // 释放锁
    6. }
    复制代码
优点



  • 有用避免并发引发的数据不一致问题。
缺点



  • 性能开销较大,适合对一致性要求高的场景。

3.5 消息队列异步更新

流程


  • 数据库更新后,发送一条更新消息到消息队列。
  • 消费者监听消息队列,收到更新消息后同步更新缓存。
实现方式



  • 数据库更新逻辑:
    1. updateDatabase(data);
    2. messageQueue.sendMessage("update_cache", "key");
    复制代码
  • 消息消费者逻辑:
    1. messageQueue.listen("update_cache", (key) -> {
    2.     data = database.query(key);
    3.     redis.set(key, data, 60);
    4. });
    复制代码
优点



  • 提高了系统解耦性,缓存更新操纵由异步任务完成。
缺点



  • 依靠消息队列的可靠性;
  • 延长可能导致短时间内不一致。

3.6 缓存更新重试机制

流程


  • 当缓存更新失败时,记录失败操纵并定期重试。
  • 可结合延时队列或定时任务实现。
实现方式



  • 更新失败记录到延时队列:
    1. try {
    2.     redis.del("key");
    3. } catch (Exception e) {
    4.     delayQueue.add("key");
    5. }
    复制代码
优点



  • 确保缓存最终更新乐成。
缺点



  • 增加了系统复杂度。

4. 数据一致性办理方案的对比

方案优点缺点适用场景Cache Aside简朴易用,易于实现高并发场景下可能导致短时间不一致一样平常业务场景延时双删有用办理并发问题延长时间难以准确控制一样平常业务场景读写一致性控制避免数据更新时的脏读增加系统复杂性高一致性要求场景分布式锁保证强一致性性能较低强一致性要求场景消息队列异步更新解耦数据库和缓存逻辑,提高吞吐量消息延长可能引起短暂不一致高吞吐量、最终一致性场景重试机制保证最终一致性增加系统复杂度缓存更新易失败的场景
5. 实际案例分析

案例1:秒杀场景



  • 问题:秒杀商品库存频繁更新,要求缓存与数据库一致。
  • 办理方案

    • 使用延时双删计谋,确保缓存数据与数据库同步。

案例2:电商商品价格



  • 问题:商品价格必要强一致性,不能表现逾期价格。
  • 办理方案

    • 使用分布式锁,确保价格更新后缓存一致。

案例3:用户信息修改



  • 问题:用户更新个人信息后,需保证缓存中的数据一致。
  • 办理方案

    • 使用消息队列异步更新缓存。


6. 总结

缓存与数据库数据一致性问题是分布式系统设计中的核心问题,必要根据业务场景和一致性要求选择适合的方案:

  • 一样平常场景:优先使用 Cache Aside 模式。
  • 高一致性要求:可结合延时双删或分布式锁。
  • 最终一致性:推荐使用消息队列异步更新。
  • 高可用性
    :采取重试机制或缓存预热。
通过合理设计,可以在性能和一致性之间找到最佳平衡点,提拔系统的稳定性和用户体验。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

海哥

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表