Redis(十三)缓存双写一致性策略

文章目录

  • 概述
    • 示例
  • 缓存双写一致性
    • 缓存按照操作来分,细分2种
      • 读写缓存:同步直写策略
      • 读写缓存:异步缓写策略
      • 双检加锁策略
  • 数据库和缓存一致性更新策略
    • 先更新数据库,再更新缓存
    • 先更新缓存,再更新数据库
    • 先删除缓存,再更新数据库
      • 解决方案:延时双删策略
    • 先更新数据库,再删除缓存
    • 解决方案
    • 总结
  • 问题示例

概述

示例

在这里插入图片描述

缓存双写一致性

  1. 如果redis中有数据
    需要和数据库中的值相同
  2. 如果redis中无数据
    数据库中的值要是最新值,且准备回写redis

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

  1. 只读缓存
  2. 读写缓存

读写缓存:同步直写策略

  1. 写数据库后也同步写redis缓存,缓存和数据库中的数据一致;
  2. 对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步直写策略

读写缓存:异步缓写策略

  1. 正常业务运行中,mysql数据变动了,但是可以在业务上容许出现一定时间后才作用于redis,比如仓库、物流系统
  2. 异常情况出现,不得不将失败的动作重新修补,有可能需要借助kafka或者RabbitMQ等消息中间件实现重试重写

双检加锁策略

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

在这里插入图片描述

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:";@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{redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS);}}}}return user;}}

数据库和缓存一致性更新策略

给缓存设置过期时间,定期清理缓存并回写,是保证最终一致性的解决方案。
我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存,达到一致性,切记,要以mysql的数据库写入库为准。

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

异常问题
1 先更新mysql的某商品的库存,当前商品的库存是100,更新为99个。
2 先更新mysql修改为99成功,然后更新redis。
3 此时假设异常出现,更新redis失败了,这导致mysql里面的库存是99而redis里面的还是100 。
4 上述发生,会让数据库里面和缓存redis里面数据不一致,读到redis脏数据

异常问题

【先更新数据库,再更新缓存】,A、B两个线程发起调用【正常逻辑】1 A update mysql 1002 A update redis 1003 B update mysql 804 B update redis 80=============================
【异常逻辑】多线程环境下,A、B两个线程有快有慢,有前有后有并行1 A update mysql 1003 B update mysql 804 B update redis 802 A update redis 100=============================最终结果,mysql和redis数据不一致,o(╥﹏╥)o,mysql80,redis100

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

mysql一般作为底单数据库,保证最后解释

【先更新缓存,再更新数据库】,AB两个线程发起调用【正常逻辑】1 A update redis 1002 A update mysql 1003 B update redis 804 B update mysql 80====================================
【异常逻辑】多线程环境下,AB两个线程有快有慢有并行A update redis  100B update redis  80B update mysql 80A update mysql 100
----mysql100,redis80

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

异常问题

(1)请求A进行写操作,删除redis缓存后,工作正在进行中,更新mysql......A还么有彻底更新完mysql,还没commit(2)请求B开工查询,查询redis发现缓存不存在(被A从redis中删除了)(3)请求B继续,去数据库查询得到了mysql中的旧值(A还没有更新完)(4)请求B将旧值写回redis缓存(5)请求A将新值写入mysql数据库 上述情况就会导致不一致的情形出现。 
时间线程A线程B出现的问题
t1请求A进行写操作,删除缓存成功后,工作正在mysql进行中…
t21 缓存中读取不到,立刻读mysql,由于A还没有对mysql更新完,读到的是旧值 2 还把从mysql读取的旧值,写回了redis1 A还没有更新完mysql,导致B读到了旧值 2 线程B遵守回写机制,把旧值写回redis,导致其它请求读取的还是旧值,A白干了。
t3A更新完mysql数据库的值redis是被B写回的旧值,mysql是被A更新的新值。出现了,数据不一致问题。

解决方案:延时双删策略

