讲讲 Redis 缓存更新一致性,看了都说好!

news2025/2/28 21:34:06

当执行写操作后,需要保证从缓存读取到的数据与数据库中持久化的数据是一致的,因此需要对缓存进行更新。

因为涉及到数据库和缓存两步操作,难以保证更新的原子性。

在设计更新策略时,我们需要考虑多个方面的问题:

  • 对系统吞吐量的影响:比如更新缓存策略产生的数据库负载小于删除缓存策略的负载

  • 并发安全性:并发读写时某些异常操作顺序可能造成数据不一致,如缓存中长期保存过时数据

  • 更新失败的影响:若某个操作失败,如何对业务影响降到最小

  • 检测和修复故障的难度: 操作失败导致的错误会在日志留下详细的记录容易检测和修复。并发问题导致的数据错误没有明显的痕迹难以发现,且在流量高峰期更容易产生并发错误产生的业务风险较大。

更新缓存有两种方式:

  • 删除失效缓存: 读取时会因为未命中缓存而从数据库中读取新的数据并更新到缓存中

  • 更新缓存: 直接将新的数据写入缓存覆盖过期数据

更新缓存和更新数据库有两种顺序:

  • 先数据库后缓存

  • 先缓存后数据库

两两组合共有四种更新策略,现在我们逐一进行分析。

并发问题通常由于后开始的线程却先完成操作导致,我们把这种现象称为“抢跑”。下面我们逐一分析四种策略中“抢跑”带来的错误。

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

若数据库更新成功,删除缓存操作失败,则此后读到的都是缓存中过期的数据,造成不一致问题。

可能存在读写线程竞争导致的并发错误:

先更新数据库,再更新缓存

同删除缓存策略一样,若数据库更新成功缓存更新失败则会造成数据不一致问题。

该策略同样存在读写线程竞争导致数据不一致的问题:

也可能因为两个写线程竞争导致并发错误:

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

可能发生的并发错误:

先更新缓存,再更新数据库

若缓存更新成功数据库更新失败, 则此后读到的都是未持久化的数据。因为缓存中的数据是易失的,这种状态非常危险。

因为数据库因为键约束导致写入失败的可能性较高,所以这种策略风险较大。

可能发生的并发错误:

两个写线程竞争也会导致数据不一致:

解决方案

使用 CAS

CAS (Check-And-Set 或 Compare-And-Swap)是一种常见的保证并发安全的手段。CAS 当且仅当客户端最后一次取值后该 key 没有被其他客户端修改的情况下,才允许当前客户端将新值写入。

func CAS(oldVal, newVal) {    if cache.get() == oldVal {        cache.set(newVal)    }}

我们以上文提到的「先更新数据库,再更新缓存」方案中两个写线程竞争为例,尝试使用 CAS 来解决这个并发问题:

时间线程A线程B数据库缓存
0v0v0
1更新数据库为 v1v1v0
2更新数据库为 v2v2v0
3执行 CAS 操作:当且仅当缓存中为 v0 时将 v2 写入缓存v2v2
4执行 CAS 操作:当且仅当缓存中为 v0 时将v1写入缓存。当前缓存为 v2 故放弃写缓存v2v2

由上图可见,CAS 可以有效的避免并发错误的发生。

目前一些兼容 Redis 协议的中间件已经提供了 CAS 命令的支持,比如阿里的 Tair 以及腾讯的 Tendis。

Redis 官方提供了 Watch + 事务的方法来支持 CAS, 或者使用 redis 中 lua 脚本原子性执行的特点来实现 CAS。不过由于代码较为复杂,这两种方案都不常见。

使用分布式锁

CAS 假设发生并发问题的概率不大, 所以 CAS 也被称为乐观锁。那么悲观锁能否解决我们的问题呢?

还是以「先更新数据库,再更新缓存」方案中两个写线程竞争为例, 我们要求任何线程在写入或读取数据库前都需要获取排它锁。

时间线程A线程B数据库缓存
0v0v0
1获取排它锁v0v0
2更新数据库为 v1v1v0
3更新缓存为 v1v1v1
4等待排它锁v1v1
5释放排它锁v1v1
6获得排它锁v1v1
7更新数据库为 v2v2v1
8更新缓存为 v2v2v2
9释放排它锁v2v2

分布式锁同样可以解决并发问题,只是成本可能略高。

异步更新

