缓存双写一致性之更新策略探讨

news2024/11/27 4:39:35

问题由来

数据redis和MySQL都要有一份,如何保证两边的一致性。

  • 如果redis中有数据:需要和数据库中的值相同
  • 如果redis中没有数据:数据库中的值是最新值,且准备会写redis

缓存操作分类

  1. 自读缓存
  2. 读写缓存:
    (一)同步直写策略:
    写数据后也同步写redis缓存,缓存和数据库中的数据一致;
    对于读写缓存来说,要想报增缓存和数据库中的数据一致,就要采用同步直写策略。
    (二)异步缓写策略
    正常业务运行中,mysql数据变动了,但是可以在业务上容许出现一定时间后才作用于redis,比如长裤、物流系统;
    异常情况出现了,不得不将失败的动作重新修补,有可能需要节奏Kafka或者RbbitMQ等消息中间件,实现重试重写。

一致性问题:

在这里插入图片描述

ckage com.atguigu.redis.service;

import com.atguigu.redis.entities.User;
import com.atguigu.redis.mapper.UserMapper;
import io.swagger.models.auth.In;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 
 * 不能直接用哦,理解高并发下“双检加锁”的思路。
 */
@Service
@Slf4j
public class UserService {
    public static final String CACHE_KEY_USER = "user:";
    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 业务逻辑没有写错,对于小厂中厂(QPS《=1000)可以使用,但是大厂不行
     * @param id
     * @return
     */
    public User findUserById(Integer id)
    {
        User user = null;
        String key = CACHE_KEY_USER+id;

        //1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql
        user = (User) redisTemplate.opsForValue().get(key);

        if(user == null)
        {
            //2 redis里面无,继续查询mysql
            user = userMapper.selectByPrimaryKey(id);
            if(user == null)
            {
                //3.1 redis+mysql 都无数据
                //你具体细化,防止多次穿透,我们业务规定,记录下导致穿透的这个key回写redis
                return user;
            }else{
                //3.2 mysql有,需要将数据写回redis,保证下一次的缓存命中率
                redisTemplate.opsForValue().set(key,user);
            }
        }
        return user;
    }


    /**
     * 加强补充,避免突然key失效了,打爆mysql,做一下预防,尽量不出现击穿的情况。
     * @param id
     * @return
     */
    public User findUserById2(Integer id)
    {
        User user = null;
        String key = CACHE_KEY_USER+id;

        //1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql,
        // 第1次查询redis,加锁前
        user = (User) redisTemplate.opsForValue().get(key);
        if(user == null) {
            //2 大厂用,对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql
            synchronized (UserService.class){
                //第2次查询redis,加锁后
                user = (User) redisTemplate.opsForValue().get(key);
                //3 二次查redis还是null,可以去查mysql了(mysql默认有数据)
                if (user == null) {
                    //4 查询mysql拿数据(mysql默认有数据)
                    user = userMapper.selectByPrimaryKey(id);
                    if (user == null) {
                        return null;
                    }else{
                        //5 mysql里面有数据的,需要回写redis,完成数据一致性的同步工作
                        redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS);
                    }
                }
            }
        }
        return user;
    }
}

数据库和缓存一致性的几种策略

目的:总之我们要到达最终的一致性。

(一)停机运维

  • 挂牌报错,凌晨升级,温馨提示,服务降级
  • 单线程,这样重量级的数据操作最好不要用多线程

(二)不停机的四种策略

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

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

