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

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

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


1. 缓存与数据库数据不一致的原因

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

    • 更新数据库后,如果删除缓存的操作失败或延迟,缓存仍会返回旧数据。
  2. 先删除缓存,再更新数据库

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

    • 缓存中数据过期后,新的请求直接访问数据库,但可能未正确写入或更新缓存。
  4. 分布式系统中的延迟与故障

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

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

2. 数据一致性要求

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

  1. 强一致性

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

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

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

3. 缓存与数据库一致性的解决方案

3.1 经典策略:Cache Aside(旁路缓存模式)

流程
  1. 读操作
    • 先从缓存读取数据,如果缓存命中则返回数据;
    • 如果缓存未命中,则从数据库读取数据,并将结果写入缓存。
  2. 写操作
    • 更新数据库后,删除对应的缓存数据。
优点
  • 简单易实现;
  • 减少了写缓存的复杂性,避免数据库和缓存操作的顺序冲突。
缺点
  • 仍可能出现先更新数据库再删除缓存导致的不一致问题。

3.2 延时双删策略

流程
  1. 删除缓存:更新数据库之前,先删除缓存中的数据。
  2. 更新数据库:更新数据库中的数据。
  3. 二次删除:等待一定延时后,再次删除缓存。
伪代码实现
// 更新逻辑
redis.del("key");  // Step 1: 删除缓存
updateDatabase(data);  // Step 2: 更新数据库
Thread.sleep(500);  // Step 3: 延迟一定时间
redis.del("key");  // Step 4: 再次删除缓存
优点
  • 有效解决先删缓存再更新数据库引起的并发脏数据问题。
缺点
  • 延时选择的时间需合理,太短可能无效,太长会影响性能;
  • 依赖数据库事务的准确性。

3.3 读写一致性控制

流程
  1. 数据写入或更新时,先删除缓存;
  2. 设置一个短时间内的“读屏障”,在此期间内,读取数据库的最新数据,而不是缓存的数据。
实现方式
  • 设置一个标记位,表示某数据正在更新,禁止读取缓存。
  • 示例:
    String lockKey = "lock:key";
    if (redis.get(lockKey) == null) {data = redis.get("key");if (data == null) {data = database.query("key");redis.set("key", data, 60);}
    } else {data = database.query("key");
    }
    
优点
  • 能够在数据更新时有效屏蔽脏数据读取。
缺点
  • 增加了系统复杂性;
  • 需要合理设计“屏障时间”。

3.4 分布式锁控制一致性

流程
  1. 更新数据库和缓存时加分布式锁,确保同一时间只有一个线程能操作缓存和数据库。
  2. 锁释放后,其余线程才能进行读写操作。
实现方式
  • 使用 Redis 的分布式锁:
    boolean lock = redis.setnx("lock:key", "1", 30);  // 获取分布式锁
    if (lock) {updateDatabase(data);  // 更新数据库redis.del("key");  // 删除缓存redis.del("lock:key");  // 释放锁
    }
    
优点
  • 有效避免并发引发的数据不一致问题。
缺点
  • 性能开销较大,适合对一致性要求高的场景。

3.5 消息队列异步更新

流程
  1. 数据库更新后,发送一条更新消息到消息队列。
  2. 消费者监听消息队列,收到更新消息后同步更新缓存。
实现方式
  • 数据库更新逻辑:
    updateDatabase(data);
    messageQueue.sendMessage("update_cache", "key");
    
  • 消息消费者逻辑:
    messageQueue.listen("update_cache", (key) -> {data = database.query(key);redis.set(key, data, 60);
    });
    
优点
  • 提高了系统解耦性,缓存更新操作由异步任务完成。
缺点
  • 依赖消息队列的可靠性;
  • 延迟可能导致短时间内不一致。

3.6 缓存更新重试机制

流程
  1. 当缓存更新失败时,记录失败操作并定期重试。
  2. 可结合延时队列或定时任务实现。
实现方式
  • 更新失败记录到延时队列:
    try {redis.del("key");
    } catch (Exception e) {delayQueue.add("key");
    }
    
优点
  • 确保缓存最终更新成功。
缺点
  • 增加了系统复杂度。

