目录

1、缓存的作用与成本

2、关于缓存双写一致性

2.1 四种更新策略

2.2 最佳实践方案

3、关于缓存穿透

3.1 使用 缓存空对象 方案

3.2 使用 布隆过滤器 方案

4、关于缓存雪崩

5、关于缓存击穿

5.1采用双检加锁策略

5.1.1 使用 互斥锁 方案

5.1.2 使用 逻辑过期 方案

5.2 设置差异失效时间

6、封装 Redis 工具类

6.1 封装 缓存穿透中的缓存空对象 方案

6.2 封装 缓存击穿中的逻辑过期 方案

7、总结

三种淘汰策略


1、缓存的作用与成本

作用: 暂存数据处理结果,并提供下次访问使用。在很多场合,数据的处理或者数据获取可能非常费时,当对这个数据的请求量很大时,频繁的数据处理会消耗大量资源。缓存的作用就是将这些来之不易的数据存储起来,当再次请求此数据时,直接从缓存中获取而省略数据处理流程,从而降低资源的消耗提高响应速度

成本:数据不一致问题,缓存层和数据层有时窗口不一致,和更新策略有关;代码维护成本,原本只需读写MySQL就能实现功能,但加入了缓存之后就需要去维护缓存的数据,增加了代码复杂度


2、关于缓存双写一致性

相关图解:

               

相关代码实现:

  从 redis 中进行查询,若缓存命中,则直接返回对应数据

//1.从 redis 中查询缓存String shopJSON = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);//1.1 若缓存中有店铺的相关信息则进行返回,即缓存命中if(StrUtil.isNotBlank(shopJSON)){//1.2 这里需要将 JSON 类型的数据,转换为 java 类型的数据进行返回Shop shop = JSONUtil.toBean(shopJSON, Shop.class);return Result.ok(shop);}

 若 redis 中命中缓存失败,则需要从 mysql 中进行查询数据返回,并将查询到的数据写回 redis 中作为缓存

//2.缓存未命中,则从 mysql 中查询相关信息Shop shop = shopService.getById(id);//2.1 mysql 中存在相关信息,写入 redis 缓存,并则进行返回if(!Objects.isNull(shop)){stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(shop));}else {//2.2 若不存在,则给出提示信息Result.fail("店铺ID 为"+id+"的信息不存在!");}return Result.ok(shop);

        这时需要考虑到一个问题,在数据库中的数据进行更新时,这时 redis 缓存那边保存的还是旧数据,需要将数据库中的数据更新到 redis 中,以保证双写一致性


 2.1 四种更新策略

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

其中,可能出现的问题:

  1. 先更新mysql的某商品的库存,当前商品的库存是100,更新为99个。
  2. 先更新mysql修改为99成功,然后更新redis。
  3. 此时假设异常出现,更新redis失败了,这导致mysql里面的库存是99而redis里面的还是100 。
  4. 上述发生,会让数据库里面和缓存redis里面数据不一致,读到redis脏数据
  • 先更新缓存,再更新数据库(不太推荐,业务上一般把mysql作为底单数据库,以保证最后解释)

其中,可能出现的问题:

【异常逻辑】多线程环境下,A、B两个线程有快有慢有并行

A update redis  100

B update redis  80

B update mysql 80

A 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数据库

解决方法:使用延时双删策略

  • 先更新数据库,再删除缓存(一般推荐使用这种方法)

其中,可能出现的问题:

假如缓存删除失败或者来不及,导致请求再次访问redis时缓存命中,读取到的是缓存旧值。

解决方法:使用中间件,如MQ消息队列

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

2.2 最佳实践方案

  1. 低一致性需求:使用 redis 自带的内存淘汰机制
  2. 高一致性需求:主动更新,并以超时剔除作为兜底方案

读操作:

  • 缓存命中则直接返回
  • 缓存未命中则查询数据库,并且写入缓存,加上超时时间

写操作:

  • 先写数据库,再删除缓存
  • 确保数据库与 redis 缓存的原子性

代码案例:

@Transactional  //这里进行添加事务public Result updateShop(Shop shop) {if(!Objects.isNull(shop.getId())) {//1.先更新数据库shopService.updateById(shop);//2.再删除 redis 缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());}else {Result.fail("店铺的 ID 不能为空!");}return Result.ok();}

3、关于缓存穿透

定义:当去查询一条记录,先查 redis 中,查询不到,然后再查询 mysql 中,也查询不到;即使这样,请求最终每次都会打到 mysql 数据库中,从而导致后台数据库的压力暴增,redis  形同虚设 ,接下来,介绍对应的解决方案