③先删除缓存,再更新数据库(延伸:延迟双删

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

阿里巴巴的canal等中间件就是类似的思想,通过binlog日志去更新消息、缓存,这些中间件去订阅binlog的日志。
比如:在这里插入图片描述

总结:如果我们想着A/B等多个线程去竞争,无论如何都有可能导致不一致。但一般系统都已数据库为最终解释权,这样3和4的方案会好许多,最不推荐的是第二种。
1 先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力导致打满mysql。
2 如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删中的等待时间就不好设置。

如果业务叫非要一致性:那就别用缓存了。加一个页面返回也是一个很好的解决方案,不用太纠结这肉眼难见的事件。当然异常了还是得处理。

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

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

相关文章

关于vuex的使用

1.首先安装vuex npm install vuex --save 这时如果直接安装vuex,不指定版本的话,就会直接安装最新的vuex的版本。所以会出现报错。 报错就安装这个 npm install --save vuex3 2.创建文件夹, 有的时候安装好会自动创建vuex的文件夹 &#xf…

Python解题 - CSDN周赛第35期 - 不算题解的题解

本期四道题还是全考过,题解在网上也都搜得到。。。只好继续水一份不算题解的题解。 第一题:交换后的or 给定两组长度为n的二进制串,请问有多少种方法在第一个串中交换两个不同位置上的数字,使得这两个二进制串“或”的结果发生改…

案例01-修改数据redis没有同步更新

目录 一:背景介绍 二:思路&方案 三:过程 1.修改数据没有删除缓存 2.修改数据删除了缓存 四:总结 五:升华 一:背景介绍 redis中存储了关于一个课程下多个班级的信息。但是难免会在一个课程下添加新…

pandas 数据预处理+数据概览 处理技巧整理(持续更新版)

这篇文章主要是整理下使用pandas的一些技巧,因为经常不用它,这些指令忘得真的很快。前段时间在数模美赛中已经栽过跟头了,不希望以后遇到相关问题的时候还去网上查(主要是太杂了)。可能读者跟我有一样的问题&#xff0…

程序员养发神器:拒绝加班熬夜,告别秃头!

身为一个程序员,每天的工作就是写代码和吹牛逼,但是代码写多了,都没有多少让自己吹的时间了。摸鱼时间少是我们太菜了吗?可不要小瞧自己,可能是你没掌握方法。 我自己本身就是一个十分疯狂的工具收集者,收…

实在智能RPA数字员工竞技“宝罗杯”机器人创新总决赛,斩获佳绩!

近日,由中国钢铁工业协会和中国自动化学会指导,中国宝武钢铁集团有限公司主办、宝信软件承办的机器人行业领域的“宝罗杯”机器人创新大赛总决赛在中国宝武上海总部圆满收官。 此次大赛旨在充分凝聚社会智力,聚焦工业机器人的应用场景&#x…

es深度分页原因概念及处理方法

概述 当使用es分页查询的时候,如果查询的数据太靠后了,就会产生深度分页问题。 假设es有3个节点,node1,node2,node3 查询 limti 50000,50 假设请求的是node1,此时会在每个节点上抓出 50050条数据,然后在node1汇总排序&#xff0…

【设计模式】装饰器模式

装饰器模式 以生活中的场景来举例,一个蛋糕胚,给它涂上奶油就变成了奶油蛋糕,再加上巧克力和草莓,它就变成了巧克力草莓蛋糕。 像这样在不改变原有对象的基础之上,将功能附加到原始对象上的设计模式就称为装饰模式(D…

如何查看磁盘空间并挂载磁盘

df -h内容参数含义Filesystem文件系统Size分区大小1k-blocks单位是1KB(使用df查看)Used已用容量Avail还可用的容量Use%已用百分比Mounted on挂载点du -h查看某目录下占用空间最多的文件或目录。取前10个。需要先进入该目录下。du -cks * | sort -rn | head -n 10参数含义-s对每…

腾讯游戏,“迷失”自己

【潮汐商业评论/原创】“那个号我忘记密码了,你等我换个新号跟你玩”。这是Lynn《王者荣耀》双排队友常说的话。因为未成年,账号只有周末能玩,而且只有两小时。所以Lynn的这位网友,经常用家长的手机号注册游戏账号,但是…

Yarn调度器和调度算法

目录 1 先进先出调度器(FIFO) 2 容量调度器(Capacity Scheduler) 3 公平调度器(Fair Scheduler) 缺额: 公平调度器队列资源分配方式 公平调度器资源分配算法 Hadoop作业调度器主要有三种&…

分库分表原理

一、数据库瓶颈 会导致数据库的活跃连接数增加,进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是,可用数据库连接少甚至无连接可用。接下来就可以想象了吧(并发量、吞吐量、崩溃)。 IO瓶颈-分库和垂直分表…

探索测试的一些总结

1)探索性测试与脚本化测试的主要区别:1)探索性测试将更多更高的认知水平的工作放在测试执行,而脚本化测试则更关注测试设计;2)前者更强调测试活动的并行和相互反馈(学习、设计、执行与结果分析等),而后者的测试活动是相对串行的。 2)脚本化测…

Grafana系统的备份、恢复、迁移

Grafana系统的备份、恢复、迁移 1. 备份Grafana相关数据 首先先关闭Grafana服务(systemctl stop grafana-server),到目录下备份以下文件或者目录: 备份grafana目录中的grafana.db(一般情况下路径:/var/l…

电子技术——数字IC技术,逻辑电路和设计方法

电子技术——数字IC技术,逻辑电路和设计方法 在我们之前的学习中,我们学习了CMOS技术,然而CMOS技术并不是唯一的数字逻辑技术,因此,本节系统的介绍当今使用的数字技术和逻辑电路族。 数字IC技术和逻辑电路族 逻辑电…

k8s--services(微服务)

文章目录一、k8s网络通信service和iptables的关系二、services1.简介2.默认3.IPVS模式的service4.clusterip5.headless6.从外部访问service的三种方式(1)nodeport(2)loadbalancer7.metallb一、k8s网络通信 k8s通过CNI接口接入其他…

tf_nndistancen 安装

为了评估MMD,断断续续装了4天,踩了几乎所有的坑,终于装上了QAQ 1. 库链接:pointnet-autoencoder/tf_ops/nn_distance at master charlesq34/pointnet-autoencoder GitHub 2. 安装TensorFlow,我的环境是cuda 11.5 ,…

【Go】用Go在命令行输出好看的表格

用Go在命令行输出好看的表格前言正文生成Table表头设置插入行表格标题自动标号单元格合并列合并行合并样式设置居中设置数字自动高亮标红完整Demo代码结语前言 最近在写一些运维小工具,比如批量进行ping包的工具,实现不困难,反正就是ping&am…

java(Class 常用方法 获取Class对象六种方式 动态和静态加载 类加载流程)

ClassClass常用方法获取Class对象六种方式哪些类型有Class对象动态和静态加载类加载流程加载阶段连接阶段连接阶段-验证连接阶段-准备连接阶段-解析初始化阶段获取类结构信息Class常用方法 第一步:创建一个实体类 public class Car {public String brand "宝…

mysql数据库innodb存储引擎之事务原理

事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为一个整体一起向系统提交或撤销操作要求,即这些操作要么同时成功,要么同时失败。 四大特性: redo log和undo log:一致性、原子性…