缓存击穿,缓存穿透和缓存雪崩

2024-05-13 10:08
文章标签 缓存 击穿 雪崩 穿透

本文主要是介绍缓存击穿,缓存穿透和缓存雪崩,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

什么是缓存?

缓存,就是数据交换的缓冲区,针对服务对象的不同(本质就是不同的硬件)都可以构建缓存。
目的是,把读写速度慢的介质的数据保存在读写速度快的介质中,从而提高读写速度,减少时间消耗。 例如:
  • CPU 高速缓存 :高速缓存的读写速度远高于内存。
    • CPU 读数据时,如果在高速缓存中找到所需数据,就不需要读内存
    • CPU 写数据时,先写到高速缓存,再回写到内存。
  • 磁盘缓存:磁盘缓存其实就把常用的磁盘数据保存在内存中,内存读写速度也是远高于磁盘的。
    • 读数据时,从内存读取。
    • 写数据时,可先写到内存,定时或定量回写到磁盘,或者是同步回写。

为什么要用缓存?

使用缓存的目的,就是提升读写性能。而实际业务场景下,更多的是为了提升读性能,带来更好的性能,更高的并发量。
日常业务中,我们使用比较多的数据库是 MySQL,缓存是 Redis 。Redis 比 MySQL 的读写性能好很多。那么,我们将 MySQL 的热点数据,缓存到 Redis 中,提升读取性能,也减小 MySQL 的读取压力。例如说:
  • 论坛帖子的访问频率比较高,且要实时更新阅读量,使用 Redis 记录帖子的阅读量,可以提升性能和并发。
  • 商品信息,数据更新的频率不高,但是读取的频率很高,特别是热门商品。

分布式缓存系统面临的问题

缓存与数据库双写不一致

一般来说,如果允许缓存可以稍微的跟数据库偶尔有不一致的情况,也就是说如果你的系统不是严格要求 “缓存+数据库” 必须保持一致性的话,最好不要做这个方案,即:读请求和写请求串行化,串到一个内存队列里去。
串行化可以保证一定不会出现不一致的情况,但是它也会导致系统的吞吐量大幅度降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
最经典的就是缓存+数据库读写的模式(Cache Aside Pattern)。
  • 读的时候,先读缓存,缓存没有的话,再读数据库,然后取出数据后放入缓存,同时返回响应。
  • 更新的时候,先更新数据库,然后再删除缓存。

缓存穿透和缓存雪崩缓存穿透

概念:
指查询一个一定不存在的数据,由于缓存是不命中时被动写,即从 DB 查询到数据,则更新到缓存中,并且出于容错考虑,如果从 DB 查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要去 DB 查询,失去了缓存的意义。在流量大时,DB 可能就挂掉了。
举个栗子。系统A,每秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。数据库 id 是从 1 开始的,而黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。
解决方案:
方案一: 缓存空对象,当从 DB 查询数据为空,我们仍然将这个空结果进行缓存,具体的值需要使用特殊的标识, 能和真正缓存的数据区分开,另外将其过期时间设为较短时间。
方案二: 使用布隆过滤器,在缓存的基础上,构建布隆过滤器数据结构,在布隆过滤器中存储对应的 key,如果存在,则说明 key 对应的值为空。这样整个业务逻辑如下:
  • 根据 key 查询缓存,如果存在对应的值,直接返回;如果不存在则继续执行。
  • 根据 key 查询缓存在布隆过滤器的值,如果存在值,则说明该 key 不存在对应的值,直接返回空,如果不存在值,继续向下执行。
  • 查询 DB 对应的值,如果存在,则更新到缓存,并返回该值,如果不存在值,则更新缓存到布隆过滤器中,并返回空。

缓存雪崩

概念:
缓存由于某些原因无法提供服务,所有请求全部达到 DB 中,导致 DB 负荷大增,最终挂掉的情况。
比如,对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。
解决方案:
  • 缓存高可用:使用 Redis Sentinel 等搭建缓存的高可用,避免缓存挂掉无法提供服务的情况,从而降低出现缓存雪崩的情况。
  • 使用本地缓存:如果使用本地缓存,即使分布式缓存挂了,也可以将 DB 查询的结果缓存到本地,避免后续请 求全部达到 DB 中。当然引入本地缓存也会有相应的问题,比如本地缓存实时性如何保证。对于这个问题,可以使用消息队列,在数据更新时,发布数据更新的消息,而进程中有相应的消费者消费该消息,从而更新本地缓存;简单点可以通过设置较短的过期时间,请求时从 DB 重新拉取。
  • 请求限流和服务降级:通过限制 DB 的每秒请求数,避免数据库挂掉。对于被限流的请求,采用服务降级处理,比如提供默认的值,或者空白值。

缓存击穿

