1.缓存穿透问题
一般情况下,前端发request请求数据的流程是这样的。
- 前端携带key发送请求,请求数据。
- 服务收到请求,先去cache层里查,然后有两种情况。
2.1 缓存命中,取出数据。
2.2 缓存未命中,去storage储存层拿数据,然后存入cache中 - 拿到数据,响应给客户端
一般情况下,这种解决方案是可行的,但是,如果查找的是一个储存层也没有的数据,这样缓存层就永远都没有数据,请求就全都会去查我们的储存层。
假设有人利用这个漏洞,恶意攻击我们的服务器,发送大量的携带着无效key的请求,因为缓存中没有数据,就会透过缓存直接访问我们的储存层,
而我们的储存层一下子处理不了那么多请求,就会造成卡顿甚至宕机。
如果有人恶意攻击我们的服务器,我们如何发现这个问题?
1.查看业务的响应时间。
2.查看相关指标:总调用数,缓存层命中数,存储层命中数。
发现了问题后,如何解决?有以下的方案。
方案1
1.缓存空对象,就是如果储存层未命中的情况下,把这个无效key也记录下来,放入缓存层中,并且设置值为null。并且设置较小的过期时间(根据场景来定)。
但是这种方案会有两个问题:
1.1如果无效key特别多,则存储层需要储存大量的无效key,导致内存浪费。
1.2存储层和缓存层数据短期不一致,举个例子,我们第一次查询的key是无效的,存入了缓存层,并且添加了过期时间,但是在还未过期时,我们在储存层对这个key进行了数据添加,储存层已经有key所对应的值了,但是客户端的请求在缓存层key未过期时会一直命中缓存层,无法读取到储存层的数据。就导致了短期数据不一致。
解决方案:过多无效key
设置过期时间降低风险
解决方案:数据短期不一致
数据短期不一致问题解决方案1。(基于Canal)
1、更新数据库数据;
2、MySQL 将数据更新日志写入 binlog 中;
3、Canal 订阅 & 消费 MySQL binlog,并提取出被更新数据的表名及 ID;
4、调用应用删除缓存接口;
5、删除缓存数据;
6、Redis 不可用时,将更新数据的表名及 ID 发送到 MQ 中;
数据短期不一致问题解决方案2(基于 MQ )
1、更新数据库数据;
2、删除缓存中的数据,可此时缓存服务出现不可用情况,造成无法删除缓存数据;
3、当删除缓存数据失败时,将需要删除缓存的 Key 发送到消息队列 (MQ) 中;
4、应用自己消费需要删除缓存 Key 的消息;
5、应用接收到消息后,删除缓存,如果删除缓存确认 MQ 消息被消费,如果删除缓存失败,则让消息重新入队列,进行多次尝试删除缓存操作。
解决方案转载自:数据短期不一致解决方案
方案2
布隆过滤器拦截
我们可以使用布隆过滤器进行缓存预热,当一个携带着key的请求发送过来时,布隆过滤器会首先拦截它,判断这个key是否存在,如果不存在,就立刻返回请求。
布隆过滤器适合用于数据不常发生变化的场景。在数据经常发生变化的场景中,数据的实时更新,怎么去生成布隆过滤器有很多问题。
2.缓存雪崩问题
是指在某一个时间段,缓存层的数据集中过期失效。在缓存集中失效的这个时间段对数据的访问查询,都落到了存储层上。这样大量的请求都去请求储存层。就容易出问题。
解决方案:
为了避免缓存雪崩的发生,我们可以将缓存的数据设置不同的失效时间,这样就可以避免缓存数据在某个时间段集中失效。例如对于热门的数据(访问频率高的数据)可以缓存的时间长一些,对于冷门的数据可以缓存的时间短一些。甚至对于一些特别热门的数据可以设置永不过期(内存的开销)。还有其它第三方的缓存(Memory Cache, Jvm中的缓存: Spring Cache, Mybatis 二级缓存)
1.设置不同的失效时长 质数
2.延长有效期 expire key 时间
3.添加第三方缓存,重要的业务数据,高频访问的
3.缓存击穿问题
热点数据的key,在高并发的场景下失效了,导致大量的请求都去访问储存层。下面我用伪代码描述一下这个问题
if (缓存中有数据) {
拿出数据响应
} else {
查询储存层取出数据
放入缓存中
}
在高并发场景下,当访问的这个热点数据的key失效时,大量的请求都判断了缓存中没有这个key,走到了else中,导致大量请求都去访问储存层了。
解决方案:
1.设置热点key永久有效,后台任务定时清理。
2.策略性延长有效期,通过每天的访问量延长key的有效期(延长时间根据场景自定义)