Redis缓存更新策略、详解并发条件下数据库与缓存的一致性问题以及消息队列解决方案

news2024/11/18 23:33:38

0、前言

        我们知道,缓存由于在内存中,数据处理速度比直接操作数据库要快很多,因此常常将数据先读到缓存中,再进行查询、更新等操作。
        但与之而来的问题就是,内存中的数据不仅没有持久化,而且需要保证redis和数据库中数据的一致性,针对这个问题,redis如何保证这样的一致性有以下几种策略。

1、Write Back(写回)策略

        实际开发中最不常用的策略,它仅针对非敏感数据、一致性要求不强的数据,才有可能采用。实际开发不采用。

        Write Back(写回)策略先把数据读入redis,在更新数据的时候,只更新缓存,同时将缓存数据设置为脏的,然后立马返回,并不会更新数据库。对于数据库的更新,会通过批量异步更新的方式进行,例如设置定时任务进行更新。

        例如,对于博客浏览量这样的数据,我们采用写回策略,即使多个用户并发访问,我们每次只要把缓存中的浏览量更新即可,这种 写回策略 非常适合发生大量写操作的场景。

        也就是说,读写都在redis中进行,然后异步地更新回数据库来保持一致性、持久化。

        明显的缺点:带来的问题是,数据不是强一致性的,而且会有数据丢失的风险。因为缓存一般使用内存,而内存是非持久化的,所以一旦缓存机器掉电,就会造成原本缓存中的脏数据丢失。

2、Read/Write Through(读穿 / 写穿)策略

 (1)Read Through 策略

        先查询缓存中数据是否存在,如果存在则直接返回,如果不存在,则由缓存组件负责从数据库查询数据,并将结果写入到缓存组件,最后缓存组件将数据返回给应用。

(2)Write Through 策略

当有数据更新的时候,先查询要写入的数据在缓存中是否已经存在:

  • 如果缓存中数据已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中,然后缓存组件告知应用程序更新完成。
  • 如果缓存中数据不存在,直接更新数据库,然后返回;

 3、Cache Aside 旁路缓存 策略(实际开发常用)

        实际开发中,前两种策略都用不了,而采用旁路缓存策略,只不过有一些难度和注意点。

先说正确结论:

写策略的步骤:

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

读策略的步骤:

  • 如果读取的数据命中了缓存,则直接返回数据;
  • 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。

  (1)数据库和缓存都要更新?

        如果叛逆一点,更新数据库,更新缓存,会带来怎样的并发问题呢?
        借用小林coding的时序图如下:
        假设请求A、B同时对数据更新,顺序如下,在并发情况下,有可能先更新的请求A还没有更新完的时候,请求B就把缓存都更新完事了,然后A再更新缓存。
        可见,这样会造成数据库为2、缓存为1,也就是不一致状况。而如果先更新缓存再更新数据库也是同理的,仍然有数据不一致问题

         

(2)改进:只更新数据库,不更新缓存了,直接把缓存中的数据删了

        反正就算redis里没数据,查询时也会从数据库里查出来放在redis里,那我直接不更新了!把数据删了,到时候再读不就好了!这就是Cache Aside 策略。

        但有1个问题:更新数据库 删除缓存 这两个步骤的顺序该如何呢?

         <1> 假设我们先删除缓存,引用小林的图片:线程A先删除缓存再更新数据库为“21”,但由于更新写入数据库的速度是慢很多的,很可能中间出现了请求B在做查询,从而读取到还未更新的值“20”,并把缓存更细。从而导致不一致问题,这是不允许的。

          <2> 这次改邪归正,我们先更新数据库,再删除缓存:有人会觉得请求A如果去查询数据时,如果缓存未命中,在把数据写回redis的过程中,线程B过来先更新再删除,那就会导致如下的不一致情况了吗?!
        但实际上这样的情况很少,根本原因在于update数据库的速度 比 update缓存的速度 要慢得多。

        也就是说,黄色线条中间,更新缓存的时间间隔是很短的,而更新数据库的时间相对要慢得多,因此这种并发问题很罕见,还是能保证一致性的。

(3)再改进:如果“删除缓存”这个步骤失败了怎么办?

        为了确保万无一失,我们可以给缓存数据加了过期时间,就算在这期间存在缓存数据不一致,但过期时间到了会自动清除redis的key,这样也能避免删除失败的问题,达到最终一致。
        但问题在于,如果删除失败需要等待过期时间,数据的时效性、一致性就不强了,有可能明明更新了数据,查询显示出来却要过一段时间才生效,这对敏感业务来说是有影响的!

        解决方案使用消息队列实现异步处理

        在消费者线程中,尝试删除缓存。
        如果删除失败,则根据任务是否在消息队列中进行判断,若在队列中,则继续重试;否则报错。
        如果删除成功,才将任务从消息队列中移除。示例代码如下:

