## 一、缓存击穿 #### 1、什么是缓存击穿 某个热点Key(高频访问)在缓存过期的一瞬间,大量并发请求同时击穿缓存层,直接访问数据库,导致数据库压力骤增甚至崩溃。 #### 2、出现条件 ##### Emp新增方法 ```java public static Emp mapToEmp(Object obj) { Map map = null; if (obj instanceof Map, ?>) { map = (Map) obj; } if (map == null) return new Emp(); return new Emp( (Integer) map.get("EMPno"), map.get("ename").toString(), map.get("job").toString(), (Integer) map.get("mgr"), map.get("hiredate").toString(), (Double) map.get("sal"), (Double) map.get("comm"), (Integer) map.get("DEPTno") ); } ``` ##### service方法 ```java public Emp getMySQLAndRedisEmpById(Integer id) { Object obj = redisDao.get("Emp::" + id); System.out.println(obj); if (obj == null) { obj = getEmpById(id); redisDao.save("Emp::" + id, obj, 300, TimeUnit.SECONDS); System.out.println("存入Redis"); } else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); } return (Emp) obj; } ``` ##### controller方法 ```java @GetMapping("/get2") public ResponseResult getMySQLAndRedisEmpById(@RequestParam("id") Integer id) { Emp emp = empService.getMySQLAndRedisEmpById(id); if (emp == null) return new ResponseResult<>(null); return new ResponseResult<>(200, "查询成功", emp); } ``` ##### 压测工具   #### 3、现象 所有请求都在MySQL中查询数据  #### 4、解决方法 ##### 线程锁代码块 双重检测,线程同步 修改service代码,添加线程锁代码块 ```java public Emp getMySQLAndRedisEmpById(Integer id) { Object obj = redisDao.get("Emp::" + id); System.out.println(obj); if (obj == null) { synchronized (EmpService.class) { obj = redisDao.get("Emp::" + id); if (obj == null) { obj = getEmpById(id); redisDao.save("Emp::" + id, obj, 300, TimeUnit.SECONDS); System.out.println("存入Redis"); }else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); } } } else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); } return (Emp) obj; } ``` 效果  ##### 线程锁关键字(效率略低) ```java public synchronized Emp getMySQLAndRedisEmpById(Integer id) { Object obj = redisDao.get("Emp::" + id); System.out.println(obj); if (obj == null) { obj = getEmpById(id); redisDao.save("Emp::" + id, obj, 300, TimeUnit.SECONDS); System.out.println("存入Redis"); } else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); } return (Emp) obj; } ```  ## 二、缓存穿透 #### 1、什么是缓存穿透 如果频繁高热点提交数据库中不存在的记录,则Redis中永远没有数据,但是用户会确认访问MySQL数据库。 #### 2、出现条件 查询不存在的数据 #### 3、现象 每次查询都会访问MySQL数据库  #### 4、解决方法 ##### 布隆过滤器 提前将能查询到的所有集合缓存到布隆过滤器中,通过布隆过滤器集合来判断请求数据是否合法。 添加依赖 Maven仓库列表:[Maven Repository: com.google.guava » guava](https://mvnrepository.com/artifact/com.google.guava/guava) ```xml com.google.guava guava 33.5.0-jre ``` 添加mapper查询方法 ```java @Select("select EMPno from EMP") List listEmpAllIds(); ``` 创建配置类`util/BloomConfig.java` ```java package cn.doooog.mybatisplus.util; import cn.doooog.mybatisplus.mapper.EmpMapper; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; @Configuration public class BloomConfig { @Autowired private EmpMapper empMapper; @Bean public BloomFilter init(){ List listIds = empMapper.listEmpAllIds(); BloomFilter filter = BloomFilter.create(Funnels.integerFunnel(),listIds.size(),0.001); for (Integer id :listIds){ filter.put(id); } return filter; } } ``` 在controller中添加布隆过滤器 ```java @Autowired private BloomFilter bloomFilter; @GetMapping("/get2") public ResponseResult getMySQLAndRedisEmpById(@RequestParam("id") Integer id) { if (!bloomFilter.mightContain(id)) { return new ResponseResult<>(null); } Emp emp = empService.getMySQLAndRedisEmpById(id); if (emp == null) return new ResponseResult<>(null); return new ResponseResult<>(200, "查询成功", emp); } ``` ## 三、缓存雪崩 #### 1、什么是缓存雪崩 大批量热点数据过期,一旦有高并发,产生数据库的崩溃现象 #### 2、解决方法 集群:长短键解决方法 * 长键:过期时间长的键 * 短键:过期时间短的键 先查询短键 -> 如果短键不存在,则查询数据库 -> 如果长键、短键都不存在,则查询数据库 ->如果长键未过期,则存入短键 修改service方法 ```java public Emp getMySQLAndRedisEmpById(Integer id) { Object obj = redisDao.get("short_Emp::" + id); System.out.println(obj); if (obj == null) { synchronized (EmpService.class) { obj = redisDao.get("long_Emp::" + id); if (obj == null) { obj = getEmpById(id); redisDao.save("short_Emp::" + id, obj, 300, TimeUnit.SECONDS); redisDao.save("long_Emp::" + id, obj, 600, TimeUnit.SECONDS); System.out.println("存入Redis"); }else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); redisDao.save("short_Emp::" + id, obj, 300, TimeUnit.SECONDS); redisDao.save("long_Emp::" + id, obj, 600, TimeUnit.SECONDS); } } } else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); redisDao.save("short_Emp::" + id, obj, 300, TimeUnit.SECONDS); redisDao.save("long_Emp::" + id, obj, 600, TimeUnit.SECONDS); } return (Emp) obj; } ``` Loading... ## 一、缓存击穿 #### 1、什么是缓存击穿 某个热点Key(高频访问)在缓存过期的一瞬间,大量并发请求同时击穿缓存层,直接访问数据库,导致数据库压力骤增甚至崩溃。 #### 2、出现条件 ##### Emp新增方法 ```java public static Emp mapToEmp(Object obj) { Map<String, Object> map = null; if (obj instanceof Map<?, ?>) { map = (Map<String, Object>) obj; } if (map == null) return new Emp(); return new Emp( (Integer) map.get("EMPno"), map.get("ename").toString(), map.get("job").toString(), (Integer) map.get("mgr"), map.get("hiredate").toString(), (Double) map.get("sal"), (Double) map.get("comm"), (Integer) map.get("DEPTno") ); } ``` ##### service方法 ```java public Emp getMySQLAndRedisEmpById(Integer id) { Object obj = redisDao.get("Emp::" + id); System.out.println(obj); if (obj == null) { obj = getEmpById(id); redisDao.save("Emp::" + id, obj, 300, TimeUnit.SECONDS); System.out.println("存入Redis"); } else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); } return (Emp) obj; } ``` ##### controller方法 ```java @GetMapping("/get2") public ResponseResult<Emp> getMySQLAndRedisEmpById(@RequestParam("id") Integer id) { Emp emp = empService.getMySQLAndRedisEmpById(id); if (emp == null) return new ResponseResult<>(null); return new ResponseResult<>(200, "查询成功", emp); } ``` ##### 压测工具   #### 3、现象 所有请求都在MySQL中查询数据  #### 4、解决方法 ##### 线程锁代码块 双重检测,线程同步 修改service代码,添加线程锁代码块 ```java public Emp getMySQLAndRedisEmpById(Integer id) { Object obj = redisDao.get("Emp::" + id); System.out.println(obj); if (obj == null) { synchronized (EmpService.class) { obj = redisDao.get("Emp::" + id); if (obj == null) { obj = getEmpById(id); redisDao.save("Emp::" + id, obj, 300, TimeUnit.SECONDS); System.out.println("存入Redis"); }else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); } } } else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); } return (Emp) obj; } ``` 效果  ##### 线程锁关键字(效率略低) ```java public synchronized Emp getMySQLAndRedisEmpById(Integer id) { Object obj = redisDao.get("Emp::" + id); System.out.println(obj); if (obj == null) { obj = getEmpById(id); redisDao.save("Emp::" + id, obj, 300, TimeUnit.SECONDS); System.out.println("存入Redis"); } else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); } return (Emp) obj; } ```  ## 二、缓存穿透 #### 1、什么是缓存穿透 如果频繁高热点提交数据库中不存在的记录,则Redis中永远没有数据,但是用户会确认访问MySQL数据库。 #### 2、出现条件 查询不存在的数据 #### 3、现象 每次查询都会访问MySQL数据库  #### 4、解决方法 ##### 布隆过滤器 提前将能查询到的所有集合缓存到布隆过滤器中,通过布隆过滤器集合来判断请求数据是否合法。 添加依赖 Maven仓库列表:[Maven Repository: com.google.guava » guava](https://mvnrepository.com/artifact/com.google.guava/guava) ```xml <!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>33.5.0-jre</version> </dependency> ``` 添加mapper查询方法 ```java @Select("select EMPno from EMP") List<Integer> listEmpAllIds(); ``` 创建配置类`util/BloomConfig.java` ```java package cn.doooog.mybatisplus.util; import cn.doooog.mybatisplus.mapper.EmpMapper; import com.google.common.hash.BloomFilter; import com.google.common.hash.Funnels; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; @Configuration public class BloomConfig { @Autowired private EmpMapper empMapper; @Bean public BloomFilter<Integer> init(){ List<Integer> listIds = empMapper.listEmpAllIds(); BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(),listIds.size(),0.001); for (Integer id :listIds){ filter.put(id); } return filter; } } ``` 在controller中添加布隆过滤器 ```java @Autowired private BloomFilter<Integer> bloomFilter; @GetMapping("/get2") public ResponseResult<Emp> getMySQLAndRedisEmpById(@RequestParam("id") Integer id) { if (!bloomFilter.mightContain(id)) { return new ResponseResult<>(null); } Emp emp = empService.getMySQLAndRedisEmpById(id); if (emp == null) return new ResponseResult<>(null); return new ResponseResult<>(200, "查询成功", emp); } ``` ## 三、缓存雪崩 #### 1、什么是缓存雪崩 大批量热点数据过期,一旦有高并发,产生数据库的崩溃现象 #### 2、解决方法 集群:长短键解决方法 * 长键:过期时间长的键 * 短键:过期时间短的键 先查询短键 -> 如果短键不存在,则查询数据库 -> 如果长键、短键都不存在,则查询数据库 ->如果长键未过期,则存入短键 修改service方法 ```java public Emp getMySQLAndRedisEmpById(Integer id) { Object obj = redisDao.get("short_Emp::" + id); System.out.println(obj); if (obj == null) { synchronized (EmpService.class) { obj = redisDao.get("long_Emp::" + id); if (obj == null) { obj = getEmpById(id); redisDao.save("short_Emp::" + id, obj, 300, TimeUnit.SECONDS); redisDao.save("long_Emp::" + id, obj, 600, TimeUnit.SECONDS); System.out.println("存入Redis"); }else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); redisDao.save("short_Emp::" + id, obj, 300, TimeUnit.SECONDS); redisDao.save("long_Emp::" + id, obj, 600, TimeUnit.SECONDS); } } } else { System.out.println("从Redis查询"); obj = Emp.mapToEmp(obj); redisDao.save("short_Emp::" + id, obj, 300, TimeUnit.SECONDS); redisDao.save("long_Emp::" + id, obj, 600, TimeUnit.SECONDS); } return (Emp) obj; } ``` 最后修改:2025 年 11 月 05 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 👆请简单粗暴地爱我。