Redis之缓存双写一致性理论分析

news/2025/4/1 13:27:38/

何为缓存双写一致性?
在这里插入图片描述
问题: 上面业务逻辑你用java代码如何写?

面试题
在这里插入图片描述
解决方案以及策略的探讨
双写一致性,谈谈你的理解。

  • 如果redis中有数据,需要和数据库中的值相同
  • 如果redis中无数据,数据库中的值是最新值,且准备回写redis
  • 缓存按照操作来分,细分2种:只读缓存与读写缓存
  • 读写缓存分为同步直写策略与异步缓写策略
  • 同步直写策略:数据库后也同步写redis缓存缓存数据库中的数据一致。对于读写缓存来说,要想保证缓存数据库中的数据一致,就要采用同步直写
  • 异步缓写策略:正常业务运行中,mysql数据变动了,但是可以在业务上容许出现一定时间后才作用于redis,比如仓库、物流系统。异常情况出现了,不得不将失败的动作重新修补,有可能需要借助Kafka或者Rocketmq等消息中间件,实现消息重写。

一图代码你如何写
在这里插入图片描述
案例:业务逻辑并没有写错,但对于小厂QPS小于1000可以使用,但大厂不行
在这里插入图片描述
在高并发的情景下,统统将请求打到mysql,mysql的数据库的压力就比较大,统统写入redis中,容易出现数据覆盖的情景。理由:从mysql中查数据,以及回写到redis这两步不是原子操作。容易在高并发的情况下,被多个线程打爆。

解决方案:双检加锁策略

  • 多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它
  • 其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存
  • 后面的线程进来发现已经缓存了,就直接走缓存
    在这里插入图片描述
    根据上面的方案解释,改写后的代码:加锁版本
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:";@Resourceprivate UserMapper userMapper;@Resourceprivate RedisTemplate redisTemplate;/*** 业务逻辑没有写错,对于小厂中厂(QPS《=1000)可以使用,但是大厂不行* @param id* @return*/public User findUserById(Integer id){User user = null;String key = CACHE_KEY_USER+id;//1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysqluser = (User) redisTemplate.opsForValue().get(key);if(user == null){//2 redis里面无,继续查询mysqluser = userMapper.selectByPrimaryKey(id);if(user == null){//3.1 redis+mysql 都无数据//你具体细化,防止多次穿透,我们业务规定,记录下导致穿透的这个key回写redisreturn 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等待一下,避免击穿mysqlsynchronized (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;}}

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

目的:达到最终一致性

在这里插入图片描述
可以停机的情况:
挂牌报错、凌晨升级、温馨提示、服务降级
单线程,这样重量级的数据操作最好不要多线程。

4种更新策略

  • 先更新数据库,再更新缓存
    异常问题1:redis更新失败了
    在这里插入图片描述
    异常问题2:多线程的乱序问题
    在这里插入图片描述
  • 先更新缓存,再更新数据库
    不太推荐,业务上一般将mysql作为底单数据库,保证最后解释
    异常问题:多线程的问题
    在这里插入图片描述
  • 先删除缓存,再更新数据库(适合读多写少的业务情景)
    异常问题:多线程数据更新失败或者超时时,容易将旧数据写入缓存
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    上面的三步串接起来看的流程
    在这里插入图片描述
    总结:先删除缓存,再更新数据库
    如果数据库更新失败或超时或返回不及时,导致B线程请求访问缓存时发现redis里面没数据,缓存缺失,B再去读取mysql时,从数据库中读取到旧值,还写回redis,导致A白干了
    解决方案:延迟双删
    在这里插入图片描述
    延迟双删的面试题
    1.这个删除操作的休眠期需要多久呢?
    在这里插入图片描述

2.这种同步淘汰策略,吞吐量降低该怎么办?
在这里插入图片描述

3.后续看门狗watchdog源码分析

  • 先更新数据库,再删除缓存
    微软方案
    微软解决方案链接在这里插入图片描述

    存在的问题
    在这里插入图片描述

    解决方案:
    在这里插入图片描述
    最终一致性的业务举例:
    在这里插入图片描述
    缓存数据库一致性更新策略小总结
    在这里插入图片描述

在这里插入图片描述
视频讲解
redis双写一致性问题


http://www.ppmy.cn/news/1584210.html

相关文章

Java 实现将Word 转换成markdown

日常的开发中&#xff0c;需要将word 等各类文章信息转换成格式化语言&#xff0c;因此需要使用各类语言将word 转换成Markdown 1、引入 jar包 <dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version&g…

如何在 Postman 中正确设置 Session 以维持用户状态?

在 Postman 里面设置有 session 的请求。如果你还不知道什么是 session&#xff0c;那么请看这里—— session 是一种记录客户端和服务器之间状态的机制&#xff0c;用于保持用户的登录状态或者其他数据&#xff0c;从而让用户在不同页面之间保持一致的体验。 Postman 设置带 …

谈谈空间复杂度考量,特别是递归调用栈空间消耗?

空间复杂度考量是算法设计的核心要素之一&#xff0c;递归调用栈的消耗问题在前端领域尤为突出。 以下结合真实开发场景进行深度解析&#xff1a; 一、递归调用栈的典型问题 1. 深层次DOM遍历的陷阱 // 危险操作&#xff1a;递归遍历未知层级的DOM树 function countDOMNode…

vue3中watch 函数参数说明

source&#xff1a;这是要监听的数据源&#xff0c;可以是一个 getter 函数、一个 ref 对象、一个 reactive 对象或者一个包含多个数据源的数组。在子组件示例中&#xff0c;() > props.parentValue 就是一个 getter 函数&#xff0c;它返回 props.parentValue 的值&#xf…

游戏引擎学习第187天

看起来观众解决了上次的bug 昨天遇到了一个相对困难的bug&#xff0c;可以说它相当棘手。刚开始的时候&#xff0c;没有立刻想到什么合适的解决办法&#xff0c;所以今天得从头开始&#xff0c;逐步验证之前的假设&#xff0c;收集足够的信息&#xff0c;逐一排查可能的原因&a…

211 本硕研三,已拿 C++ 桌面应用研发 offer,计划转音视频或嵌入式如何规划学习路线?

今天给大家分享的是一位粉丝的提问&#xff0c;211 本硕研三&#xff0c;已拿 C 桌面应用研发 offer&#xff0c;计划转音视频或嵌入式如何规划学习路线&#xff1f; 接下来把粉丝的具体提问和我的回复分享给大家&#xff0c;希望也能给一些类似情况的小伙伴一些启发和帮助。 …

清华大学.智灵动力-《DeepSeek行业应用实践报告》附PPT下载方法

导 读INTRODUCTION 今天分享是由清华大学.智灵动力&#xff1a;《DeepSeek行业应用实践报告》&#xff0c;主要介绍了DeepSeek模型的概述、优势、使用技巧、与其他模型的对比&#xff0c;以及在多个行业中的应用和未来发展趋势。为理解DeepSeek模型的应用和未来发展提供了深入的…

Java 中装饰者模式与策略模式在埋点系统中的应用

前言 在软件开发中&#xff0c;装饰者模式和策略模式是两种常用的设计模式&#xff0c;它们在特定的业务场景下能够发挥巨大的作用。本文将通过一个实际的埋点系统案例&#xff0c;探讨如何在 Java 中运用装饰者模式和策略模式&#xff0c;以及如何结合工厂方法模式来优化代码…