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

news2024/9/24 19:15:00

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

1、 面试题
只要双写,就一定会有数据一致性问题,那么如何解决一致性问题?
双写一致性,你先动缓存redis还是数据库?为什么?
延时双删做过吗?会有哪些问题?
有这么一种情况,微服务查询redis无mysql有,为保证数据一致性回写redis你需要注意什么?双检加锁策略你了解过吗?如何尽量避免缓存击穿?
redis和mysql双写100%会出纰漏,做不到强一致性,你如何保证最终一致性?

2、缓存双写一致性,谈谈你的理解

如果Redis中有数据
需要和数据库中的值相同

如果Redis中无数据
数据库中的值要是最新值,且准备回写进Redis

缓存按照操作来分,细分2种

只读缓存
读写缓存
● 同步直写策略
○ 写数据库后也同步写Redis缓存,缓存和数据库中的数据一致
○ 对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步直写策略
● 异步缓写策略
○ 正常业务运行中,mysql数据变动了,但是可以在业务上容许出现一定时间后才作用于Redis,比如仓库、物流系统
○ 异常情况出现了,不得不将失败的动作重新修补,有可能需要借助kafka或者RabbitMQ等消息中间件,实现重试重写

在这里插入图片描述
问题,上面业务逻辑你用java代码如何写?

一图教会你如何写

双检加锁策略
多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个互斥锁来锁住它。其他线程走到这一步拿不到锁就等着,等第一个线程查到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

在这里插入图片描述

搜索 
100%

便笺
package 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;

/**
 * @auther zzyy
 * @create 2021-05-01 14:58
 */
@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;
    }

}

 

 


3、数据库和缓存一致性的几种更新策略
目的
达到最终一致性
● 给缓存设置过期时间,定期清理缓存并回写,是保证最终一致性的解决方案
● 我们可以对存入缓存的数据设置过期时间,那么所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要达到了过期时间,则后面的读请求自然会从数据库中读取新值然后回写到缓存中,达到一致性,切记,要以mysql的数据库写入库为准
● 上述方案和后续落地案例是调研后的主流+成熟的做法,但是需要考虑不同公司业务系统的差距,不是100%绝对正确,不保证绝对适配全部情况。

可以停机的情况
● 挂牌报错,凌晨升级,温馨提示,服务降级
● 单线程,这样重量级的数据操作最好不要多线程

4种更新策略

先更新数据库,再更新缓存
在这里插入图片描述

先更新缓存,再更新数据库
在这里插入图片描述

先删除缓存,再更新数据库
在这里插入图片描述
○ 双删方案面试题
■ 这个删除应该休眠多久呢?—>线程A sleep的时间,需要大于线程B读取数据再写入缓存的时间
● 第一种方案:在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,自行评估自己的项目的读数据业务逻辑的耗时,以此为基础来进行估算,然后写数据的休眠时间则再读数据业务逻辑的耗时上加几百毫秒即可
● 第二种方案:新启动一个后台监控程序,比如WatchDog监控程序,会加时
■ 这种同步淘汰策略,吞吐量降低怎么办?---->二次删除使用异步的方式
在这里插入图片描述

先更新数据库,再删除缓存
在这里插入图片描述
● 业务指导思想
○ 微软云
○ 阿里巴巴canal
在这里插入图片描述

● 类似经典的分布式事务问题,只有一个权威答案—>保证最终一致性
○ 流量充值,先下发短信实际充值可能滞后5分钟,可以接受
○ 电商发货,短信下发但是物流明天见

小总结
在大多数业务场景下,优先使用先更新数据库,再删除缓存的方案(先更库—>后删存)。理由如下:
● 先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力导致打满Mysql
● 如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删的等待时间就不好设置。
补充:如果是使用先更新数据库,再删缓存的方案,如果业务层要求必须读取一致性的数据,那么我们就需要再更新数据库时,先在Redis缓存客户端暂停并发读请求,等数据库更新完、缓存值删除后,再读取数据,从而保证数据一致性,这是理论可以达到的效果,但实际是不推荐的,因为在真实生产案例上,分布式下很难做到实时一致性,一般都是最终一致性。
在这里插入图片描述

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

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

相关文章

《算法竞赛·快冲300题》每日一题:“立方体表面距离”

《算法竞赛快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。 所有题目放在自建的OJ New Online Judge。 用C/C、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。 文章目录 题目描述题解C代码Java代码Python代码 “ 立…

作为spring框架的另外的重点AOP的介绍(详细篇)

