如何保证缓存与数据库的双写一致性?

如何保证缓存与数据库的双写一致性?

  • 概述
  • 同步策略
    • 更新缓存还是删除缓存:
    • 先操作数据库还是缓存:
      • 案例一、先删除缓存,在更新数据库
      • 案例二 先操作数据库,再删除缓存
    • 延时双删策略(不推荐)
    • 使用分布式锁实现双写一致性
    • 使用读写锁实现双写一致性
  • 使用消息队列异步通知
  • 订阅Mysql的Binlog文件(可借助Canal来进行)
  • 总结

概述

MySQL 和 Redis 都是常见的数据存储方案,MySQL 用于存储结构化数据,Redis 用于存储非结构化数据。在一些高并发场景下,为了提升系统的性能,我们通常会将数据存储在 Redis 缓存中,并通过 Redis 缓存来提高系统的读取速度。但是,Redis 缓存中的数据是不稳定的,可能会随时被删除或者被更新,因此需要和 MySQL 中的数据进行同步,保证数据的一致性。

但是使用过缓存的人都应该知道,在实际应用场景中,要想实时刻保证缓存和数据库中的数据一样,很难做到。 基本上都是尽可能让他们的数据在绝大部分时间内保持一致,并保证最终是一致的。

同步策略

首先介绍一下双写一致性·当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库

四种同步策略:
想要保证缓存与数据库的双写一致,一共有4种方式,即4种同步策略:
1. 先更新缓存,再更新数据库;
2. 先更新数据库,再更新缓存;
3. 先删除缓存,再更新数据库;
4. 先更新数据库,再删除缓存。

从这4种同步策略中,我们需要作出比较的是:

  • 更新缓存与删除缓存哪种方式更合适?
  • 应该先操作数据库还是先操作缓存?

更新缓存还是删除缓存:

下面,我们来分析一下,应该采用更新缓存还是删除缓存的方式。

  1. 更新缓存
  • 优点:每次数据变化都及时更新缓存,所以查询时不容易出现未命中的情况。
  • 缺点:更新缓存的消耗比较大。如果数据需要经过复杂的计算再写入缓存,那么频繁的更新缓存,就会影响服务器的性能。如果是写入数据频繁的业务场景,那么可能频繁的更新缓存时,却没有业务读取该数据。
  1. 删除缓存
  • 优点:操作简单,无论更新操作是否复杂,都是将缓存中的数据直接删除。
  • 缺点:删除缓存后,下一次查询缓存会出现未命中,这时需要重新读取一次数据库。

从上面的比较来看,一般情况下,删除缓存是更优的方案。

先操作数据库还是缓存:

下面,我们再来分析一下,应该先操作数据库还是先操作缓存。

案例一、先删除缓存,在更新数据库

初始时,缓存和数据库均为10。
在这里插入图片描述
如上图,先删除缓存,再更新数据库,可能会出现的问题:

  1. 线程1删除缓存
  2. 线程2查询缓存未命中,查询数据库
  3. 写入缓存的值为10,
  4. 线程1再进行更新数据库,值为20

此时数据库为更新过的值20,而缓存还是旧值10,此时出现了数据库和缓存数据不一致情况。

案例二 先操作数据库,再删除缓存

在这里插入图片描述
如上图,先删除缓存,再更新数据库,可能会出现的问题:

  1. 线程1查询缓存未命中,查询数据库
  2. 线程2更新数据库为20,
  3. 线程2删除缓存
  4. 线程1写入缓存值为10

此时数据库为更新过的值20,而缓存还是旧值10,此时出现了数据库和缓存数据不一致情况。

经过案例一和案例二的比较,先删除缓存和先更新数据库都会出现问题。

延时双删策略(不推荐)

在写库前后都进行redis.del(key)操作,并且设定合理的超时时间。
在这里插入图片描述

伪代码如下:

public void write( String key, Object data ){
    redis.delKey(key);
    db.updateData(data); 
    Thread.sleep(500);
    redis.delKey(key);
}

