redis应用问题解决
缓存穿透
什么是缓存穿透?
可以参考下图,当客户端发送读的请求过来时,会先访问缓存中的数据,如果不存在则直接去访问MySQL服务器中的数据。这时候如果MySQL服务器中并不存在他请求对应的信息,请求就会反反复复一直访问MySQL服务器,黑客利用此漏洞进行攻击可能压垮数据库。
文章图片
解决方案
- 方案一:缓存空值
如果MySQL服务器中不存在相对应的数据,可以将对应的key的value值设置为空,当请求再次访问时可以直接去缓存中读取空值
- 方案二:布隆过滤器
在访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来,做第一层拦截,当收到一个对key请求时先用布隆过滤器验证是key否存在,如果存在在进入缓存层、存储层。可以使用bitmap做布隆过滤器。这种方法适用于数据命中不高、数据相对固定、实时性低的应用场景,代码维护较为复杂,但是缓存空间占用少。
什么是缓存击穿?
比如微博的热点事件,大家都在同一时间点访问这个请求,就可能造成服务器崩溃的情况,就好像子弹打在墙上一样,始终往一个地方大,迟早打穿这堵墙
解决方案
- 方案一:提前设置预热数据
在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
- 方案二:使用分布式锁
类似于Java中的锁机制一样,一个时间只有一个用户可以访问,当这个用户访问完之后,解锁了,其他用户才可以开始访问,这个在后面也会详细展开讲解
什么时候缓存雪崩?
举个栗子,在双十一的时候,非常多的用户访问非常多的请求,我们虽然提前做了缓存,但在一定时间后这些缓存同时失效,那就会有一大批的请求直接访问MySQL数据库,给服务器带来巨大的压力
解决方案
- 方案一:构建多级缓存架构
- 方案二:设置过期标志更新缓存
记录缓存过期时间,快到过期时间时触发另外一个线程更新过期时间
- 方案三:将缓存失效时间分散开
比如A请求的失效时间为10分钟后,B请求的失效时间可以设置为10分01秒后,依次类推,即使过期了,请求直接访问时也可以错开时间点
业务需求
我们上述说到的解决缓存击穿的方案,可以使用分布式锁,让请求一个一个访问。随着时代的发展,现在我们使用redis已经不是单单的一台服务器了, 我们会建一个集群,但是锁这个东西他是不能横跨服务器的,这种情况redis也提供了解决方案
解决方案
- 使用setnx命令
我们都知道setnx命令如果这个key不存在的话可以对其value进行设置,但key存在时是不允许设置的,我们就可以利用这一点,将此key作为锁。
- 设置过期时间
就好像有一个人去上厕所,外面排着长队,结果这个人上着上着突然睡着了,但是外面的人就无法使用。所以我们要将锁设置一定的过期时间,如果长时间没有完成就要自动释放锁
- 【小黄学redis|redis——缓存穿透、缓存击穿、缓存雪崩、分布式锁】具体命令
# set key value nx ex second : 其中nx等同于setnx,ex设置过期时间单位为秒 set k2 v2 nx ex 10
@GetMapping("/testLock")
public void testLock(){
//1.获取锁
Boolean isLock = redisTemplate.opsForValue().setIfAbsent("kkk", "vvv", 10, TimeUnit.SECONDS);
//2.操作数据
if (isLock){
//查询key为num的数值
Object value = https://www.it610.com/article/redisTemplate.opsForValue().get("num");
if (StringUtils.isEmpty(value)){
return;
}
//把值转换为数字并加1
int i = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num",i+1);
//释放锁
redisTemplate.delete("kkk");
}else {
//3.获取锁失败,每个0.1秒再获取
try {
Thread.sleep(100);
testLock();
}catch (Exception e){
e.printStackTrace();
}
}
}
设置num的值为0,使用ab工具进行多次访问,如果锁有效的话,1000个请求num的值应该为1000
ab -n 1000 -c 100 http://192.168.0.10:8080/redisTest/testLock
127.0.0.1:6379> get num
"1000"
问题解决
其实上述案例中还是有那么一些些问题的,需要我们来梳理一下
问题一
如下图所示,A请求在执行操作的过程中宕机了,但是key的过期时间已到,B请求获取到了锁并加上了锁,B在执行操作的过程中,A相应过来了,手动释放了锁,这时其他的请求又会一起挤进来,可能会出现两个请求操作一个数据的情况
解决方案:使用UUID作为value值,防止误删除
文章图片
@GetMapping("/testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1.获取锁
Boolean isLock = redisTemplate.opsForValue().setIfAbsent("kkk", "uuid", 10, TimeUnit.SECONDS);
//2.操作数据
if (isLock){
//查询key为num的数值
Object value = https://www.it610.com/article/redisTemplate.opsForValue().get("num");
if (StringUtils.isEmpty(value)){
return;
}
//把值转换为数字并加1
int i = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num",i+1);
//释放锁
//判断是否为自己的锁
String keyForUuid = (String) redisTemplate.opsForValue().get("kkk");
if (uuid.equals(keyForUuid)){
redisTemplate.delete("kkk");
}
}else {
//3.获取锁失败,每个0.1秒再获取
try {
Thread.sleep(100);
testLock();
}catch (Exception e){
e.printStackTrace();
}
}
}
问题二
如下入、图所示,A在释放锁的过程中,先判断了uuid是相同的,正准备删除时,锁的过期时间到了,自动删除后,B获取到了锁并加上了锁,A再删除了这个锁
解决方案:使用LUA脚本保证删除的原子性
文章图片
@GetMapping("/testLock")
public void testLock(){
String uuid = UUID.randomUUID().toString();
//1.获取锁
Boolean isLock = redisTemplate.opsForValue().setIfAbsent("kkk", "uuid", 10, TimeUnit.SECONDS);
//2.操作数据
if (isLock){
//查询key为num的数值
Object value = https://www.it610.com/article/redisTemplate.opsForValue().get("num");
if (StringUtils.isEmpty(value)){
return;
}
//把值转换为数字并加1
int i = Integer.parseInt(value + "");
redisTemplate.opsForValue().set("num",i+1);
// 定义lua 脚本
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
// 使用redis执行lua执行
DefaultRedisScript redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(script);
// 设置一下返回值类型 为Long
// 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
// 那么返回字符串与0 会有发生错误。
redisScript.setResultType(Long.class);
// 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
redisTemplate.execute(redisScript, Arrays.asList("kkk"), uuid);
}else {
//3.获取锁失败,每个0.1秒再获取
try {
Thread.sleep(100);
testLock();
}catch (Exception e){
e.printStackTrace();
}
}
}
推荐阅读
- Redis|Redis——Redis用作缓存(内存回收/穿透/击穿/雪崩)
- redis|Redis集群——分布式缓存
- redis|缓存——redis——redis常用配置参数
- Redis|Redis——详解持久化
- Redis|Redis——集群之主从模式
- redis|Redis——缓存雪崩、缓存击穿、缓存穿透
- Redis——集群之哨兵模式
- 数据库|GBase 8s MPP产品简介
- 电商数仓|电商数据仓库系统