在学习缓存穿透和缓存雪崩之前,先来了解以下关于缓存命中的概念
目录
缓存命中:指数据在缓存中查询到,不用再访问 mysql 数据库
一. 缓存穿透
用户或者攻击者大量访问不存在于缓存和 mysql 中的数据,此时缓存被穿透失去作用,当高并发或有人利用不存在的 Key 频繁攻击时,底层数据库的压力骤增,甚至崩溃
模拟缓存穿透
ProductController 类和 JedisUtil 类在上一篇博客中
public class TestCachePenetration {
public static void main(String[] args) {
ProductController product = new ProductController();
// 大量请求查询不存在 redis 和 mysql 的数据
for (int i = 0; i<100; i++){
product.getProductById(-1);
}
}
}
解决办法1:缓存不存在的 key
@RestController
public class ProductController {
@Resource
ProductMapper productMapper;
@GetMapping("/getProductById")
public Product getProductById(int id){
Product product = null;
// 1. 查看 redis 缓存中是否有数据
product = JedisUtil.getProductByRedis(id);
if(product == null){ // redis 中没有该商品
System.out.println("redis中没有该商品");
// 进 mysql 中查询
product = productMapper.getProductById(id);
if(product==null){ // mysql 中没有
System.out.println("mysql 中未查询到该商品");
// 缓存穿透---创建一个不存在的key
Product p = new Product(id," ", 0.0F,0);
JedisUtil.saveToRedis(p);
}else { // mysql 中有
System.out.println("mysql: "+product);
// 返回给前端的同时也要将数据写入到 redis 中
long flag = JedisUtil.saveToRedis(product);
System.out.println("save flag:"+flag);
}
}else { // redis 中有该商品
System.out.println("redis: " + product);
}
return product;
}
}
解决办法2:布隆过滤器
用途:布隆过滤器一般用于在大数据量集合中判定某个元素是否存在
特点:
- 布隆过滤器判断存在,只是可能存在,不一定真的存在
- 布隆过滤器判断不存在,则一定不存在
原理:当某个 key 被加入集合时,通过 k 个 hash 函数将这个 key 映射成到位数组中的 k 个点,将 这些点置为 1。当查询数据时,先进布隆过滤器查询,将查询元素通过 hash 函数映射到 k 个点,如果这些点全是 1,则该元素可能存在,进 redis 缓存或者 mysql 数据库查询;若这些点有任何一个是 0,则该元素一定不存在,直接返回,无需再查询缓存和数据库
二. 缓存击穿
当某个热点key失效,短时间内大量高并发对该key的请求就会穿过redis缓存,到达底层mysql数据库,短时间内导致数据库压力骤增,数据库崩溃
解决方法:
- 对于某个热点key,可以设置某段时间内永不过期,防止失效产生缓存击穿
- 加互斥锁,当多个请求到达底层mysql数据库时,给请求线程加互斥锁,当第一个线程拿到数据后,会将数据写入到缓存中,这样其他等待的请求就可以从缓存中直接读取数据
三. 缓存雪崩
当大量热点 key 被同时缓存进 redis 中,就会导致某个时刻这些热点 key 同时过期,此时大量高并发的查询热点 key 的请求被转发到底层 mysql 数据库,从而导致数据库崩溃,甚至宕机
与缓存击穿区别:
- 缓存击穿是指单个key过期,大量请求穿过缓存到达底层数据库
- 缓存雪崩是成片的,指多个key同时失效,导致大量高并发请求
解决办法1:给 key 设置生命时间时添加一个随机值
// 存储商品到 redis---key:(product:id) field: value
public static long saveToRedis(Product product){
Jedis jedis = JedisUtil.getJedisPool();
HashMap<String,String> hashMap = new HashMap();
hashMap.put("name",product.getName());
hashMap.put("price",String.valueOf(product.getPrice()));
hashMap.put("category",String.valueOf(product.getCategory()));
String key = "product:" + product.getId();
long flag = jedis.hset(key, hashMap);
// 生成一个 [0,100) 左闭右开的随机数
int random = new Random().nextInt(100);
jedis.expire(key,3600L+random);
jedis.close();
return flag;
}
解决办法2:
如果是因为Redis服务器宕机,导致缓存失效,产生的缓存雪崩,可以通过构建主从复制,哨兵机制,cluster集群模式,来防止因为redis服务器故障产生的雪崩问题

本文介绍了缓存系统中的三种常见问题:缓存穿透是大量请求不存在的数据,导致数据库压力增大;缓存击穿是热点key失效时,请求直接落到数据库;缓存雪崩则是多个热点key同时过期,引发数据库崩溃。针对这些问题,提出了缓存不存在的key、使用布隆过滤器、设置key的随机过期时间等解决方案。
1479

被折叠的 条评论
为什么被折叠?



