缓存穿透(查不到数据)
概述
当用户想要查询一个数据,发现Redis中不存在,也就是所谓的缓存没有命中,于是这个数据请求就会打到数据库中。结果数据库中也不存在这条数据,那么结果就是什么都没查询出来。那么当用户很多时候的查询,缓存中都没有数据,请求直接打到数据库中,这样就会给数据库造成很大的压力,缓存的作用也就几近于失效了,那么这种情况就叫做缓存穿透。
解决方案
方案一:保存空值
当数据库中也查询不到数据时,那么将返回的空对象也缓存起来,同时设置一个过期时间,之后再访问这个数据将会从缓存中获取,从而起到保护数据库的作用。
例如:查询userId=100的用户信息(key=[userId],value=[用户json]),那么如果缓存和DB中都不存在,则在缓存中保存一条key=100,value=""
的数据,那么用户再查询userId=100的时候,就直接可以返回空了。不需要查询DB。
方案二:布隆过滤器
步骤1:将数据库所有的数据加载到布隆过滤器。
步骤2:当有请求来的时候先去布隆过滤器查询
,判断查询的数据是否存在。
步骤3:如果Bloom Filter判断数据不存在,那么直接返回空
给客户端。
步骤4:如果Bloom Filter判断数据存在,那么则查询缓存
或DB
。
步骤5:将DB中查询的结果返回给客户端(并且缓存到Redis中)
缓存击穿(高并发查询某数据,且缓存过期)
概述
指一个非常热点的key,在不停的高并发请求着,那么当这个key在缓存中失效的一瞬间,持续对这个key的高并发就击穿了缓存,直接请求到了数据库,就像在一个屏障上早开了一个洞。
当热点key过期失效的一瞬间,高并发突然融入,会对数据库突然造成巨大的压力,严重的情况甚至会造成数据库宕机。
解决方案
方案一:设置热点数据永不过期
从缓存层面来看,没有设置过期时间,所以不会出现热点key过期后所产生的缓存击穿问题。
方案二:加互斥锁
使用分布式锁,当缓存数据过期后,保证对每个热点key同时只有一个线程去查询后端服务
,并将热点数据添加到缓存。
缓存雪崩(缓存大批量失效或Redis宕机)
概述
指在某一个时间段,缓存集中过期失效,或Redis宕机,导致针对这批数据的查询都落到了数据库上,对于数据库而言,就会产生周期性的压力波峰。于是所有的请求都会达到存储层,存储层的调用量会暴增,造成存储层也会挂掉的情况。
其实缓存集中过期,倒不是最致命的,比较致命的是Redis发生节点宕机或断网。因为缓存集中过期后,数据库压力增大,但是随着缓存的创建,压力也会逐渐变小。但是Redis服务节点宕机,对数据库服务器造成的压力是不可预知的,很有可能是持续压力而最终造成数据库宕机。
解决方案
方案一:配置Redis集群
通过配置Redis集群,提升高可用性,那么即使挂掉几个Redis节点,集群内的其他Redis节点依然可以继续对外提供服务。
方案二:限流降级
缓存失效后,通过加锁或队列来控制读取数据库且写入缓存的线程数量。
方案三:数据预热分散过期时间
在正式部署之前,先把可能被高频访问的数据预先访问一遍,这样大部分热点数据就加载到缓存中了,并且通过设置不同的过期时间,让缓存失效的时间尽量均匀,防止同一时刻大批量缓存失效。
布隆过滤器
概念介绍
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
布隆过滤器的特征是:它可以判断某个数据一定不存在,但是无法判断一定存在。(确实有点拗口,但当我们介绍完它的原理,就很容易明白了)
使用场景
场景一:原本有10亿个号码,现在又来了10万个号码,如何快速判断这10万个号码是否在10亿个号码库中?
场景二:需要爬虫的网站千千万万,对于一个新的网站url,我们如何判断这个url我们是否已经爬过了?
解决方案
针对上面的需求,我们一般会想到两种
解决方案:
方案一:将10亿个号码存入数据库中,进行数据库查询,准确性有了,但是速度会比较慢。
方案二:将10亿号码放入内存中,比如Redis缓存中,这里我们算一下占用内存大小:10亿*8字节=8GB,通过内存查询,准确性和速度都有了,但是大约8GB的内存空间,挺浪费内存空间的。
那么对于类似这种,海量数据集合,如何准确快速的判断某个数据是否在大数据量集合中,并且不占用内存?那么布隆过滤器应运而生了。
实现原理
布隆过滤器是什么?
它是一种数据结构,是由一串很长的二进制向量组成,也可以将其看成一个二进制数组。既然是二进制,那么里面存放的不是0
,就是1
,但是初始默认值都是0。如下图所示:
添加数据如何处理?
当要插入一个元素时,将其数据分别输入k个哈希函数,产生k个哈希值。以哈希值作为位数组中的下标,将所有k个对应的比特置为1。
比如,下图hash1(x)=1
,那么在第2个格子将0变为1
(数组是从0开始计数的),hash2(x)=6
,那么将第5个格子置为1
,hash3(x)=16
,那么将第16个格子置位1
,依次类推。如下图所示(只演示hash1~hash3):
如何判断数据是否存在?
知道了如何向布隆过滤器中添加一个数据,那么新来一个数据,我们如何判断其是否存在于这个布隆过滤器中呢?
很简单,我们只需要将这个新的数据通过上面自定义的几个哈希函数,分别算出各个值,然后看其对应的地方是否都是1,如果存在一个不是1的情况,那么我们可以说,该新数据一定不存在于这个布隆过滤器中。
那么反过来说,如果通过哈希函数算出来的值,对应的地方都是1,那么我们能够肯定的得出:这个数据一定存在于这个布隆过滤器中吗?
答案是否定的,因为多个不同的数据通过hash函数算出来的结果是会有重复的,所以会存在某个位置是别的数据通过hash函数置为的1。即:“假阳性”(false positive)如下图所示:
今天的文章内容就这些了:
写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享 。
更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \(^o^)/ ~ 「干货分享,每天更新」