所谓缓存,就是在逻辑代码和数据库之间插入那么一层缓冲层,查询等操作可以在缓冲层解决,不用再去数据库操作,以达到缓解数据库压力和增加查询效率的那么一个缓冲层。从这个角度来看,对于缓存穿透,字面理解就是将这个缓冲层打透,我每次都能直达数据库,你得缓冲层不起什么效果,就是这么一个概念。
网上也有对这个概念的各种解释,我简要提取整理一下就是: 查询一个一定不存在的数据,由于缓存是不命中,数据库也不存在并且数据库不存在时不会写入缓存,导致这个数据每次请求都要到数据库去查询,失去了缓存的意义。
这种情况如果少了还可以,如果并发量达到一个高度,可能会给数据库造成很大压力,甚至宕机。
当然,有了问题我们就得有解决的方案,目前比较成熟的解决方案主要是两种:一是空值缓存,二是设置过滤器。简单介绍一下这两种方案。
- 空值缓存:当我们去查询一个缓存数据,这个数据查不到,就去数据库查询,再查不到,如果直接报错返回,下次再查,还是这么个循环。比如电商平台,我们查询一个商家推荐商品,当前要是没有推荐,我们每次去查询该商家的推荐商品,都要去数据库查询。设想一下,如果我们第一次在数据库查询完没有查到数据,就在缓存中缓存该商家的推荐商品是空,那么下一次我们再查询的时候,是不是就可以直接到缓存层就可以知道该商家没有推荐商品,而不用去查询数据库了。所谓的空值缓存,就是这样一个逻辑,将第一次查出来的空值,设置在缓存中,以达到缓解数据库压力的问题。
- 设置过滤器,一般是使用一个bitmap(也可以使用一个set集合),将可查的数据放入这个集合当中。再拿商家推荐例子吧,我将有推荐的商家id都放入这个一个集合当中,每次查询的时候都去这个集合过滤一遍,只有合格的数据,我才去缓存查,不合格的,我就默认他是null就可以了。
当然过滤方案不止这一种啊,比如像我们要是缓存ip信息,这个字段是有规则的啊,我们可以根据ip规则进行过滤再去查询,防止恶意攻击等等。
对于这两种解决方案,普遍使用的都是第一种,对比来说,第一种和第二种,对数据实时变化要求是不一样的,空值过滤适用于数据变化频率较高也可以,而你写死的set集合,对于变化频率较高的数据,显然是不擅长的。而对于维护成本来将,第一种的代码维护成本也是简单暴力,第二种则略显复杂。
写到这里,就多扯两句,关于缓存存在的问题,还有缓存失效和缓存雪崩等问题。缓存失效,很多情况,一个是缓存时间到了,失效了,其次还有服务器关了,没有作用了。关于缓存雪崩,这个肯定算是事故了,比如缓存宕机了,或者缓存大面积的失效,引起一段时间大量数据直接访问数据库,造成极大的压力,这种情况就叫缓存雪崩。但是目前为止,好像还没有什么好的缓存雪崩的解决方案,只能预防为主。
预防措施主要有
1.使用锁或者队列来控制数据库访问量,
2.使用不失效缓存(长短两个缓存)和设置缓存时间均匀分布,
3.不要同一时间很多缓存都失效等。