1. 缓存穿透
在大多数互联网应用中,缓存的使用方式如下图所示:
1.当业务系统发起某一个查询请求时,首先判断缓存中是否有该数据;
2.如果缓存中存在,则直接返回数据;
3.如果缓存中不存在,则再查询数据库,然后返回数据。
了解了上述过程后,下面说说缓存穿透。
1.1 什么是缓存穿透?
业务系统要查询的数据根本就存在!当业务系统发起查询时,按照上述流程,首先会前往缓存中查询,由于缓存中不存在,然后再前往数
据库中查询。由于该数据压根就不存在,因此数据库也返回空。这就是缓存穿透。
综上所述:业务系统访问压根就不存在的数据,就称为缓存穿透。
1.2 缓存穿透的危害
如果存在海量请求查询压根就不存在的数据,那么这些海量请求都会落到数据库中,数据库压力剧增,可能会导致系统崩溃(你要知道,
目前业务系统中最脆弱的就是IO,稍微来点压力它就会崩溃,所以我们要想种种办法保护它)。
1.3 为什么会发生缓存穿透?
发生缓存穿透的原因有很多,一般为如下两种:
恶意攻击,故意营造大量不存在的数据请求我们的服务,由于缓存中并不存在这些数据,因此海量请求均落在数据库中,从而可能会导致
数据库崩溃。
代码逻辑错误。这是程序员的锅,没啥好讲的,开发中一定要避免!
1.4 缓存穿透的解决方案
下面来介绍两种防止缓存穿透的手段。
1.4.1 缓存空数据
之所以发生缓存穿透,是因为缓存中没有存储这些空数据的key,导致这些请求全都打到数据库上。
那么,我们可以稍微修改一下业务系统的代码,将数据库查询结果为空的key也存储在缓存中。当后续又出现该key的查询请求时,缓存直
接返回null,而无需查询数据库。
缓存空对象会有两个问题:
第一,空值做了缓存,意味着缓存层中存了更多的键,需要更多的内存空间 ( 如果是攻击,问题更严重 ),比较有效的方法是针对这类数
据设置一个较短的过期时间,让其自动剔除。
第二,缓存层和存储层的数据会有一段时间窗口的不一致,可能会对业务有一定影响。例如过期时间设置为 5 分钟,如果此时存储层添加
了这个数据,那此段时间就会出现缓存层和存储层数据的不一致,此时可以利用消息系统或者其他方式清除掉缓存层中的空对象。
1.4.2 BloomFilter
第二种避免缓存穿透的方式即为使用BloomFilter。
它需要在缓存之前再加一道屏障,里面存储目前数据库中存在的所有key,如下图所示:
当业务系统有查询请求的时候,首先去BloomFilter中查询该key是否存在。若不存在,则说明数据库中也不存在该数据,因此缓存都不要
查了,直接返回null。若存在,则继续执行后续的流程,先前往缓存中查询,缓存中没有的话再前往数据库中的查询。
这种方法适用于数据命中不高,数据相对固定实时性低(通常是数据集较大)的应用场景,代码维护较为复杂,但是缓存空间占用少。
1.4.3 两种方案的比较
这两种方案都能解决缓存穿透的问题,但使用场景却各不相同。
对于一些恶意攻击,查询的key往往各不相同,而且数据贼多。此时,第一种方案就显得提襟见肘了。因为它需要存储所有空数据的key,
而这些恶意攻击的key往往各不相同,而且同一个key往往只请求一次。因此即使缓存了这些空数据的key,由于不再使用第二次,因此也
起不了保护数据库的作用。
因此,对于空数据的key各不相同、key重复请求概率低的场景而言,应该选择第二种方案。而对于空数据的key数量有限、key重复请求
概率较高的场景而言,应该选择第一种方案。