一.Aop介绍,以及作用范围,和其专业名词的解释 1.什么是Aop? Java Spring中的AOP(面向切面编程)是一种编程范式,用于通过将与核心业务逻辑无关的横切关注点(如日志记录、性能统计、安全控制等&…

BOXTRADE-天启量化分析平台 系统功能预览

BOXTRADE-天启量化分析平台 系统功能预览 系统功能预览 1.登录 首页 参考登录文档 2. A股 行情与策略分析 2.1 A股股票列表 可以筛选和搜索 2.2 A股行情及策略回测 2.2.1 行情数据提供除权和前复权,后复权数据;外链公司信息 2.2.2 内置策略执行结果…

使用 PyTorch 进行高效图像分割:第 2 部分

一、说明 这是由 4 部分组成的系列的第二部分,旨在使用 PyTorch 中的深度学习技术从头开始逐步实现图像分割。本部分将重点介绍如何实现基线图像分割卷积神经网络(CNN)模型。 图 1:使用 CNN 运行图像分割的结果。按从上到下的顺序…

建筑结构健康监测系统,解锁建筑安全监测新模式

随着现代建筑技术的发展,高层、超高层、大型公共建筑以及桥梁等复杂结构的数量不断增加,对建筑结构监测的要求也日益迫切。万宾建筑结构健康监测系统通过先进的传感技术和和数据分析技术来持续监测建筑的结构健康,这种监测的目的是可以识别建…

Cat(4):API介绍—Transaction

1 基本用法 Transaction 适合记录跨越系统边界的程序访问行为,比如远程调用,数据库调用,也适合执行时间较长的业务逻辑监控,Transaction用来记录一段代码的执行时间和次数。 现在我们的框架还没有与dubbo、mybatis做集成,所以我…

网络协议的定义、组成和重要性?

什么是网络协议? 网络协议是在计算机网络中,用于规定通信实体之间进行数据传输和通信的规则集合。网络协议涵盖了各种通信细节,包括数据包格式、错误处理、数据传输速率等,是用于分组交换数据网络的一种协议,其任务仅…

Linux:shell脚本:基础使用(4)《正则表达式-grep工具》

正则表达式定义: 使用单个字符串来描述,匹配一系列符合某个句法规则的字符串 正则表达式的组成: 普通字符串: 大小写字母,数字,标点符号及一些其他符号 元字符:在正则表达式中具有特殊意义的专用字符 正则表…

发掘Win10神奇工具:计划任务程序的自动化魔力

在Windows 10系统中,隐藏着许多不为人知的神奇工具,您了解多少呢?想象一下,如果有一种工具,能够像机器人一样在您设定的时间自动执行各种任务,您会不会觉得它是一件非常实用的利器?今天&#xf…

算法通关村第4关【黄金】| 表达式问题

1. 计算器问题 思路:此题不考虑括号和负数情况,单纯使用栈即可解决。注意的是数字可能是多位数需要保留完整的num, 保留数字的前缀符号,当碰到加号,存进去;当碰到减号,存相反数进去;…

【算法系列篇】双指针

文章目录 前言什么是双指针算法1.移动零1.1 题目要求1.2 做题思路1.3 Java代码实现 2.复写零2.1 题目要求2.2 做题思路2.3 Java代码实现 3.快乐数3.1 题目要求3.2 做题思路3.3 Java代码实现 4.盛最多水的容器4.1 题目要求4.2 做题思路4.3 Java代码实现 5.有效三角形的个数5.1 题…

Windows 10 20H2升级至Windows 11

关于Windows 10 20H2和21H1版本结束支持 在Windows 10中,20H2版本是Windows 10的第十个主要更新。此次升级于2020年10月20日上线。 2020年10月更新中的显著变化包括开始菜单、Microsoft Edge的改进、新的可自定义体验、通知体验的增强等。 然而&#…

Windows防火墙屏蔽恶意TCP连接

关闭所有软件(except 安全),wireshark抓包 set filtertcp,抓取所有tcp包, 抓包文件导出为tcp.txt 过滤出ip address 去掉文件头尾,执行以下程序获得ip address #cut_file.py def copy_first_10_chars(input_file, output_fil…

展会预告 | 图扑与您相约用友 2023 全球商业创新大会

为汇聚商业智慧,释放企业潜能,深入推动企业数智化转型升级,创新客户价值,让数智化在更多的企业成功,由用友主办的“2023 全球商业创新大会”,将于本月 8 月 18 日至 20 日,在上海市“国家会展中…

IT运维:使用数据分析平台监控深信服防火墙(进阶)

概述 本文主要在原文档基础上,进行了字段抽取规则、图表的优化。 字段抽取:原文档使用正则的方式,创建了多个视图,本文将改为正则键值的抽取方式,并介绍键值抽取的适用场景 图表的优化:原文使用多为简单的…

56.linux 进程管理命令和用户管理命令

目录 一、进程管理命令 1.ps 2.pstree 3.kill 4.pkill 5.&后台运行程序 6.jobs 7.fg bg 8.top 二、用户管理命令 1.系统存储用户信息的文件 2.添加新用户 3.修改用户密码 4.删除用户 一、进程管理命令 1.ps 用于查看当前系统中运行的进程信息。它可以…

thinkphp开发的在线学习培训考试模拟考试做题练习系统带商城功能证书管理课程系统

thinkphp开发的在线学习培训考试模拟考试做题练习系统带商城功能证书管理课程系统 1、做题界面 2、前端UI的展示 3、带商城购物功能

springboot 通过博途获取plc点位的数据

springboot 通过博途获取plc点位的数据 maven依赖<dependency><groupId>com.github.dathlin</groupId><artifactId>HslCommunication</artifactId><version>3.6.0</version> </dependency>这个版本尽量是新版本&#xff0c;不…

微服务-GateWay(网关)

所谓网关是什么意思&#xff1f; 相当于就是你们小区家的保安&#xff0c;进出小区都得获得保安的同意&#xff0c;守护你们小区的生命财产健康&#xff0c;网关也是如此&#xff0c;对每个请求都严格把关&#xff0c;将合法的或者是获得权限的请求进入服务器 网关的功能&…

【CTF-web】bugku-成绩查询(sql注入)

题目链接&#xff1a;https://ctf.bugku.com/challenges/detail/id/84.html 判断注入点 查看网页源码可知输入数据通过POST发送到index.php并显示出查询结果&#xff0c;可能需要sql注入。 如上图所示&#xff0c;当id为1时返回名字为“龙龙龙”的成绩单。 再测试&#xff0c…