阿里开源了 MySQL 数据库binlog的增量订阅和消费组件 - canal。canal 模拟从库获得主库的 binlog 更新,然后将更新数据写入 MQ 或直接进行消费。

我们可以让API服务器只负责写入数据库,另一个线程订阅数据库 binlog 增量进行缓存更新。

因为 binlog 是有序的,因此可以避免两个写线程竞争。但我们仍然需要解决读写线程竞争的问题:

这里同样可以 CAS 解千愁:

延时双删

使用删除缓存策略时读线程先开始却后写缓存会导致不一致,那么我们在读线程结束后再次清除缓存是不是就可以解除错误状态了?延时双删就是写线程等待一段时间“确保”读线程都结束后再次删除缓存,以此清除可能的错误缓存数据。

理论上我们无法给出一个时间来“确保”读线程都结束,所以仍有存在并发问题的可能。但是延时双删实现成本很低而且极大的减少了并发问题出现的概率,不失为一种简单实用的手段。

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

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

相关文章

那些测试行业的细分岗位,你知道多少?薪资又如何?

软件测试是个需求多,就业机会大的职业。目前,我国具备软件测试能力的人员数量和市场需求相差巨大,巨大的市场空缺,使软件测试工程师从初级到高级,只需要 1 年甚至更短的时间来完成。所以作为一名软件测试工程师&#x…

【北亚数据恢复】不认盘的移动硬盘恢复数据案例解决方案

【案例一】 一块西数移动硬盘不小心摔了,插到电脑上就不认盘,之后没在其他的任何操作。这是比较典型的硬盘故障类型:故障原因就是移动硬盘磁头损坏。 北亚数据恢复工程师在用户同意的前提下开盘,对移动硬盘开盘换磁头。&#xff0…

8.5 Spring解决循环依赖的机理(AOP)

8.5 Spring解决循环依赖的机理(AOP) MyAspect Aspect public class MyAspect {After(value "execution(* com.cjf.bean.B.*(..))")public void myAfter(){System.out.println("最终通知的功能.........");} }SpringBean.xml <aop:aspectj-autoproxy&g…

Unity游戏Mod/插件制作教程03 - 插件实例1: HelloWorld

准备工作 作为编程类的教程&#xff0c;果然第一个需要来一个传统项目——HelloWolrd。 在开始之前&#xff0c;我先贴一个链接&#xff0c;这是BepInex官方的开发手册 https://bepinex.github.io/bepinex_docs/v5.0/articles/dev_guide/index.html 有什么问题也可以翻阅官方的…

论文阅读【6】RRN:LSTM论文阅读报告(1)

lstm类似于Simple_RNN,但是又比他复杂很多.我是参考这个视频的老师讲解的,这个老师讲解的非常好.https://www.bilibili.com/video/BV1FP4y1Z7Fj?p4&vd_source0a7fa919fba05ffcb79b57040ef74756 lstm的最重要的设计就是那一条传输带,即为向量CtC_tCt​,过去的信息通过他传送…

跨程序共享数据:Android四大组件之内容提供器

跨程序共享数据&#xff1a;Android四大组件之内容提供器前言七、跨程序共享数据&#xff1a;Android四大组件之内容提供器7.1 内容提供器&#xff08;Content Provider&#xff09;简介7.2 运行时权限&#xff08;软件不能为所欲为&#xff0c;想要什么权限&#xff0c;还得主…

【project 】软件使用

project软件使用 1.如何为某任务或资源创建日历 创建新日历 工具->更改工作时间->新建->定义日历名称&#xff0c;选择“新建基准日历”->根据各承建商的日历创建相应的日历 使用新日历 拷贝各承建商的各项任务到指定的项目计划中&#xff0c;然后&#xff…

