从实战出发,聊聊缓存数据库一致性

news2024/9/29 21:18:32

在云服务中,缓存是极其重要的一点。所谓缓存,其实是一个高速数据存储层。当缓存存在后,日后再次请求该数据就会直接访问缓存,提升数据访问的速度。但是缓存存储的数据通常是短暂性的,这就需要经常对缓存进行更新。而我们操作缓存和数据库,分为读操作和写操作。

读操作的详细流程为,请求数据,如缓存中存在数据则直接读取并返回,如不存在则从数据库中读取,成功之后将数据放到缓存中。

写操作则又分为以下 4 种:

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

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

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

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

一些一致性要求不高的数据,如点赞数等,可以先更新缓存,然后再定时同步到数据库。而在其它情况下,我们通常会等数据库操作成功,再操作缓存。

下面主要介绍更新数据库成功后,更新缓存和删除缓存这两个操作的区别和改进方案。

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

先更新数据库,再删除缓存,这种模式也叫 cache aside,是目前比较流行的处理缓存数据库一致性的方法。
它的优点是:

  • 出现数据不一致的概率极低,实现简单

  • 由于不更新缓存,而是删除缓存,在并发写写情况下,不会出现数据不一致的情况

出现数据不一致的情况出现在并发读写的场景下,详情可见下图:

这种情况发生的概率比较低,必须要在某⼀时间区间同时存在两个或多个写⼊和多个读取,所以大部分业务都容忍了这种小概率的不一致。

虽然发生的概率较低,但还是有一些方案可以让影响降到更低。

优化方案

第一种方案为:采用较短的过期时间来减少影响。这种方法有两个缺点:

  • 删除后,读请求会 miss

  • 如果缓存不一致,不一致的时间取决于过期时间设置

第二种方案则是采用延迟双删的策略,比如:1分钟以后删除缓存。这种做法也存在两个缺点:

  • 删除缓存之前的时间里可能会有不一致

  • 删除后,读请求会 miss

第三种方案为双更新策略,思路与延迟双删策略差不多。不同的点是,此方案不删除缓存而是更新缓存,所以读请求就不会发生 miss。但是另一个缺点还是存在。

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

相比先更新数据库再删除缓存的操作,先更新数据库再更新缓存的操作可以避免用户请求直接打到数据库,进而导致缓存穿透的问题。

此方案是更新缓存,我们需要关注并发读写和并发写写两个场景下导致的数据不一致。

先来看看并发读写的情况,步骤如下图所示:

可以看到由于 4 和 5 操作步骤都设置了缓存,如果步骤4发生在步骤5之前,那么会出现旧值覆盖新值的情况,也就是缓存不一致的情况。这种情况只需要修改一下步骤5,便可解决。

优化方案

可以通过在第五步不要 set cache,改用 add cache,redis 中使用 setnx 命令来进行优化。修改后步骤示意图如下:

解决完了并发读写场景导致的数据不一致,再来看看并发写写情况导致的数据不一致问题。

出现不一致的情况如下图所示,Thread A 比 Thread B 先更新完 DB,但是 Thread B 却先更新完缓存,这就导致缓存会被 Thread A 的旧值所覆盖。

这种情况也是有方法可以优化的,下面介绍两个主流方法:

  • 使用分布式锁

  • 使用版本号

使用分布式锁

要解决并发读写的问题,第一个思路就是消灭并发写。而使用分布式锁,让写操作排队执行,理论上就可以解决并发写的问题,但现在并没有可靠的分布式锁实现方案。

不管是基于 Zookeeper,etcd 还是 redis 实现分布式锁,为了防止程序挂掉而锁不能释放,我们都会给锁设置租约/过期时间,想象一种场景:如果进程卡顿几分钟(虽然概率较低),导致锁失效,而其它线程获取到锁,此时就又出现了并发读写的场景了,还是有可能会造成数据不一致。

使用版本号

并发写导致的数据不一致,是因为低版本覆盖了高版本。那么我们可以想办法不让这种情况发生,一种可行的方案是引入版本号,如果写入的数据低于现版本号,则放弃覆盖。

缺点:

  • 应用层维护版本的代价很大,大规模落地很难

  • 需修改数据模型,添加版本

  • 每次需要修改,让版本自增

不管是更新缓存还是删除缓存,优化以后都将出现数据不一致的概率降到最低了。但是有没有一种办法既简单,又不会出现数据不一致的场景呢。下面就介绍一下 Rockscache。

Rockscache

简介

Rockscache 也是一种保持缓存一致性的方法,它采用的缓存管理策略是:更新数据库后,将缓存标记为删除。主要通过以下两个方法来实现:

  • Fetch 函数实现了前面的查询缓存

  • TagAsDeleted 函数实现了标记删除的逻辑