在这里插入图片描述
在这里插入图片描述
问题示例:

线程A sleep的时间,就需要大于线程B读取数据再写入缓存的时间。
这个时间怎么确定呢?

  • 第一种方法:
    在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,自行评估自己的项目的读数据业务逻辑的耗时,
    以此为基础来进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒即可。
    这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。
  • 第二种方法:
    新启动一个后台监控程序,比如后面要讲解的WatchDog监控程序,会加时

这种方法吞吐降低怎么办?
使用异步
使用WatchDog

在这里插入图片描述

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

时间线程A线程B出现的问题
t1更新数据库中的值…
t2缓存中立刻命中,此时B读取的是缓存旧值。A还没有来得及删除缓存的值,导致B缓存命中读到旧值。
t3更新缓存的数据

假如缓存删除失败或者来不及,导致请求再次访问redis时缓存命中,读取到的是缓存旧值。
微软云案例:https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside
阿里巴巴canal:

解决方案

在这里插入图片描述

  1. 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如使用Kafka/RabbitMQ等)。
  2. 当程序没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
  3. 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作,此时,我们也可以保证数据库和缓存的数据一致了,否则还需要再次进行重试
  4. 如果重试超过的一定次数后还是没有成功,我们就需要向业务层发送报错信息了,通知运维人员。

最终解决方案:最终一致性
案例:

  1. 流量充值,先下发短信实际充值可能滞后5分钟,可以接受
  2. 电商发货,短信下发但是物流明天见

总结

优先使用先更新数据库,再删除缓存的方案(先更库→后删存)。理由如下:

  1. 先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力导致打满mysql。
  2. 如果业务应用中读取数据库和写缓存的时间不好估算,那么,延迟双删中的等待时间就不好设置。

如果业务层要求必须读取一致性的数据,那么我们就需要在更新数据库时,先在Redis缓存客户端暂停并发读请求,等数据库更新完、缓存值删除后,再读取数据,从而保证数据一致性,这是理论可以达到的效果,但
实际,不推荐,因为真实生产环境中,分布式下很难做到实时一致性,一般都是最终一致性

如果使用先更新数据库,再删除缓存的方案

策略高并发多线程条件下问题现象解决方案
先删除redis缓存,再更新mysql缓存删除成功但数据库更新失败Java程序从数据库中读到旧值再次更新数据库,重试
缓存删除成功但数据库更新中…有并发读请求并发请求从数据库读到旧值并回写到redis,导致后续都是从redis读取到旧值延迟双删
先更新mysql,再删除redis缓存数据库更新成功,但缓存删除失败Java程序从redis中读到旧值再次删除缓存,重试
数据库更新成功但缓存删除中…有并发读请求并发请求从缓存读到旧值等待redis删除完成,这段时间有数据不一致,短暂存在。

问题示例

  1. 你只要用缓存,就可能会涉及到redis缓存与数据库双存储双写,你只要是双写,就一定会有数据一致性的问题,那么你如何解决一致性问题?
  2. 双写一致性,你先动缓存redis还是数据库mysql哪一个?why?
  3. 延时双删你做过吗?会有哪些问题?
  4. 有这么一种憶况,微服务查询redis无,mysql有,为保证数据双写一致性回写redis你需要注意什么?双减加锁策略了解过吗?如何尽量避免缓存击穿
  5. redis和mysql双写100%会出纰漏,做不到强一致性,你如何保证最终一致性?

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法学习——LeetCode力扣链表篇1

算法学习——LeetCode力扣链表篇1 203. 移除链表元素 203. 移除链表元素 - 力扣(LeetCode) 描述 给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val val 的节点,并返回 新的头节点 。 示例 示例 …

【Jenkins】pipeline基本使用

目录 一、pipeline 二、创建pipeline项目 1、安装pipeline插件 2、创建pipeline项目 三、pipeline语法 1、pipeline组成 2、agent:指定流水线的执行位置,流水线中每个阶段都必须在某个地方执行 3、stage:阶段,代表流水线的…