Redis缓存双写一致性、穿透(布隆)、雪崩、击穿的案例总结相关推荐

  1. 缓存淘汰、缓存穿透、缓存击穿、缓存雪崩、数据库缓存双写一致性

    缓存淘汰 为什么需要缓存淘汰?你需要缓存30G的数据,但是Redis本身只能使用10G的内存,那你就得做个取舍了,毕竟鱼与熊掌不可兼得.为了利益最大化肯定要保留最重要的10个G. Redis本身提供了 ...

  2. 【Redis】--缓存双写一致性分析、解决方案

    目录 一.缓存双写一致性场景 1.先更新数据库,再更新缓存 2.先更新数据库,再删除缓存 2.1.存在的问题一:删除缓存失败,导致Redis和mysql的数据不一致. 2.2.存在的问题二:删除缓存成 ...

  3. 如何保证数据库和缓存双写一致性?

    前言 数据库和缓存(比如:redis)双写数据一致性问题,是一个跟开发语言无关的公共问题.尤其在高并发的场景下,这个问题变得更加严重. 我很负责的告诉大家,该问题无论在面试,还是工作中遇到的概率非常大 ...

  4. 数据库缓存双写一致性的一些个人想法

    数据库缓存双写一致性的一些个人想法 有这么个问题,还是经典面试题: 说我们有个数据库,他的读请求特别多,以至于要在数据库上加一层缓存来抗压,这个都能理解吧. 这里的缓存,可能是和数据库一样的数据,也可 ...

  5. 大厂面试官喜欢这样问Redis,双写一致性、并发竞争、线程模型,我整理好了

    你知道的越多,你不知道的越多 点赞再看,养成习惯 https://github.com/java-已经开源,有面试脑图 前言 正文 上几期吊打系列我们提到了Redis的很多知识,还没看的小伙伴可以回顾 ...

  6. Redis学习总结(13)——分布式之数据库和缓存双写一致性方案解析!

    一.为什么写这篇文章? 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作: 但是在更新缓存方面,对于更新完数据库,是更新缓存 ...

  7. 深入理解分布式之数据库和缓存双写一致性方案解析

    引言 为什么写这篇文章? 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作. 但是在更新缓存方面,对于更新完数据库,是更新缓 ...

  8. 数据库和缓存双写一致性方案解析

    https://blog.csdn.net/z50L2O08e2u4afToR9A/article/details/81008933 https://zhuanlan.zhihu.com/p/4833 ...

  9. 分布式之数据库和缓存双写一致性方案解析

    引言 为什么写这篇文章? 首先,缓存由于其高并发和高性能的特性,已经在项目中被广泛使用.在读取缓存方面,大家没啥疑问,都是按照下图的流程来进行业务操作. 但是在更新缓存方面,对于更新完数据库,是更新缓 ...

最新文章

  1. 7-1 最大子列和问题
  2. 用Java将文件读入字节数组的7个示例
  3. ASP.NET 2.0中使用自定义provider (2)
  4. Ranger知识地图
  5. Document、HTMLDocument关系的探究
  6. django 给单个文件加log_django配置日志模块
  7. matlab求解含两个累加公式的方程,使用fsolve 函数求解含两个参数的多个方程问题,希望有会的人帮...
  8. Dubbo 生态添新兵,Dubbo Admin 发布 v0.1
  9. devc 能优化吗_小网站能做seo优化吗?如何为小公司网站做seo优化?
  10. 开学季Web渗透测试工程师精英班“第5期”来袭,加入即永久观看!
  11. html弄出油画效果,使用HTML5 Canvas实现仿PS图像油画滤镜特效
  12. itools android玩游戏,itools模拟器能玩ios游戏吗?
  13. 第7课:郭盛华课程_Linux系统的常用操作命令
  14. white-space: pre-wrap 会出现首行缩进的解决办法
  15. 开机后电脑只剩计算机和回收站,电脑开机后C盘拒绝访问,图标只剩下此电脑和回收站是怎么回事?...
  16. 曾经开发过Linux Mobile Platform的MiZi被风河收购。
  17. python快速对比两个excel的数据是否一致
  18. gjson - Golang 解析 JSON
  19. Python ACCESS学习(二) 创建文件链接ACCESS数据库
  20. canal 学习笔记

热门文章

  1. LSTM:tf.nn.dynamic_rnn的输出outputs和state含义
  2. [渝粤教育] 南宁职业技术学院 走进东盟 参考 资料
  3. 读王立群《千古一帝秦始皇》有感
  4. SQLite数据库下载安装、配置、测试
  5. Apollo 应用与源码分析:Monitor监控-软件监控-定位、camera、功能安全、数据记录监控
  6. 无传感器磁场定向控制(FOC)风机控制原理
  7. for语句、while语句及循环结束语句的介绍
  8. Teamviewer 开机启动
  9. (思科模拟器)单臂路由配置以及带你去了解它
  10. sentinel 使用示例