一、为什么会出现数据不一致?

根本原因在于:这是一个涉及两个独立存储系统的数据更新操作,它无法被包装成一个原子操作(分布式事务)。更新数据库和更新缓存是两个独立的步骤,无论在代码中如何排列这两个步骤,都可能因为并发、失败重试等原因导致不一致。

主要的不一致场景可以归结为以下两类顺序问题:

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

    • 成功更新 DB -> 删除 Cache -> 数据一致(下次读取会回种缓存)。
    • 失败
      • 更新 DB 成功,删除 Cache 失败:数据库是新数据,缓存是旧数据,发生不一致。
      • 并发读写:在更新 DB 后、删除 Cache 前,另一个线程来读取,发现缓存是旧的并加载,随后 Cache 被删除,但里面已经是脏数据了。
  2. 先删除缓存,再更新数据库

    • 成功删除 Cache -> 更新 DB -> 数据一致。
    • 失败
      • 删除 Cache 成功,更新 DB 失败:缓存是空的,数据库是旧数据,数据一致(但缓存miss,会从DB读旧数据回种,仍是旧数据)。
      • 并发读写(更严重)
        1. 线程A 删除缓存
        2. 线程B 发现缓存不存在,从数据库读取旧数据
        3. 线程B 将旧数据回种到缓存
        4. 线程A 更新数据库为新数据
        • 结果:数据库是新数据,缓存是旧数据,发生不一致。

二、常见的解决方案与策略

没有一种完美的银弹方案,需要根据业务场景(对一致性的要求、读写比例、性能要求等)进行权衡和选择。

1. Cache-Aside (旁路缓存) + 延迟双删

这是最常用的模式,其读/写逻辑如下:

  • 读流程

    1. 从 Redis 读取数据。
    2. 如果命中,直接返回。
    3. 如果未命中,从 MySQL 读取数据。
    4. 将 MySQL 的数据写入 Redis(回种缓存),然后返回。
  • 写流程(关键)

    1. 更新 MySQL 中的数据。
    2. 删除 Redis 中的对应缓存。
    3. (延迟双删的关键步骤) 等待一小段时间(如几百毫秒),再次删除 Redis 缓存。

为什么需要“延迟双删”?
它旨在解决上述“先更新数据库,再删除缓存”模式下的并发问题。第二次删除是为了清除在“更新DB”和“第一次删除Cache”这个时间间隙内,可能被其他读请求回种的旧数据。

优点

  • 实现相对简单,适用性广。
  • 延迟双删能解决大部分并发导致的不一致。

缺点

  • 等待时间(延迟)需要估算,不好设置。
  • 第二次删除可能仍会失败(需要重试机制)。
  • 在延迟期间,可能仍有短暂的不一致。

改进:为第二次删除增加重试机制。可以将失败的删除操作写入一个消息队列,由专门的服务消费重试,确保最终一定删除成功。

2. Write-Through (穿透写) / Write-Behind

这类方案通常需要依赖一个独立的服务或中间件来统一管理缓存和数据库的写入。

  • Write-Through

    • 应用层只写入缓存(由一个中间件来管理)。
    • 中间件同步地写入缓存和数据库。
    • 保证了强一致性,但性能很差,因为每次写操作都要等待两个存储系统都完成。
  • Write-Behind (也叫Write-Back)

    • 应用层只写入缓存(由一个中间件来管理)。
    • 中间件先写缓存,然后异步地批量更新到数据库。
    • 性能极高,但存在数据丢失风险(如果缓存宕机,未持久化的数据会丢失)。一致性最弱。

优点

  • Write-Through 强一致。
  • Write-Behind 性能极高。

缺点

  • 架构复杂,需要引入和维护额外的中间件。
  • Write-Through 性能低。
  • Write-Behind 有丢失数据风险。
3. 基于 MySQL Binlog 的最终一致性方案(推荐)

这是目前最流行、最可靠的大型项目方案。其核心是利用 MySQL 的二进制日志(Binlog)进行增量数据同步。

工作原理

  1. 业务代码正常更新 MySQL。
  2. 一个数据同步服务(如 Canal, Maxwell, Debezium)伪装成 MySQL 的从库,订阅并解析 Binlog。
  3. 同步服务获取到数据的变更事件(增、删、改)后,发送到一个消息队列(如 Kafka/RocketMQ)。
  4. 一个缓存更新服务消费 MQ 中的消息,然后删除 Redis 中对应的缓存。