import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisException;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class CacheManager {
    private Jedis redisClient;
    private BlockingQueue<String> messageQueue;

    public CacheManager() {
        // 初始化Redis连接和消息队列
        redisClient = new Jedis("localhost");
        messageQueue = new LinkedBlockingQueue<>();

        // 创建并启动消费者线程
        Thread consumerThread = new Thread(new Consumer());
        consumerThread.start();
    }

    public void deleteCache(String key) {
        // 将任务添加到消息队列中
        String task = key;
        try {
            messageQueue.put(task);
            System.out.println("Added cache delete task for key: " + key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    String task = messageQueue.take();

                    // 尝试删除缓存
                    try {
                        // 删除缓存的操作,此处为示例代码,根据实际情况进行修改
                        redisClient.del(task);
                        System.out.println("Deleted cache for key: " + task);

                    } catch (JedisException e) {
                        // 删除失败,重试或报错
                        if (messageQueue.contains(task)) {
                            // 仍在队列中,继续重试
                            System.out.println("Failed to delete cache for key: " + task + ", retrying...");
                            messageQueue.put(task);
                        } else {
                            // 不在队列中,报错
                            System.out.println("Failed to delete cache for key: " + task + ", max retries exceeded. Reporting error...");
                        }
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        CacheManager cacheManager = new CacheManager();

        // 示例使用
        cacheManager.deleteCache("user:1");
        cacheManager.deleteCache("user:2");
    }
}

4、小结

        本文通过介绍多种缓存更新策略,以及深入理解了实际开发中常用的旁路缓存策略所遇到的问题,并通过消息队列进行改进,实现了缓存与数据库的一致性。

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

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

相关文章

Dajngo02_第一个Django案例

Dajngo02_第一个Django案例 经过之前学习&#xff0c;我们已经可以创建Django环境 现在开始尝试快速使用Django开发一个案例 案例&#xff1a;利用Django实现一个查看当前时间的web页面。 在django中要提供数据展示给用户,一般情况下我们需要完成3个步骤&#xff1a; 在urls.…

如何选择合适的预测性维护工具和平台

随着技术的不断进步&#xff0c;预测性维护&#xff08;Predictive Maintenance&#xff0c;简称PdM&#xff09;已经成为许多企业提高生产效率、减少停机时间和维护成本的核心策略。然而&#xff0c;选择适合自己业务需求的PdM工具和平台可能并不容易。本文将为您提供一些关键…

传统机器学习总结以及深度学习初识

传统机器学习总结以及深度学习初识 文章目录 前言一、传统机器学习总结1.1. 监督学习算法&#xff08;Supervised Learning&#xff09;1.2. 无监督学习算法&#xff08;Unsupervised Learning&#xff09; 二、深度学习初识三、github与gitee的介绍3.1. GitHub&#xff1a;3.2…

Sqlserver 监控使用磁盘空间情况

最近遇到一个小问题&#xff1a;为了保存以往的一些数据&#xff0c;间了大量临时表&#xff0c;导致SQLserver 数据增长过快&#xff0c;不得不想个办法监控磁盘空间使用情况。 网上一般有几种办法&#xff1a; 一是使用 dm_os_volume_stats函数&#xff0c;缺点是 无法获取非…

【实践篇】MySQL执行计划详解

文章目录 本文知识大纲速览1. 前言2. 基本介绍1. 什么是执行计划2. 如何查看执行计划3. 执行计划的组成部分 3. 执行计划的关键元素1. id2. select_type3. table:4. type:5. possible_keys:6. key:7. key_len8. ref:9. rows:10. Extra 4. 底层原理5. 执行计划示例解读本文知识图…

本地引入 Axios 报错

目录 报错信息&#xff1a; 报错截图&#xff1a; ​编辑报错原因&#xff1a; 解决方法&#xff1a; ​编辑运行结果成功&#xff1a; 报错信息&#xff1a; Cannot read properties of undefined (reading post) TypeError: Cannot read properties of undefined (rea…

SpringBoot,Mybatis 使用Java8(JSR310)时间日期规范

目录 一. 依赖二. 前台三. Controller&#xff0c;Form&#xff0c;Service四. 数据库类型五. 效果 一. 依赖 ⏹若使用的是SpringBoot <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifac…

Microsoft 365跨平台协同办公功能,实现Mac、iOS、Windows用户的实时无缝协作

Microsoft 365 for Mac(Office 365)现已更新&#xff0c;最新版本的Microsoft 365 现已支持跨平台协同办公&#xff0c;接下来为你介绍一些使用office 365 Mac版进行创作及写作的好方法。 Microsoft 365 在全平台共用相同的代码库&#xff0c;这意味着使用 Mac、ios 和Windows…

【日积月累】SpringBoot启动流程

目录 SpringBoot启动流程 1.前言2.构造一个SpringApplication的实例&#xff0c;完成初始化的工作SpringApplication实例构造完之后调用run方法&#xff0c;启动SpringApplication3.SpringBoot启动代码SpringBootConfigurationComponentScanEnableAutoConfiguration 总结参考…

随手笔记(四十六)——idea source root错乱

一般问题会出现在这里&#xff0c;写着别的项目的项目名&#xff0c;就是因为reload了别的项目的maven文件&#xff0c;借鉴了很多网上的说法&#xff0c;比如改project Structure里面改子项目的source。确实讲得挺好&#xff0c;就是不会用&#xff1b;所以最后的解决方案就是…

多云系列|10个关键的多云战略:简介

随着VMware继续向客户介绍多云问题以及VMware跨云服务在云智能计算历程中的优势&#xff0c;有一个问题经常被提及&#xff0c;“我如何开始&#xff1f;”。本博客系列旨在为客户提供指导&#xff0c;并回顾多云的十大领域&#xff0c;介绍我们应该关注哪些方面。此外&#xf…

引领UI设计生产工具进入AI时代,猿辅导旗下Motiff发布三大AI功能

近期&#xff0c;IXDC 2023国际体验设计大会在北京国家会议中心举行&#xff0c;共邀请全球800企业&#xff0c;1000名设计师共襄主题为“设计领导力”的创新盛会。作为全球最具影响力的创新设计大会之一&#xff0c;大会围绕创新、系统、商业三个关键维度&#xff0c;结合在AI…

MySQL优化第二篇

MySQL优化第二篇 性能分析小表驱动大表慢查询日志日志分析工具mysqldumpslow Show Profile进行SQL分析&#xff08;重中之重&#xff09; 七种JOIN 1、inner join &#xff1a;可以简写为join&#xff0c;表示的是交集&#xff0c;也就是两张表的共同数据 sql语句&#xff1a…

Recognize Anything:一个强大的图像标记模型

Recognize Anything是一种新的图像标记基础模型&#xff0c;与传统模型不同&#xff0c;它不依赖于手动注释进行训练;相反&#xff0c;它利用大规模的图像-文本对。RAM的开发过程包括四个关键阶段: 通过自动文本语义解析获得大规模的无标注图像标签。结合标题和标注任务&#…

网络电视盒子哪个品牌好?测评工作室深入分析电视盒子排名

电视盒子只需要联网就可以收看海量资源&#xff0c;不需要每月缴费&#xff0c;玩游戏、上网课、K歌都不在话下&#xff0c;对新手来说电视盒子如何选择&#xff1f;网络电视盒子哪个品牌好&#xff1f;工作室购入了最热销的15款电视盒子经过多角度对比后整理了电视盒子排名&am…

Linux内核源码分析 (B.x)Linux页表的映射

Linux内核源码分析 (B.x)Linux页表的映射 文章目录 Linux内核源码分析 (B.x)Linux页表的映射一、ARM32页表1、页表术语2、虚拟地址到物理地址转换3、一级页表项4、二级页表项 二、ARM64页表1、ARMv8-A架构2、4KB大小页4级映射 三、Linux内核中关于页表的函数和宏1、查询页表2、…

第三方ipad笔哪个牌子好用?开学季比较好用的电容笔

新学期有什么电容笔值得入手&#xff1f;这款平替电容笔&#xff0c;名为Apple Pencil&#xff0c;唯一的区别就是它的压力感应功能&#xff0c;同时拥有重力压感以及倾斜压感&#xff0c;而平替电容笔仅只拥有倾斜压感一种功能&#xff0c;不过它的压力感应能力很强&#xff0…

034:vue项目利用qrcodejs2生成二维码示例

第034个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

无涯教程-JavaScript - IF函数

描述 如果条件为TRUE,则IF函数返回一个值,如果条件为FALSE,则返回另一个值。 语法 IF (logical_test, value_if_true, [value_if_false]) 争论 Argument描述Required/Optionallogical_testThe condition you want to test.Requiredvalue_if_trueThe value that you want re…

藿香正气水泡脚火了!谁都可以“插一脚”吗?

白露身不露&#xff0c;寒露脚不露 眼见着凉凉秋意脚步将至 不少人又把泡脚养生提上了日程 不过&#xff0c;用藿香正气水泡脚你有尝试过吗&#xff1f; 有人说泡完能祛湿 可有人居然腹泻了这是怎么回事&#xff1f; &#x1f447;&#x1f447;&#x1f447; 藿香正气水…