Qt 常见容器类用法(二)

目录 QList类 QLinkedList类 QList类 对于不同的数据类型&#xff0c;QList<T>采取不同的存储策略&#xff0c;存储策略如下&#xff1a; 如果T是一个指针类型或指针大小的基本数据类型(该基本类型占有的字节数和指针类型占有的字节数相同)&#xff0c;QList<T>…

负重20kg复合翼垂直起降无人机应用,复合翼无人机技术分析

主要任务应用 1.管线巡查 挂载可见光/红外二合一光电载荷和小型SAR设备&#xff0c;对既定线路进行昼夜巡视侦察&#xff0c;利用图像实时传回指挥控制中心&#xff0c;可用于石油管路、电力线路、舰艇航线及周围态势感知&#xff0c;利于依据现场实情进行战略决策和指令传达…

如何使用Python + 百度翻译API 自动大批量免费翻译Excel文件中的外语内容

手里有一个Excel文件,包括了大量的亚马逊德语搜索词(关键词),每个单元格1个,需要翻译为中文。但是文件大小超过了10M,不能使用百度或Google免费的文档功能,如果手工一个个的翻译然后粘贴又太麻烦,于是想到用Python加免费翻译API完成。 一、openpyxl库 用Python编辑处…

基于SpringBoot+Vue的外卖点餐管理系统

末尾获取源码作者介绍&#xff1a;大家好&#xff0c;我是墨韵&#xff0c;本人4年开发经验&#xff0c;专注定制项目开发 更多项目&#xff1a;CSDN主页YAML墨韵 学如逆水行舟&#xff0c;不进则退。学习如赶路&#xff0c;不能慢一步。 目录 一、项目简介 二、开发技术与环…

【深度学习】Softmax实现手写数字识别

实训1&#xff1a;Softmax实现手写数字识别 相关知识点: numpy科学计算包&#xff0c;如向量化操作&#xff0c;广播机制等 1 任务目标 1.1 简介 本次案例中&#xff0c;你需要用python实现Softmax回归方法&#xff0c;用于MNIST手写数字数据集分类任务。你需要完成前向计算…

Springboot+vue的企业财务管理系统(有报告)。Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的企业财务管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的企业财务管理系统&#xff0c;采用M&#xff08;model&…

SpringBoot Security安全认证框架初始化流程认证流程之源码分析

SpringBoot Security安全认证框架初始化流程&认证流程之源码分析 以RuoYi-Vue前后端分离版本为例分析SpringBoot Security安全认证框架初始化流程&认证流程的源码分析 目录 SpringBoot Security安全认证框架初始化流程&认证流程之源码分析一、SpringBoot Security安…

国外邮箱是什么?功能、作用和价格

不同于我们熟悉的国内邮箱服务&#xff08;如163、QQ邮箱等&#xff09;&#xff0c;国外邮箱通常指的是海外提供商所提供的电子邮件服务&#xff0c;如谷歌的Gmail、微软的Outlook、雅虎的Yahoo Mail等&#xff0c;当然也有特殊的比如Zoho Mail邮箱&#xff0c;国内外双版本&a…

Spring Data Envers 数据审计实战

随着各行各业信息化发展&#xff0c;决策者们越来越意识到数据版本追踪的重要性&#xff0c;尤其是上市公司&#xff0c;数据对于他们尤为重要。考虑到研发成本&#xff0c;对重要表单数据支持页面级的修改历史查看、对所有业务数据支持DB级的版本查看是一个不错的选择。 对于…

全套电气自动化样例图纸分享,使用SuperWorks自动化版免费设计软件!

今天给大家分享一套完备的电气自动化样例图纸&#xff0c;结构准确、内容清晰&#xff0c;适合初学者入门操作练习。 整套图纸包含图纸目录、原理图、端子列表、连接列表、元件列表、接线图&#xff0c;具有较高的参考价值&#xff0c;请大家点击自行下载文件&#xff01; 1e8…