发布于 

缓存击穿?缓存穿透?缓存雪崩?

一.缓存击穿

1.什么是击穿

所谓击穿,用个形象的比喻就是射击,子弹穿过了缓存挡板,打了一个孔,这个孔就是热点Key,再击中了标的物:数据库。那么就是缓存中已经过期淘汰了,但是数据库中还有,然后所有的请求刚好在缓存失效的那一瞬间,刚好有大批量的请求在这个热点key,从而所有的请求打在了数据库上。

2.解决击穿问题的思路

分析击穿的致命点在哪?就在于:失效的那一瞬间所有的请求到达了数据库。那么在失效的那一瞬间将资源只分配给某个请求,加锁~

加锁时需要深入考虑的问题:

  • 加锁目的是排他,排他归排他,但是不能一直占用对吧?

占着茅坑不拉x,怎么防止?我在马桶上安装一个倒计时,规定每个人最多占用3分钟(代码里面三分钟用户是无法忍受的~),三分钟到了启动马桶自动弹飞如何?

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
String pageCache = redisUtils.get(key);
// pageEntity 是请求的目标对象
PageEntity pageEntity = null;
if (pageCache != null) {
pageEntity = GsonUtils.fromJson(pageCache,PageEntity.class);
} else {
// 加锁 防止穿透
String lockKey = "lock-key";
boolean lockFlag = redisUtils.setIfAbsent(lockKey, "lock", 3, TimeUnit.SECONDS);
try{
if (lockFlag) {
// 当前请求加锁成功,从数据库获取数据
pageEntity = findFromDB();
// 设置缓存,从而下一个请求线程能从缓存中获取
resetPageToCache(pageEntity);
}else{
// 等一会再获取
Thread.sleep(10);
// 再次尝试获取
pageCache = redisUtils.get(key);
pageEntity = GsonUtils.fromJson(pageCache,PageEntity.class);
}
}finally {
redisUtils.delete(lockKey);
}
}
  • 倒计时设置多久合适?

强制弹飞对于其他线程而言是公平了,但是假如从数据库查询时候三分钟还没查出来结果,这个锁就已经释放了,又同样的导致了并发请求打到了数据库。

在占用的时候搞个角色在边上轮询。你好了吗?你好了吗...,谁来充当这个角色?子线程咯。我们能想到的问题,肯定有前人已经想到了。肯定有造好的轮子:Redisson

二.缓存穿透

1.什么是缓存穿透?

穿透,形象的比喻就是,当月光洒在了我的脸上,我想我...,月光穿透了空气,为什么是空气?空气就是没有,ok,那么穿透就是在缓存中没有,数据库也没有。应用场景就是:例如我们搜索一个系统不存在的东西。导致的问题就是:这个请求又直接和数据库交互了,极端一点的:系统恶意攻击,目的就是为了拖垮你们系统。

2.如何解决缓存穿透?

  • 事前缓存预热

预先把系统存在的内容东西先给放到缓存中的一个集合中去,用户请求某个存在的key,再去缓存中拿到数据返回个用户,不存在的搜索内容直接告诉用户不存在,更友好点的话组装一个推荐数据返回。
具体实现:参考布隆过滤器、布谷鸟过滤器。

  • 事中服务限流

当流量被打满的到系统的限定值,进行限流、降级。

三.缓存雪崩

1.什么是缓存雪崩?

大批量的缓存失效,导致所有的请求全部打在了数据库。比如说系统一批活动设置在今晚十二点过期,商品恢复原价。

2.如何解决缓存雪崩?

  • 对于和时间点没有关系的缓存:随机设置key的过期时间
  • 对于系统一定得整点过期,要保证数据一致性的情景:在过期时间的这个时间点,加排他锁,先让一个请求打到数据库,然后设置好缓存,再把其他的请求放进来。类似上文穿透的解决方案。

四.双写一致性【TODO】