4. 数据一致性解决方案的对比

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

5. 实际案例分析

案例1:秒杀场景

  • 问题:秒杀商品库存频繁更新,要求缓存与数据库一致。
  • 解决方案
    • 使用延时双删策略,确保缓存数据与数据库同步。

案例2:电商商品价格

  • 问题:商品价格需要强一致性,不能显示过期价格。
  • 解决方案
    • 使用分布式锁,确保价格更新后缓存一致。

案例3:用户信息修改

  • 问题:用户更新个人信息后,需保证缓存中的数据一致。
  • 解决方案
    • 使用消息队列异步更新缓存。

6. 总结

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

  1. 一般场景:优先使用 Cache Aside 模式。
  2. 高一致性要求:可结合延时双删或分布式锁。
  3. 最终一致性:推荐使用消息队列异步更新。
  4. 高可用性
    :采用重试机制或缓存预热。

通过合理设计,可以在性能和一致性之间找到最佳平衡点,提升系统的稳定性和用户体验。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/888330.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux DNS解释器

作用 DNS(Domain Name System)是互联网上的一项服务,用于将域名和IP地址进行相互映射,使人 更方便的访问互联网 正向解析:域名->IP 反向解析:IP->域名 连接方式 DNS使用53端口监听网络 查看方法&a…

3.STM32通信接口之SPI通信---SPI实战(W25Q64存储模块介绍)《精讲》

上一节介绍了SPI的通信过程和方法,接下来就要进行STM32与外围模块通信了,这个模块是一块非易失型存储芯片,能够提供8MB的存储空间。接下来跟着Whappy脚步,进行探索新大陆吧!【免费】W25Q64(中英文数据手册)资源-CSDN文…

嵌入式系统应用-LVGL的应用-平衡球游戏 part2

平衡球游戏 part2 4 mpu60504.1 mpu6050 介绍4.2 电路图4.3 驱动代码编写 5 游戏界面移植5.1 移植源文件5.2 添加头文件 6 参数移植6.1 4 mpu6050 4.1 mpu6050 介绍 MPU6050是一款由InvenSense公司生产的加速度计和陀螺仪传感器,广泛应用于消费电子、机器人等领域…

java将word docx pdf转换为图片(不需要额外下载压缩包,直接导入maven坐标)

(本代码实现的是将第1页转为图片,主要用于制作文件缩略图) pdf转图片容易 docx转图片麻烦,看其他博客可以直接导入maven坐标,但我知道那是需要付费且有时限的包 本着简单实用的心,我找到法子了 pdf转图片:有库直接转…

C#学写了一个程序记录日志的方法(Log类)

1.错误和警告信息单独生产文本进行记录; 2.日志到一定内存阈值可以打包压缩,单独存储起来,修改字段MaxLogFileSizeForCompress的值即可; 3.Log类调用举例:Log.Txt(JB.信息,“日志记录内容”,"通道1"); usi…

Qt—QLineEdit 使用总结

文章参考:Qt—QLineEdit 使用总结 一、简述 QLineEdit是一个单行文本编辑控件。 使用者可以通过很多函数,输入和编辑单行文本,比如撤销、恢复、剪切、粘贴以及拖放等。 通过改变 QLineEdit 的 echoMode() ,可以设置其属性,比如以密码的形式输入。 文本的长度可以由 m…

linux(centos) 环境部署,安装JDK,docker(mysql, redis,nginx,minio,nacos)

目录 1.安装JDK (非docker)1.1 将文件放在目录下: /usr/local/jdk1.2 解压至当前目录1.3 配置环境变量 2.安装docker2.1 验证centos内核2.2 安装软件工具包2.3 设置yum源2.4 查看仓库中所有docker版本,按需选择安装2.5 安装docker2.6 启动docker 并 开机…

电阻改善信号完整性

1.为什么电路端接电阻能改善信号完整性 由于电信号在PCB上传输,因此在PCB设计中可以把PCB走线认为是信号的通道,当该通道的 物理结构(线宽,线到参考面的距离等)发生变化,特别是有一些突变时,都会…

Java基础面试题,46道Java基础八股文(4.8万字,30+手绘图)