基于特征选择的二元蜻蜓算法(Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

C. String Equality(思维)

Problem - 1451C - Codeforces Ashish有两个字符串a和b&#xff0c;每个字符串的长度为n&#xff0c;还有一个整数k。 他想通过对a进行一些&#xff08;可能是零&#xff09;操作&#xff0c;将字符串a转换成字符串b。 在一次操作中&#xff0c;他可以 选择一个索引i&#x…

哪吒汽车的技术发布会都发布了什么?纯干货抢先看

11月21日&#xff0c;哪吒汽车发布了浩智超算、浩智电驱、浩智增程三大技术品牌&#xff0c;并推出三款技术产品&#xff0c;包括智能汽车中央超算平台、800V SiC高性能电驱系统、高效三合一增程器。去年年底&#xff0c;哪吒曾经发布过山海平台&#xff0c;据说是一个支持哪吒…

性能环境搭建(0-CentOS7 安装配置)

1.前言 根据现有的组件&#xff0c;准备动手搭建一套完整的监控环境。既然是练手&#xff0c;还是在虚拟机里自己先练习一下。出了问题也好恢复。所有就先从最基本的开始。那就是操作系统开始搭建玩起来。 2.环境 资源有效利用吧&#xff0c;公司的资源能自由使用的那最方便…

数据结构-复杂度(一)

目录 一、什么是复杂度&#xff1f; 算法效率&#xff1a; 复杂度&#xff1a; 二、复杂度分类 一、时间复杂度 二、空间复杂度&#xff08;Space Complexity&#xff09; 了解数据结构之前需要了解复杂度。 一、什么是复杂度&#xff1f; 在介绍复杂度之前我们现分享一…

CengBox靶机

0x01 信息收集 nmap -sV 10.0.2.6 22 ssh端口&#xff0c;弱口令爆破为主 80 web页面 目录遍历&#xff0c;备份文件查找等 dirsearch -u http://10.0.2.6 获取了目录&#xff0c;发现存在一个maseradmin目录&#xff0c;可能存在些东西&#xff0c;继续扫。 dirsearch -u …

C规范编辑笔记(四)

大家好&#xff0c;今天来给大家分享一下C规范编辑笔记第四篇&#xff0c;距离我们C规范编辑笔记第三篇也快过去了一个月&#xff0c;这次继续分享一波~ 1、以大写形式声明常量&#xff0c; 为避免误解&#xff0c;常量值必须根据其类型使用后缀。这不仅有助于代码阅读&#x…

CSO面对面丨如何通过“联合作战”,加强银行安全体系建设

随着数字化转型的深入&#xff0c;以银行为代表的金融机构不断加码金融科技建设。然而随着线上业务量不断上升&#xff0c;银行面临的安全风险暴露面也愈大、问题愈加复杂。本期腾讯安全《CSO面对面》栏目&#xff0c;邀请到某头部商业银行安全主管&#xff0c;以金融行业的安全…

【Lilishop商城】No1-1.业务了解+划分各模块逻辑

目录 A1.整体业务逻辑 B1.模块整理 C1.运营后台 C2.店铺后台 C3.买方平台 B2.重点模块梳理图 C1.订单模块 C2.退货/退款模块&#xff08;即售后模块&#xff09; C3.促销活动模块 A2.模块划分&#xff08;自己思考的&#xff09; A3.数据结构划分&#xff08;自己思考…

算法设计与分析 SCAU11079 可以移动的石子合并(优先做)

11079 可以移动的石子合并&#xff08;优先做&#xff09; 时间限制:1000MS 代码长度限制:10KB 提交次数:25 通过次数:9 题型: 编程题 语言: G;GCC;VC;JAVA Description 有n堆石子形成一行(a1,a2,…,an&#xff0c;ai为第i堆石子个数)&#xff0c;现要将石子合并成一堆&…

Android App开发手机阅读中贝塞尔曲线的原理讲解及实现波浪起伏动画实战(附源码和演示视频 可直接使用)

需要图片集和源码请点赞关注收藏后评论区留言~~~ 一、贝塞尔曲线的原理 贝塞尔曲线是一种用于二维图形的数学曲线。贝塞尔曲线由节点和线段构成&#xff0c;其中节点是可拖动的支点&#xff0c;而线段彷佛有弹性的牛皮筋。它除了起点和终点之外&#xff0c;不再描绘中间的折现…

嵌入式(驱动开发)(中断处理)

一、什么是中断 一种硬件上的通知机制&#xff0c;用来通知CPU发生了某种需要立即处理的事件 分为&#xff1a; 内部中断 CPU执行程序的过程中&#xff0c;发生的一些硬件出错、运算出错事件&#xff08;如分母为0、溢出等等&#xff09;&#xff0c;不可屏蔽外部中断 外设发…

SpringBoot怎么整合第三方缓存技术/EhCache缓存技术使用以及Redis缓存技术使用怎么在SpringBoot中使用

写在前面&#xff1a; 继续记录自己的SpringBoot学习之旅&#xff0c;这次是SpringBoot应用相关知识学习记录。若看不懂则建议先看前几篇博客&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 3.5 整合第三方技术 3.5.1 缓存 3.5.1.1 介绍 缓…