优点

  • 彻底解耦:业务代码只关心数据库,完全不知道缓存的存在。代码简洁。
  • 高可靠性:Binlog 是 MySQL 自带的高可靠机制,保证了数据变更不会丢失。
  • 最终一致性:通过 MQ 的异步消费,保证了数据最终会一致,延迟低。
  • 通用性:一套系统可以为多种业务服务。

缺点

  • 架构最复杂,技术门槛高,需要维护多个组件(同步服务、MQ、消费服务)。

三、方案选择与最佳实践总结

策略一致性强度复杂度性能适用场景
Cache-Aside + 延迟双删最终一致(可能有短暂不一致)中等通用方案,适合大多数中小型项目
Write-Through强一致对一致性要求极高,可接受写性能差的场景
Write-Behind弱一致(可能丢失数据)极高写入巨大,对性能要求极高,能容忍数据丢失的场景(如计数、日志)
Binlog 同步最终一致(可靠性高)非常高大型互联网项目,架构完善,需要高可靠性和解耦

通用最佳实践建议

  1. 优先选择删除缓存,而不是更新缓存

    • 更新缓存可能带来并发问题、浪费资源(多次更新可能只有最后一次被读到)。直接删除让下一次读请求来回种缓存,是更 lazy 和高效的做法。
  2. Key 的过期时间

    • 即使一切正常,也一定要给 Redis 的 Key 设置一个过期时间。这是最后一道防线,即使同步失败,旧缓存也会自动失效,最终达到一致。
  3. 保证删除操作的重试

    • 无论是哪种方案,删除缓存都可能失败。必须要有重试机制(如通过消息队列),确保删除最终成功。
  4. 读操作是否回种缓存?

    • 在高并发场景下,如果缓存缺失(Cache Miss),可能会导致大量请求穿透到数据库(缓存击穿)。可以考虑使用互斥锁(Mutex Lock),只让一个请求去数据库回种缓存,其他请求等待。
  5. 根据业务容忍度选择策略

    • 对于用户信息、商品价格等对一致性要求较高的数据,推荐使用 Binlog 同步方案延迟双删
    • 对于点赞数、浏览量等对一致性要求不高的数据,甚至可以设置短一点的过期时间,容忍短暂不一致。

结论:

对于追求稳定和可靠性的大型项目基于 Binlog 的异步同步方案是最佳选择。对于中小型项目,从简单有效出发,Cache-Aside + 延迟双删 + 失败重试机制 是一个不错的起点,同时务必为缓存设置过期时间

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

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

相关文章

coolshell文章阅读摘抄

coolshell文章阅读摘抄打好基础学好英语限制你的不是其它人,也不是环境,而是自己Java打好基础 程序语言:语言的原理,类库的实现,编程技术(并发、异步等),编程范式,设计模…

数据库造神计划第六天---增删改查(CRUD)(2)

🔥个人主页:寻星探路 🎬作者简介:Java研发方向学习者 📖个人专栏:《从青铜到王者,就差这讲数据结构!!!》、 《JAVA(SE)----如此简单&a…

使用Rust实现服务配置/注册中心

