Redis缓存击穿
概念:Redis缓存击穿(Redis Cache Breakdown)是指当某个热点数据在缓存中失效的同时,大量并发请求涌入,导致请求直接打到数据库上,增加了数据库的负载压力。
解决方法:
- 使用分布式锁控制只有一个线程可以查询数据库并更新缓存,其他线程等待结果。
总结:就是在缓存数据库还没有进行缓存时,或者缓存刚好失效时,大量的并发访问数据库,执行SQL,增加数据库的压力。可是使用互斥锁进行解决(排队,相当于同步代码块)
代码实例:没有加分布式锁
package com.wzk.config;
import com.wzk.mapper.UserInfoMapper;
import com.wzk.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @author wzk
* @date 2023/10/10
**/
@RestController
@RequestMapping("/test")
@Slf4j
public class test {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Resource
private UserInfoMapper userInfoMapper;
@GetMapping("/test")
public void test(){
//获取用户名
List<String> list =null;
//查询缓存是否有数据
Object obj = redisTemplate.opsForValue().get("test");
if(obj==null){
log.info("缓存中没有数据,从数据库查询获取");
//查询数据库
list = userInfoMapper.queryRoleByUserName("admin");
//把数据存入redis缓存中
redisTemplate.opsForValue().set("test",list,2, TimeUnit.MINUTES);
}else{
log.info("缓存中有数据,从缓存中获取数据");
//从缓存中去数据
list = (List<String>)obj;
}
}
}
可以看到大量的并发进行数据库的执行sql增加数据库的压力
使用互斥锁,解决缓存击穿
Boolean is = redisTemplate.opsForValue().setIfAbsent(key,value)方法:在缓存数据库中存在该(key)键时返回false,在缓存数据库中不存在该(key)键时返回true。
分布式锁:
Boolean is = redisTemplate.opsForValue().setIfAbsent("locK:sou", uuid + "",3,TimeUnit.MINUTES); //存入数据
注意:
1、锁中的value值需要唯一,因为在高并发的线程中公用一个键(key),防止误删缓存数据库中的键,使用唯一的值进行比较,判断是否是当前线程。
2、使用分布式锁中需要设置锁的失效时间,如果没有设置,这个线程如果很长时间没有执行完成,就会出现其他线程都在休眠,无法执行程序。
3、使用try-catch-finally进行捕获异常,在finally中删出缓存,防止线程出现异常时,缓存无法进入程序,影响性能。
使用分布式锁:
package com.wzk.config;
import com.wzk.mapper.UserInfoMapper;
import com.wzk.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @author wzk
* @date 2023/10/10
**/
@RestController
@RequestMapping("/test")
@Slf4j
public class test {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Resource
private UserInfoMapper userInfoMapper;
@GetMapping("/test")
public void test() throws InterruptedException {
//获取线程名方便观看
long id = Thread.currentThread().getId();
//创建唯一键
UUID uuid = UUID.randomUUID();
//使用分布式锁
//redisTemplate.opsForValue().setIfAbsent在缓存数据库中没有键时返回true,否则返回false
Boolean is = redisTemplate.opsForValue().setIfAbsent("locK:sou", uuid + "",3,TimeUnit.MINUTES); //存入数据
if(!is){ //进入循环说明数据库中存在线程正在运行,后面线程等待
log.info("有线程正在运行,线程等待{}",id);
while (!redisTemplate.opsForValue().setIfAbsent("locK:sou", uuid + "")){ //false线程等待
Thread.sleep(1000); //等待一秒,休眠,防止cpu的消耗
}
}
try {
//获取用户名
List<String> list =null;
//查询缓存是否有数据
Object obj = redisTemplate.opsForValue().get("test");
if(obj==null){
log.info("缓存中没有数据,从数据库查询获取");
//查询数据库
list = userInfoMapper.queryRoleByUserName("admin");
//把数据存入redis缓存中
redisTemplate.opsForValue().set("test",list,2, TimeUnit.MINUTES);
}else{
log.info("缓存中有数据,从缓存中获取数据");
//从缓存中去数据
list = (List<String>)obj;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if (redisTemplate.opsForValue().get("locK:sou").equals(uuid+"")){ //判断当前线程是否相等,防止误删(多线陈进入,共用键key,使用的值唯一)
redisTemplate.delete("locK:sou"); //删除键,表示这个线程执行完成
}
}
}
}
Redis缓存穿透
概念:Redis缓存穿透(Redis Cache Penetration)是指一个查询无法从缓存中获取数据,也无法从数据库中获取数据,导致每次请求都需要查询数据库,从而增加了数据库的负载。这通常发生在恶意攻击或者非法请求下,攻击者会故意查询缓存中不存在的数据,导致缓存无法起到其应有的作用。
解决方法:
- 针对缓存不存在的数据,也可以将空结果缓存一段时间,避免频繁查询数据库
总结:redis的缓存穿透是,在缓存数据库中查询不到的数据,并且在数据库中页查询不到。导致在每一次查询中都会访问数据库,造成数据库压力。解决方法:在数据库中查询不到带值时,在数据库中存入空字符。
代码实例:
//缓存穿透
//恶意的传入空值对象,缓存数据库中没有数据,从而进行频繁的执行数据库
@GetMapping("/test01")
public void test01(){
//定义键
String key = "name";
Object obj = redisTemplate.opsForValue().get(key);
if (obj == null){
log.info("没有该用户,从数据库中获取数据");
//查询数据库(数据库中没有id为10的用户)
UserInfo userInfo = userInfoMapper.selectById(10);
redisTemplate.opsForValue().set(key,userInfo,5,TimeUnit.MINUTES);
}else {
log.info("有用户,从缓存数据库中获取数据");
}
}
下图:可以看到访问的线程都是进入数据库中查询数据,造成数据库压力
解决缓存穿透:
/缓存穿透
//恶意的传入空值对象,缓存数据库中没有数据,从而进行频繁的执行数据库
@GetMapping("/test01")
public void test01(){
//定义键
String key = "name";
Object obj = redisTemplate.opsForValue().get(key);
if (obj == null){
log.info("没有该用户,从数据库中获取数据");
//查询数据库(数据库中没有id为10的用户)
UserInfo userInfo = userInfoMapper.selectById(10);
log.info(" => {}",userInfo);
if (userInfo == null) {
//数据库中没有查询到数据时,在redis数据库中存入空字符串
redisTemplate.opsForValue().set(key, "", 5, TimeUnit.MINUTES);
}else{
// //数据库中查出数据,直接存入redis数据库中
redisTemplate.opsForValue().set(key,userInfo,5,TimeUnit.MINUTES);
}
}else {
log.info("有用户,从缓存数据库中获取数据");
}
}
下图可以看见:在第一次查询时会走数据库sql,但是后面直接从缓存数据库中获取
缓存雪崩
概述:缓存中大量的数据在同一时间段中同时失效,造成大量的数据需要从数据库中获取,从而增加数据库的压力。、
解决方法:使用时间函数,修改不同数据的失效时间不同,避免数据在同一时间同时失效。
代码省略