Java是一种广泛使用的编程语言,由Sun Microsystems(现为Oracle Corporation的一部分)在1995年首次发布。它是一种面向对象的语言,这意味着它支持通过类和对象的概念来构造程序。 Java设计有一个核心理念:“编写一次&am…

redisson-spring-data与Spring-Data-Redis的版本关系问题

redisson-spring-boot-starter https://github.com/redisson/redisson/tree/master/redisson-spring-boot-starter https://github.com/redisson/redisson/tree/master/redisson-spring-data#spring-data-redis-integration 将 Redisson 与 Spring Boot 库集成。依赖于Spring…

记录下nginx接口代理配置问题

其中api和api1是前面定义的upstream,ip相同只是端口不同。 一开始/api1/直接 像api一样 proxy_pass http://api1这样是不行的,因为会代理到 后端的 /api1/...接口,而后端实际接口地址是 /api/..... 所以必须像上面写法才能将外网的 /api…

高效流程图绘制:开发设计流程图利器

在选择画流程图的工具时,不同的项目和使用场景会决定最佳的工具。以下是几款常见的流程图工具,并结合具体项目使用场景提供建议: 1. Lucidchart 特点: 在线协作:支持多人实时协作,适合团队合作。模板丰富&…

【Python网络爬虫笔记】7-网络爬虫的搜索工具re模块

目录 一、网络爬虫中的正则表达式和re模块(一)数据提取的精确性(二)处理复杂的文本结构(三)提高数据处理效率 二、正则表达式的内涵(一)、常用元字符(二)、量…

2411mfc,修改按钮颜色

添加消息:ON_WM_CTLCOLOR() //在OnInitDialog()方法中添加{HWND hSatateWnd GetDlgItem(IDC_CHK)->GetSafeHwnd();SetWindowTheme(hSatateWnd, _T(""), _T(""));}头文件中: afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);HBRUSH O…

python(18) : flask_sqlalchemy 配置sqlserver数据库对象

1.安装依赖 pip3 install flask_sqlalchemy -i https://mirrors.aliyun.com/pypi/simple/ requests2.数据库配置信息(db_manager.py) import osfrom flask import Flask from flask_cors import CORS from flask_sqlalchemy import SQLAlchemydatabase testapp Flask(__nam…

3248. 矩阵中的蛇

3248. 矩阵中的蛇 题目链接&#xff1a;3248. 矩阵中的蛇 代码如下&#xff1a; class Solution { public:int finalPositionOfSnake(int n, vector<string>& commands){int i 0, j 0;for (string& command : commands){if (command "LEFT") { j…

React进阶面试题目(二)

React 组件声明的方法有哪些&#xff1f;各有什么不同&#xff1f; React 组件声明的方法主要有三种&#xff1a; 无状态函数式组件&#xff1a;这种组件只负责根据传入的props来展示&#xff0c;不涉及到state状态的操作。组件不会被实例化&#xff0c;整体渲染性能得到提升…

Day4:生信新手笔记 — R语言简单命令与Rstudio配置

一、Rstudio的界面展示 (很像Matlab风格) 二、Rstudio设置字体大小 三、 用Rproject管理工作目录 工作目录(working directory) 即当前所在的目录&#xff0c;是脚本、图片、文件的默认保存位置&#xff0c;也是文件读取的默认位置。R语言只能和一个文件夹进行互动&#xff0…

IPv6 NA RTR/SOL/OVR标志位,单播多播选择,ndppd代理和kernel配置

NA消息用单播还是多播的判断理由 单播回复&#xff08;Unicast&#xff09;&#xff1a; 如果客户端发送 RS 消息时&#xff0c;使用的是一个全局地址或链路本地地址作为源地址&#xff0c;则 RA 消息会单播回复到客户端的源地址。这种方式减少了网络中的广播流量&#xff0c;…

koa中间件

文章目录 1. koa中间件简介2. 中间件类型1. 应用级中间件2. 路由级中间件3. 错误处理中间件4. 第三方中间件 3.中间件执行流程 1. koa中间件简介 在Koa中&#xff0c;中间件呈现为一个异步函数&#xff0c;该函数支持 async/await 语法&#xff0c;它接收两个参数&#xff1a;…