缓存穿透+缓存雪崩+缓存击穿(解决方法+实战)
声明:这是我根据黑马程序员视频做的学习笔记!(融入了自己的理解)感兴趣的可以去看原视频!
https://www.bilibili.com/video/BV1cr4y1671t/?spm_id_from=333.337.search-card.all.click
那么接下来开始我的博客
缓存穿透
产生原因:
缓存穿透是指客户端请求的数据再缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库,给数据库带来巨大压力
场景: 不怀好意的人 用多线程的形式去查询一个id不存在的信息,redis未命中,mysql中也没有 此时会给用户返回null值,线程继续用null 去查询信息,就会搞垮数据库
解决方案:
1.缓存null值(被动解决)
redis会将不存在的id存入缓存,就会阻挡访问mysql,比较暴力
优点:简单
缺点:额外的内存消耗,可能造成短期的不一致
2.布隆过滤(实际是种算法,依旧是被动姐u额):
布隆过滤器 底层是一个Byte[]数组,里面存的是二进制位
判断数据是否存在时,实际上是把这些数据基于某种哈希算法,再将哈希值转换成二进制位保存到布隆过滤器里,判断是否存在的是否就比较01二进制,
这种存在是概率上的问题,如存准确率达不到100%,存在可能不存在,但不存在真的不存在!
优点:空间占用非常的小
缺点:实现复杂,存在误判
3.雪花算法(主动避免缓存穿透)
解决商铺查询的缓存穿透问题
核心代码:
@Overridepublic Result queryById(Long id) {//1,从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY+id);//2.判断是否存在,并且shopJson不是null或" "if (StrUtil.isNotBlank(shopJson)) {//3.直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return Result.ok(shop);}//判断是是否是空值,若判断条件成立,此时shopJson只能是" "if(shopJson != null){return Result.fail("该店铺不存在");}//4.不存在,根据id查询数据库Shop shop = getById(id);//5.数据库不存在则返回错误if (shop == null) {stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);return Result.fail("店铺不存在");}//6,数据库若存在,将数据库写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY+id,JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);//7.返回return Result.ok(shop);}
缓存雪崩
缓存雪崩是指同一时间段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案:
1.给不同的key的TTL添加随机值,(将key失效时间分散开来,避免同时失效)
2.利用Redis集群提高服务的可用性(避免redis宕机)
3.非缓存业务添加降级限流策略
4.给业务添加多级缓存
缓存击穿
缓存击穿问题也是热点Key问题,就是一个被高并发访问把那个且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
解决方案:
1.互斥锁
2.逻辑过期
实际上添加这个把这个key-value添加到缓存之后就永远不会过期,我们只是在value上添加了一个逻辑及过期时间,每次线程访问缓存的时候就可以判断逻辑上过没过期(避免大量的请求直接落到数据库),那过期了怎么办,为了解决互斥锁的互相等待问题,当一个线程发现逻辑时间过期,就会加锁
,但是它会创建一个新的线程去做查询数据库,重写缓存,重置逻辑时间的操作,其他线程发现,缓存被加锁,先使用旧数据。
二者对比
基于互斥锁解决缓存击穿问题
流程图:
获取锁命令:setnx lock 操作名(设置有效期,兜底方案,防止某些情况 del lock不运行,迟迟不释放锁)
释放锁命令:del lock
核心代码:
public Shop queryWithMutex(Long id) {//1,从redis查询商铺缓存String key = CACHE_SHOP_KEY + id;String shopJson = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在,并且shopJson不是null或" "if (StrUtil.isNotBlank(shopJson)) {//3.直接返回Shop shop = JSONUtil.toBean(shopJson, Shop.class);return shop;}//判断是是否是空值,若判断条件成立,此时shopJson只能是" "if(shopJson != null){return null;}//4.实现缓存重建//4.1获取互斥锁String lockKey = "Lock:"+key;Shop shop = null;try {boolean isLock = tryLock(lockKey);//4.2判断是否获取成功if(!isLock){//4.3如果失败则休眠 并重试Thread.sleep(50);//休眠后重试return queryWithMutex(id);}//4.4如果成功 根据id查询数据库shop = getById(id);//**模拟重建的延迟Thread.sleep(1000);//5.不存在,则返回错误if (shop == null) {stringRedisTemplate.opsForValue().set(key, "", 2, TimeUnit.MINUTES);return null;}//6,数据库若存在,将数据库写入redisstringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {unlock(lockKey);}return shop;}
@Overridepublic Result queryById(Long id) {//缓存穿透//Shop shop = queryWithPassThrough(id);//互斥锁解决缓存击穿Shop shop = queryWithMutex(id);if (shop == null) {return Result.fail("店铺不存在");}return Result.ok(shop);}