在运行时只要读数据时调用 Fetch,并且确保更新数据库之后调用 TagAsDeleted,就能够确保缓存最终一致。这一策略有 4 个特点:

  • 不需要引入版本,几乎可以适用于所有缓存场景

  • 架构上与"更新 DB 后删除缓存”一样,无额外负担

  • 性能高:变化只是将原来的 GET/SET/DELETE,替换为 Lua 脚本

  • 强一致方案的性能也很高,与普通的防缓存击穿方案一样

在 Rockscache 策略中,缓存中的数据是包含几个字段的 hash:

  • value:数据本身

  • lockUtil:数据锁定到期时间,当某个进程查询缓存无数据,那么先锁定缓存一小段时间,然后查询 DB,然后更新缓存

  • owner:数据锁定者 uuid

证明

因为 Rockscache 方案并不更新缓存,所以只要确保并发读写数据一致性即可。下面来看看 Rockscache 是怎么解决数据不一致的问题,先回忆一遍 cache aside 模式导致的数据不一致的原因。

结合 cache aside 模式出现数据不一致的场景,来讲讲 Rockscache 是怎么解决的。

我们要解决的核心问题是,防止旧值写入到缓存中。Rockscache 的解决方案是这样的:

  • 查询请求,如果缓存中读不到数据,还要做一个操作:锁定缓存,为key设置一个uuid(代码示例:https://github.com/dtm-labs/rockscache/blob/main/client.go#L191)

  • 写请求在删除缓存的时候,需要把锁删了(代码示例:https://github.com/dtm-labs/rockscache/blob/main/client.go#L96)

  • 读请求在设置缓存的时候,通过uuid比对,发现上锁的不是自己,说明有写请求把数据更新了,则放弃修改缓存(代码示例:https://github.com/dtm-labs/rockscache/blob/main/client.go#L160)

至此我们已经完成了 rockscache 策略下的缓存更新。不过和其他缓存更新策略一样,我们都默认操作数据库成功后,操作缓存肯定成功。但是这是不对的,在实际操作过程即便操作数据库成功,也可能出现缓存操作失败的情况,因此可以通过以下 3 种方式来保证缓存更新成功:

  • 本地消息表

  • 监听 binlog

  • dtm 的二阶段消息

除了缓存更新,Rockscache 还有以下两种功能:

  • 防止缓存击穿

  • 防止防止穿透和缓存雪崩

这都是非常实用的功能,推荐大家实际使用操作试试看。

参考资料

  • https://dtm.pub/app/cache.html

  • https://smartkeyerror.oss-cn-shenzhen.aliyuncs.com/Psyduck/redis/%E7%BC%93%E5%AD%98%E6%9B%B4%E6%96%B0%E7%9A%84%E7%AD%96%E7%95%A5.pdf

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

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

相关文章

Linux常用命令——lsb_release命令

在线Linux命令查询工具 lsb_release 显示发行版本信息 补充说明 LSB是Linux Standard Base的缩写,lsb_release命令用来显示LSB和特定版本的相关信息。如果使用该命令时不带参数,则默认加上-v参数。 -v 显示版本信息。 -i 显示发行版的id。 -d 显示该…

2023 Real World CTF 体验赛 --- wp

文章目录misc🐑了拼🐑webEvil MySQL ServerBe-a-Language-ExpertBe-a-Wiki-HackerYummy ApiApacheCommandTextmisc 🐑了拼🐑 游戏类题目,直接打开js文件搜索rwctf,发现flag rwctf{wellcome_to_the_rwct…

跟着开源项目学java8-从支持最大密码重试次数的提交看redis的场景化使用和基于jdk的schedule的异步延迟日志记录策略

我们这里要实现的功能是登录时添加账号登录错误时最大错误次数和锁定时间,功能不复杂,这次提交里面我们主要来看下一个项目里面一个业务功能怎样写更加优雅 核心实现 我们先来看核心实现的思路 首先是 login 方法重写,进入 loadUserByUser…

【vue2】Vue Cli脚手架与VueTools的安装详解

🥳博 主:初映CY的前说(前端领域) 🌞个人信条:想要变成得到,中间还有做到! 🤘本文核心:Vue Cli脚手架与VueTools的安装详解 目录 一、vue-cli脚手架工具的安装及文件介绍 1.v…

ArcGIS基础实验操作100例--实验72土地利用变化分析

本实验专栏参考自汤国安教授《地理信息系统基础实验操作100例》一书 实验平台:ArcGIS 10.6 实验数据:请访问实验1(传送门) 高级编辑篇--实验72 土地利用变化分析 目录 一、实验背景 二、实验数据 三、实验步骤 (1&…

ArcGIS 制图流程 非常详细

如何在ArcMap中从头到尾制作一幅专题图?你可以看本编教程,从准备数据到最终导出成图,一步一步进行操作,一定可以教会你。 至于为何使用英文版软件,你如果作图就会知道一般经纬度都是利用英文显示,不然最终…

微信小程序实现左边图片右边文字效果

实现的效果&#xff1a; xml布局文件&#xff1a; <view class"chuxingItem"> <image class"img" src"/pages/image/banche.png"></image> <view style"font-size: 30rpx;margin-left: 15rpx;">班车查询</…

Leetcode:513. 找树左下角的值(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 递归&#xff1a; 原理思路&#xff1a; 层序遍历&#xff1a; 原理思路&#xff1a; 问题描述&#xff1a; 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至…

kaggle平台学习复习笔记 | Data Visualization | Seaborn

目录1.hello Seaborn2.Line Charts3.Bar Charts and Heatmaps4.Scatter PlotsDistributions5.Choosing Plot Types and Custom Styles1.hello Seaborn import pandas as pd pd.plotting.register_matplotlib_converters() import matplotlib.pyplot as plt %matplotlib inline…

阿里云服务器安装mysql数据库教程

阿里云服务器怎么安装mysql数据库&#xff1f;阿里云服务器ECS如何安装mysql数据库教程。主机教程网下面就来分享一下阿里云服务器安装mysql数据库教程。 第一步 1、登录个人的阿里云服务管理终端 2、点击进入远程连接&#xff0c;输入之前设置的远程登录密码&#xff08;如…

能够激发创作灵感的笔记软件,强大在哪里? #RoamResearch

今天的人类知识体系&#xff0c;已经汇聚成了一个浩瀚的信息与思想的海洋&#xff0c;信息量呈指数级增长&#xff0c;如果能够解决潜在的协作问题&#xff0c;这会给个体带来巨大的机会。怎么有效利用信息&#xff1f;如何搭建自己的知识体系&#xff1f;这些都是信息爆炸的时…

在ubuntu系统上用pyinstaller打包yolov5项目代码

目录0. 背景1. 创建虚拟环境2. pyinstaller打包2.1. 生成并修改spec文件2.2. 重新生成二进制文件3. 测试0. 背景 最近需要在ubuntu 18.04上将自己写的一些基于yolov5的项目代码打包成二进制文件&#xff0c;方便部署的同时也尽量减少暴露源码。 参考网上的很多教程&#xff0…

Node.JS(4)--模块、exports和module

文章目录模块核心模块文件模块基本数据类型引用数据类型exports和module.exports的关系模块 分为两大类 核心模块 由node引擎提供的模块 核心模块的标识就是模块的名字 var fsrequire("fs");文件模块 由用户自己创建的模块文件模块的标识就是文件的路径&#x…

一个专注推荐.Net开源项目的榜单

大家好&#xff0c;我是编程乐趣&#xff0c;从7月份开始推荐开源项目&#xff0c;已经推荐了接近100个开源项目了&#xff0c;其中绝大部分是有关.Net的开源项目&#xff0c;也受到大家非常多人的喜欢。 由于公众号不方便查询&#xff0c;很多人又想了解更多的开源项目&#…

C++【多线程】

文章目录一、什么是线程二、创建线程一、什么是线程 线程在进程内部执行&#xff0c;是OS调度的基本单位。 在堆区上存在下面一种数据结构 struct vm_area_struct{ //用来记录这块空间的起始和终止。unsigned long vm_start;unsigned long vm_end;//其实这是一个双向链表中的结…

判断环形链表是否有环??返回环形链表的入口点!!

上次笔者写了一篇大概有7个题的链表相关的题目解析&#xff0c;感觉还不错&#xff0c;感兴趣的各位老铁&#xff0c;可以点一下链接进行欣赏&#xff1a;做几个与链表相关的题吧&#xff01;https://blog.csdn.net/weixin_64308540/article/details/128550685?spm1001.2014.3…

CPT205-Computer Graphics(2)

文章目录7. Hierarchical Modelling7.1 Local and world co-ordinate frames of reference7.1.1 Relative motion7.2 Linear modelling7.3 Hierarchical modelling7.3.1 Hierarchical transformations8. Lighting and Materials8.1 Lighting sources8.1.1 Point light8.1.2 Dir…

如何将revit的内建模型导出使用?项目族管理功能介绍

Revit中内建模型建模是我们常用的功能&#xff0c;每次建模完成后都可以在另一个项目中使用&#xff0c;但是前提是需要导出后再载入才能在另一个项目中使用。今天就教教大家如何将Revit的内建模型单独导出&#xff0c;方便大家下次使用在其它项目。 如何将revit的内建模型单独…

计算机组成原理_总线的性能指标

计算机组成原理总目录总线的性能指标 一、总线周期和时钟周期 总线周期 总线周期通常指的是CPU完成一次访问内存或I/O端口操作所需要的时间 其中包括申请阶段、寻址阶段、传输阶段和结束阶段时钟周期 时钟周期是处理操作最基本的单位&#xff0c;在一个时钟周期内&#xff0c;…

【历史上的今天】1 月 9 日:iPhone 问世;iTunes 发布;激光打印机的发明者出生

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2023 年 1 月 9 日&#xff0c;在 1978 年的这段时间&#xff0c;我国恢复了研究生制度&#xff0c;这一年&#xff0c;共录取了 10500 多名研究生。研究生教育的中断和…