Conreg 使用 Rust 实现的配置与注册中心,参考了 Nacos 的设计,简单易用,使用 Raft 保证集群节点数据一致性。 支持的平台: UbuntuCentOS其他常见的 Linux 发行版(我们使用 musl 编译,理论上支持所有主流…

三色标记算法

在 JVM 并发垃圾收集(GC)中,三色标记算法是实现 “GC 线程与用户线程并行执行” 的关键技术,它解决了并发场景下 “如何准确标记存活对象” 的核心问题,是 CMS、G1 等现代收集器的底层基础。一、三色标记的核心&#x…

OpenStack 管理与基础操作学习笔记(一):角色、用户及项目管理实践

OpenStack实验 OpenStack命令 admin-openrc.sh 进入管理员视图查看当前 OpenStack 中的项目列表,验证是否已经登录成功切换用户 修改文件切换用户上传文件切换用户OpenStack 认证管理 实验介绍 通过 OpenStack Dashboard 和 OpenStack CLI 两种方式创建角色、用户、…

直接查找试卷且可以免费下载

有什么网站可以直接查找试卷且可以免费下载? SearXNG开源元搜索引擎 This website shows the SearXNG public instances searx一个可定制的搜索引擎 分享一个基于Blockstack的DApp-searx,一个可定制的搜索引擎。 1- 链接 官网地址:https://searx.worl…

【独立版】智创云享知识付费小程序 v5.0.23+小程序 搭建教程

介绍智创云享知识付费小程序v5.0.23 含PC、小程序、H5 、前端,系统独立版已修复已知bug问题。框架是一款基于ThinkPHP框架开发的虚拟资源知识付费小程序,为广大创业者、自媒体及培训机构提供知识付费、内容付费、资源变现等领域的行业解决方案&#xff1…

布尔运算-区间dp

面试题 08.14. 布尔运算 - 力扣(LeetCode) Solution 这题的思路比较直接,就是枚举最后一个进行计算的运算符,但是在实现过程中需要注意,定义范式f(l,r)表示l到r范围,l和r必须为数字,l1,r-1为运…

MyBatis-Plus 扩展全局方法

1.文件内容package com.ruoyi.business.mybatisplus.base;import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.extension.service.IService;import java.util.List;/*** 扩展的 Service 接口* 所有自定义 Service 接口都需要继承此接口…

13.Linux OpenSSH 服务管理

文章目录Linux OpenSSH 服务管理环境准备OpenSSH 服务介绍SSH 介绍SSH 建立连接的过程加密类型双向加密过程使用 ssh 访问远端CLIssh 工具演示ssh工具配置文件配置 ssh 密钥认证ssh 故障模拟故障模拟排故故障自定义 SSH 服务配置文件禁止 root 登录禁止密码登录只允许特定用户登…

速通ACM省铜第五天 赋源码(MEX Count)

目录 引言: MEX Count 题意分析 逻辑梳理 代码实现 结语: 引言: 本来,今天我是想着出俩题或三题题解的,但是在打第一题的时候就天塌了,导致今天就只搓了一道题,这题的难度在CF中为1300的水准&…

【数据结构与算法-Day 27】堆的应用:从堆排序到 Top K 问题,一文彻底搞定!

Langchain系列文章目录 01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…

企业即时通讯保障企业通讯安全,提升企业部门协作效率

在当今数字化转型的大潮中,企业即时通讯软件已从单纯的沟通工具,逐步演变为保障企业数据安全的核心基础设施。吱吱企业即时通讯软件通过“私有化部署全流程加密”的双重机制,为企业构建了一套集“通讯安全”与“部门协作”于一体的数字化解决…

《华为变革法:打造可持续进步的组织》读书笔记

推荐序一:变革是企业活下去的基础(胡彦平)华为前常务副总裁、变革指导委员会成员胡彦平在序言中强调,企业存续的核心命题是应对不确定性,而变革能力是破解这一命题的唯一答案。他以华为 30 余年的发展历程为例&#xf…

第二篇:排序算法的简单认识【数据结构入门】

排序算法的分类标准 时间复杂度分类 a. 简单排序算法:时间复杂度O(n),冒泡排序、选择排序、插入排序; b. 高级排序算法:时间复杂度O(n logn),快速排序、归并排序、堆排序; c. 线性排序算法:时间…

快速掌握Dify+Chrome MCP:打造网页操控AI助手

你是否曾经希望那些强大的开源大模型能更贴合你的专业领域,或者学会模仿你的行文风格?其实,实现这个目标的关键就在于“微调”。曾几何时,微调模型是大公司的专属游戏——动不动就需要几十张GPU和复杂的分布式训练技术。 而现在&…

单词记忆-轻松记忆10个实用英语单词(15)

1. repaint含义:重新油漆 读音标注:/ˌriːˈpeɪnt/ 例句:We need to repaint the walls after the repairs. 译文:修理完成后需要重新粉刷墙壁。 衍生含义:重新绘制(图像场景);翻新…

visual studio快捷键

1.visual studio代码格式化快捷键 1.CtrlA(全选) 2.CtrlK 3.CtrlF2.多行注释 1.Ctrlk 2.Ctrlc2.多行取消注释 1.Ctrlk 2.Ctrlu

Django全栈班v1.04 Python基础语法 20250913 下午

练习:个人信息收集器 任务:创建一个个人信息收集和展示程序 要求: 收集用户的姓名,年龄,城市,爱好验证年龄输入,必须是正数格式化输出用户信息计算用户出生年份 name input("请输入姓名&a…

学习海康VisionMaster之字符缺陷检测

前言:差不多三个月没更新了,天天码代码,实在是太忙了,有时候也在想这么忙到底是不是工作方法的问题,怎么样才能变成大师呢! 一:进一步学习 今天学习下VisionMaster中的字符缺陷检测&#xff1…