这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
前面学习和记录了缓存穿透这一问题,接下来继续学习项目开发时可能会遇到的与缓存相关的问题。本次记录的是关于缓存雪崩和缓存击穿的学习笔记。
一.缓存雪崩
- 发生原因:同一时段大量缓存同时失效,或redis服务宕机导致大量请求到达数据库
- 解决方案
- 给不同的key的TTL添加随机值,防止同时失效
- 利用redus集群提高服务的可用性
- 给缓存业务添加降级限流策略
- 给业务添加多级缓存
二.缓存击穿
- 一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击
- 解决方案
- 互斥锁
- 原理:重建过程中一个线程上锁,其它线程获取锁失败则等待,直到那个线程重建缓存完毕释放锁,但性能可能比较差
- 案例
public Shop queryWithMutex(Long id){ String key = RedisConstants.CACHE_SHOP_KEY + id; Shop shop = new Shop(); try { shop = redisService.getObject(key, Shop.class); if(!Objects.isNull(shop)){ return shop; }else { String s = redisService.get(key); if(s.equals("null")){ return null; } } } catch (Exception e){ log.info("null"); } //1.实现缓存重建互斥锁 //1.1.获取互斥锁 String lockKey = RedisConstants.LOCK_SHOP_KEY + id; try { boolean isLock = tryLock(lockKey); //1.2.判断是否获取成功 if(!isLock){ //不成功则休眠,并重新尝试 Thread.sleep(50); return queryWithMutex(id); } //1.3.成功,根据id查询数据库 shop = getById(id); //模拟重建延迟 //Thread.sleep(2000); if(Objects.isNull(shop)){ //保存空值,针对缓存穿透问题 redisService.add(key,"null",RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES); return null; } //1.4.写入redis redisService.add(key,shop,RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES); } catch (InterruptedException e) { throw new RuntimeException(e); } finally { //1.5.释放锁 unlock(lockKey); } return shop; } - 逻辑过期
- 对于缓存入redis的数据自定义增加一个字段用于记录过期时间,当每次获取数据时,先判断数据是否过期,没过期则直接返回,若过期则上锁,开启独立线程用于重构数据,并先将旧数据返回。此时若其它线程请求同一份数据,因为获取不了锁所以不能开启线程进行数据重构,此时就直接将旧数据返回。
- 互斥锁