问题:这个500毫秒怎么确定的,具体该休眠多久时间呢?

  1. 需要评估自己的项目的读数据业务逻辑的耗时。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
  2. 当然这种策略还要考虑redis和数据库主从同步的耗时。
  3. 另外这种策略也会可能会有脏数据的风险,而且还会消耗不必要的性能。

在实际场景中,并不推荐延时双删策略,一方面可能会有脏数据的风险,而且还会消耗不必要的性能。

虽然先更新数据库,再删除缓存也是会出现数据不一致性的问题,但是在实际中,这个问题出现的概率并不高。因为缓存的写入通常要远远快于数据库的写入,所以在实际中很难出现请求 B 已经更新了数据库并且删除了缓存,请求 A 才更新完缓存的情况。所以,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的。

但是,为了确保万无一失,在更新完缓存时,给缓存加上较短的过期时间,这样即时出现缓存不一致的情况,缓存的数据也会很快过期,对业务还是能接受的。另外在更新缓存中加入过期时间,这样就算出现了缓存和数据库不一致问题,但最终是一致的。

使用分布式锁实现双写一致性

分别在写数据和读数据加分布式锁,保证同一时间只运行一个请求更新缓存(保证读写串行化),就会不会产生并发问题了,这样就能保证redis和mysql的数据强一致性。
在这里插入图片描述
但是这样的话读操作和写操作都需要加锁,效率就会大大降低。其实在真实场景中放入缓存中的数据一般是读多写少,如果是读少写多,那完全可以不用缓存,直接操作数据库了。

使用读写锁实现双写一致性

在读多写少的场景下,可以使用读锁和写锁的机制。

  1. 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作,写互斥
  2. 排他锁:独占锁writeLock也加写锁,加锁之后,堵塞其他线程读写操作。
    在这里插入图片描述

使用redisson中的读写锁实现双写一致性

想要拿到共享锁或者排他锁,都需要先拿到读写锁。通过固定代码可以拿到读写锁。

RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("READ_WRITE_LOCK");

随后分别拿到共享锁和排他锁。(注意两个锁需要是同一把读写锁)

RLock readLock = readWriteLock.readLock();
RLock writeLock = readWriteLock.writeLock();

读操作加入读锁(共享锁)

public void getById(Integer id){
  RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("READ_WRITE_LOCK");
  RLock readLock = readWriteLock.readLock();
  try{
    readLock.lock();
    System.out.println("readLock...");
    Item item = (Item) redisTemplate.opsForValue().get("item"+id);
    if(item != null){
      return item;
    }
    item = new Item(id, "手机", "手机", 60.00);
    redisTemplate.opsForValue().set("item"+id, item);
    return item;
  }finally{
    readLock.unlock();
  }
}

写操作加入写锁(排他锁)

