目录
一、缓存穿透
二、缓存雪崩
三、缓存击穿
本地锁
分布式锁-使用redis存储key
阶段一
阶段二
阶段三
阶段四
一、缓存穿透
缓存穿透
是指
查询一个一定不存在的数据
,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的 null
写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义。
在流量大时
,可能
DB
就挂掉了,要是有人利用不存在的
key
频繁攻击我们的应用,这就是漏洞。
解决:
缓存空结果、并且设置短的过期时间。
二、缓存雪崩
缓存雪崩是指在我们设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到 DB
,
DB
瞬时压力过重雪崩。
解决:
原有的失效时间基础上增加一个随机值
,比如
1-5
分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
三、缓存击穿
对于一些设置了过期时间的
key
,如果这些
key
可能会在某些时间点被超高并发地访问,是一种非常“
热点
”
的数据。
这个时候,需要考虑一个问题:如果这个
key
在大量请求同时进来前正好失效,那么所有对这个 key
的数据查询都落到
db
,我们称为缓存击穿。
解决:
加锁,大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去db
缓存
db
本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有的分布式服务,必须使用分布式锁
本地锁
本地锁流程如下:
// 本地锁
synchronized (this) {
// 查询数据库前先再查一次缓存
......
System.out.println("查询了数据库");
// 查询数据库的业务
......
// 在放开锁之前放入redis,因为存放进redis也是一次网络交互,如果放在锁外面也需要时间(此时可能被别的线程乘虚而入又查询数据库)
......
}
本地锁流程图如下:
分布式锁-使用redis存储key
锁即是lock,每次都往redis中存储key---使用setnu(原子操作),如果存放成功,那么就是拿到锁了
阶段一
问题:
1
、
setnx
占好了位,业务代码异常或者程序在页面过程中宕机。没有执行删除锁逻辑,这就造成了死锁
解决:
设置锁的自动过期,即使没有删除,会自动删除
阶段二
问题:
1
、
setnx
设置好,正要去设置过期时间,宕机。又死锁了。
解决:
设置过期时间和占位必须是原子的。
redis
支持使用
setnx ex命令(占位的同时设置过期时间)
阶段三
问题:
1
、删除锁直接删除???
如果由于业务时间很长,锁自己过期了,我们直接删除,有可能把别人正在持有的锁删除了。
解决:
占锁的时候,值指定为
uuid
,每个人匹配是自己的锁才删除
阶段四
问题:
1
、如果正好判断是当前值,正要删除锁的时候,锁已经过期,别人已经设置到了新的值。那么我们删除的是别人的锁
解决:
删除锁必须保证原子性。使用
redis+Lua
脚本完成
最终版本:
1、每个线程存放的锁要有自己的标识(uuid等)
2、占锁时存放和设置时间需要是原子性的(EX NX)
3、删锁时判断是否是自己的锁和删除操作需要是原子性的(LUA脚本,脚本一起成功或者一起失败)
// TODO 本地锁:synchronized,JUC(Lock),在分布式情况下,想要锁住所有的分布式服务,必须使用分布式锁
// 分布式锁 redis存lock Key来实现
// 先去redis占坑
String uuid = UUID.randomUUID().toString();
Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 300, TimeUnit.SECONDS);
if (lock) {
Map<String, List<Catelog2Vo>> result ;
//占坑成功
try {
result = getDataFromDb();
} finally {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 删除key
Long lock1 = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class),
Arrays.asList("lock"), uuid);
}
return result;
} else {
// 加锁失败 重试
// 这里也可休眠一段时间
return getCatelogJsonFromDBWithRedisLock(); //采用自旋的方式
}