概念:
某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。
解决方案:
  • 使用互斥锁 (mutex key):感知到缓存失效,去查询 DB 时,使用分布式锁,使得只有一个线程去数据库加载数据,加锁失败的线程,等待即可。
    • 获取分布式锁,直到成功或超时。如果超时,则抛出异常,返回。如果成功,继续向下执行。
    • 再去缓存中。如果存在值,则直接返回;如果不存在,则继续往下执行。因为,获得到锁,可能已经被“那个”线程去查询过 DB ,并更新到缓存中了。
    • 查询 DB ,并更新到缓存中,返回值。
  • 手动过期:redis 上从不设置过期时间,功能上将过期时间存在 key 对应的 value 里,如果发现要过期,通过一个后台的异步线程进行缓存的构建,也就是“手动”过期。

缓存并发竞争

某个时刻,多个系统实例都去更新某个 key。可以基于 zookeeper 实现分布式锁。每个系统通过 zookeeper 获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 key,别人都不允许读和写。
要写入缓存的数据都是从 mysql 里查出来的,都得写入 mysql 中,写入 mysql 中的时候必须保存一个时间戳,从 mysql 查出来的时候,时间戳也要查出来。
每次要写之前,先判断一下当前这个 value 的时间戳是否比缓存里的 value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
更多免费技术资料可关注:annalin1203

这篇关于缓存击穿,缓存穿透和缓存雪崩的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/985404

相关文章

Java实现本地缓存的常用方案介绍

《Java实现本地缓存的常用方案介绍》本地缓存的代表技术主要有HashMap,GuavaCache,Caffeine和Encahche,这篇文章主要来和大家聊聊java利用这些技术分别实现本地缓存的方... 目录本地缓存实现方式HashMapConcurrentHashMapGuava CacheCaffe

如何更改pycharm缓存路径和虚拟内存分页文件位置(c盘爆红)

《如何更改pycharm缓存路径和虚拟内存分页文件位置(c盘爆红)》:本文主要介绍如何更改pycharm缓存路径和虚拟内存分页文件位置(c盘爆红)问题,具有很好的参考价值,希望对大家有所帮助,如有... 目录先在你打算存放的地方建四个文件夹更改这四个路径就可以修改默认虚拟内存分页js文件的位置接下来从高级-

PyCharm如何更改缓存位置

《PyCharm如何更改缓存位置》:本文主要介绍PyCharm如何更改缓存位置的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录PyCharm更改缓存位置1.打开PyCharm的安装编程目录2.将config、sjsystem、plugins和log的路径

JSR-107缓存规范介绍

《JSR-107缓存规范介绍》JSR是JavaSpecificationRequests的缩写,意思是Java规范提案,下面给大家介绍JSR-107缓存规范的相关知识,感兴趣的朋友一起看看吧... 目录1.什么是jsR-1072.应用调用缓存图示3.JSR-107规范使用4.Spring 缓存机制缓存是每一

Spring 缓存在项目中的使用详解

《Spring缓存在项目中的使用详解》Spring缓存机制,Cache接口为缓存的组件规范定义,包扩缓存的各种操作(添加缓存、删除缓存、修改缓存等),本文给大家介绍Spring缓存在项目中的使用... 目录1.Spring 缓存机制介绍2.Spring 缓存用到的概念Ⅰ.两个接口Ⅱ.三个注解(方法层次)Ⅲ.

Spring Boot 整合 Redis 实现数据缓存案例详解

《SpringBoot整合Redis实现数据缓存案例详解》Springboot缓存,默认使用的是ConcurrentMap的方式来实现的,然而我们在项目中并不会这么使用,本文介绍SpringB... 目录1.添加 Maven 依赖2.配置Redis属性3.创建 redisCacheManager4.使用Sp

springboot项目redis缓存异常实战案例详解(提供解决方案)

《springboot项目redis缓存异常实战案例详解(提供解决方案)》redis基本上是高并发场景上会用到的一个高性能的key-value数据库,属于nosql类型,一般用作于缓存,一般是结合数据... 目录缓存异常实践案例缓存穿透问题缓存击穿问题(其中也解决了穿透问题)完整代码缓存异常实践案例Red

Spring三级缓存解决循环依赖的解析过程

《Spring三级缓存解决循环依赖的解析过程》:本文主要介绍Spring三级缓存解决循环依赖的解析过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、循环依赖场景二、三级缓存定义三、解决流程(以ServiceA和ServiceB为例)四、关键机制详解五、设计约

Redis中6种缓存更新策略详解

《Redis中6种缓存更新策略详解》Redis作为一款高性能的内存数据库,已经成为缓存层的首选解决方案,然而,使用缓存时最大的挑战在于保证缓存数据与底层数据源的一致性,本文将介绍Redis中6种缓存更... 目录引言策略一:Cache-Aside(旁路缓存)策略工作原理代码示例优缺点分析适用场景策略二:Re

Linux修改pip和conda缓存路径的几种方法

《Linux修改pip和conda缓存路径的几种方法》在Python生态中,pip和conda是两种常见的软件包管理工具,它们在安装、更新和卸载软件包时都会使用缓存来提高效率,适当地修改它们的缓存路径... 目录一、pip 和 conda 的缓存机制1. pip 的缓存机制默认缓存路径2. conda 的缓