public void updateById(Integer id){
  RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("READ_WRITE_LOCK");
  RLock writeLock = readWriteLock.writeLock();
  try{
    writeLock.lock();
    System.out.println("writeLock...");
    Item item = new Item(id, "手机", "手机", 100.00);
    try{
      Thread.sleep(2000);
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    redisTemplate.delete("item"+id);
  }finally{
    writeLock.unlock();
  }
}

可以实现强一致性方案,虽然比分布式锁好一点,但是在高并发场景下性能也比较低。

使用消息队列异步通知

如果允许缓存中的数据在短时间内可以跟数据库数据不一致的情况下,可以使用异步通知的方案,可以保证最终一致性。

为了解决双写一致性的问题,我们可以引入消息队列,比如RabbitMQ,来异步更新Redis。将操作同一资源的请求,打到同一个队列中。

当有数据变动时,我们先操作数据库,然后通过消息队列发送消息到一个缓存更新的队列中,异步更新缓存。这种方式能够让写操作变得更加高效,并且避免了高并发下的缓存与数据库数据不一致的问题。
在这里插入图片描述

订阅Mysql的Binlog文件(可借助Canal来进行)

另一种更为可靠的方法是使用MySQL的binlog。我们可以使用Maxwell或者Canal等工具,实时解析binlog,然后更新Redis。
在这里插入图片描述
这种方案的好处是即使应用程序崩溃,也不会丢失binlog,因此能够保证最终的数据一致性。但是,这种方案的实现比较复杂,需要对MySQL的内部机制有深入的理解。

总结

允许延时一致的业务,采用异步通知

  1. 使用MQ中间件,更新数据之后,通知缓存更新,将操作同一资源的请求,打到同一个队列中。
  2. 利用canal中间件,不需要修改业务代码,伪装为mysql的一个从节点,canal通过读取binlog数据更新缓存

强一致性,采用Redisson提供的读写过

在读多写少的场景下,可以使用读锁和写锁的机制。

  1. 共享锁:读锁readLock,加锁之后,其他线程可以共享读操作,写互斥
  2. 排他锁:独占锁writeLock也加写锁,加锁之后,堵塞其他线程读写操作。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java拆装箱及128陷阱

有以下一段代码: Integer a 123; Integer b 123; int c 123; int d 123; System.out.println(c d); System.out.println(a b); System.out.println(a c); 这段代码运行的结果是什么呢? c d 一定为True。 由于Java中存在自动拆装箱&#xff0…

刷到一个问题还请道友们解疑

问题如上&#xff0c;题目挺简单的&#xff0c;就是插入后排序的思路&#xff0c;我的代码如下&#xff1a; #include <bits/stdc.h>using namespace std; int f(int x,int y){return x < y;//其实要这个没有用&#xff0c;默认是就是从小到大排序 }int main(){int n…

【MySQL】详谈约束

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习计网、mysql和算法 ✈️专栏&#xff1a;MySQL学习 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac…

PostgreSQL中控制文件的解析与恢复

最近遇到有人问起PG中控制文件的一些使用问题,总结了一下。 1、PG控制文件简介 1.1、存储的位置 它的路径位于: 相关信息,可以用命令pg_controldata得到: [10:41:27-postgres@centos2:/var/lib/pgsql/14/data/global]$ pg_controldata -D $PGDATA pg_control version …

git提交和回退

目录 一. git 提交二. git commit 后准备回退&#xff0c;尚未 git push三. git add 添加多余文件 撤销操作四. 更改 Git commit 的默认编辑器五. 撤销某个commit的变更六. 回退到之前的commit状态总结&#xff1a; 一. git 提交 git pull # 更新代码 git status # 查看代码状…

【保姆级讲解如何Stable Diffusion本地部署】

&#x1f308;个人主页:程序员不想敲代码啊&#x1f308; &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家&#x1f3c6; &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提…

rancher2.6部署

rancher2.6部署 1、准备环境镜像 2、部署3、密码获取密码设置新密码 4、设置语言5、导入已有集群 1、准备 环境 docker-ce-20.10.23-3.el8.x86_64.rpm以及依赖rpm kubernetes&#xff1a;v1.23.17 镜像 &#xff08;rancher和k8s有个版本对应关系&#xff0c;rancher2.5就不…

OSCP靶场--GLPI

OSCP靶场–GLPI 考点(CVE-2022-35914 php执行函数绕过ssh端口转发jetty xml RCE) 1.nmap扫描(ssh端口转发) ## ┌──(root㉿kali)-[~/Desktop] └─# nmap 192.168.194.242 -sV -sC --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-26 22:22 EDT Nmap…

第一个JDBC程序

一、JDBC的概念&#xff1a; JDBC 是 Java DataBase Connectivity (Java 数据连接)技术的简称&#xff0c;是一种可用于执行 SQL 语句的 Java API。它由一些 java 语言编写的类和接口组成&#xff1b;程序员通过使用 jdbc 可以方便地将 SQL 语句传送给几乎任何一种数据库。 二…

C++ :STL中vector扩容机制

vector是STL提供的动态数组&#xff0c;它会在内部空间不够用时动态的调整自身的大小&#xff0c;调整过程中会有大量的数据拷贝&#xff0c;为了减少数据拷贝的次数vector会在调整空间的时候尽量多申请一些空间&#xff0c;这些预留出的空间可以很大程度上减少拷贝的发生。 在…

Shadow Tactics

本题链接&#xff1a; 题目&#xff1a; 样例&#xff1a; 输入 1 1 3 3 U 2 2 2 输出 YES 思路&#xff1a; 根据题意&#xff0c;隼人的坐标是不会动的&#xff0c;并且士兵只能直线来回行动。 所以这里我们需要分成三种情况。 1、隼人坐标在士兵走动路线之间&#xff0c;…

linux如何查看编译器支持的C++版本(支持C++11、支持C++14、支持C++17、支持C++20)(编译时不指定g++版本,默认使用老版本编译)

参考:https://blog.csdn.net/Dontla/article/details/129016157 C各个版本 C11 C11是一个重要的C标准版本&#xff0c;于2011年发布。C11带来了许多重要的改进&#xff0c;包括&#xff1a; 智能指针&#xff1a;引入了shared_ptr和unique_ptr等智能指针&#xff0c;用于更好地…

http认证

1.Digest认证 各字段含义&#xff1a; Nonce 服务器直接返回的数据 H1MD5(user”:”realmpassword) H2MD5(method”:”url) method为请求类型、url不包括域名 Nc 指当前的第几次请求&#xff0c;使用8位16进制显示 Cnonce 8位随机字符串 ResponseMD5(H1”:”nonce”:”…

【C++语言】冲突-C语言:命名冲突(输入输出、缺省参数、引用、内联函数)

文章目录 前言正文2. C的输入与输出&#xff1a;3.缺省参数3.1 缺省参数的概念&#xff1a;3.2 缺省参数的分类&#xff1a;全缺省参数&#xff1a;半缺省参数&#xff1a; 4.函数重载4.1 函数重载的概念&#xff1a; 5.引用5.1 引用的基本概念&#xff1a;5.2 引用的特性&…

系统工程学思想

系统工程学思想 大项目或复杂问题的实施和解决&#xff0c;需要按照系统工程学理论进行&#xff0c;以系统的方法完整、全面的分析&#xff0c;而不是零星的处理问题&#xff0c;沿着逻辑推理的路径&#xff0c;去解决哪些原本靠直觉判断处理的问题。 系统分析过程逻辑结构分为…

A Review on Influence Dissemination in Social Networks

Abstract 影响力传播研究是社交网络信息传播的关键问题。由于影响力分析在营销、广告、个性化推荐、舆情监测等方面的现实意义&#xff0c;研究人员从不同角度研究了该问题并提出了解决方案。在本文中&#xff0c;我们回顾了社交网络中的影响力传播&#xff0c;并得出结论&…

淘宝APP详情数据抓取技术揭秘:用Python实现自动化数据获取(附代码实例)

获取淘宝APP详情数据接口通常涉及到网络爬虫技术&#xff0c;因为淘宝作为一个大型电商平台&#xff0c;其数据并不直接对外公开提供API接口供第三方开发者使用。然而&#xff0c;通过模拟浏览器行为或使用淘宝开放平台提供的API&#xff08;如果有的话&#xff09;&#xff0c…

借助剪映软件生成原创视频(真人人声,免VIP)

civilpy&#xff1a;借助各大模型的优点生成原创视频&#xff08;真人人声&#xff09;Plus0 赞同 0 评论文章​编辑 是的&#xff0c;剪映也出了声音克隆了&#xff0c;只需要十几秒的录音就可以克隆自己的声音&#xff0c;虽然微瑕&#xff0c;但是对于不习惯机器音的很多创…

【面试】Elasticsearch 在部署时,对 Linux 的设置有哪些优化方法?

Elasticsearch 在部署时&#xff0c;对 Linux 的设置有哪些优化方法&#xff1f; Elasticsearch是一个分布式搜索和分析引擎&#xff0c;它在Linux环境下的性能和稳定性可以通过一些优化方法进行提升。以下是一些针对Linux环境下Elasticsearch部署的优化方法&#xff1a; 1. 内…

职场人必备!效率翻倍的多微信号必备管理工具大揭秘

在职场中&#xff0c;高效率的工作方式是非常重要的。而为了提高工作效率&#xff0c;合理运用一些工作神器也是必不可少的。今天给大家分享一个多微信号管理工具——微信管理系统&#xff0c;它能够帮助职场人员管理多个微信号&#xff0c;让工作变得更加高效。 首先&#xf…