当前位置: 首页 > news >正文

Redis缓存穿透雪崩击穿及解决

 

封装缓存空对象解决缓存穿透与逻辑过期解决缓存击穿工具类

@Slf4j
@Component
public class CacheClient {private final StringRedisTemplate stringRedisTemplate;public CacheClient(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}public void set(String key, Object value , Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}public void setWithLogicalExpire(String key, Object value , Long time, TimeUnit unit) {//设置逻辑过期时间RedisData redisData = new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));//写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}//解决缓存穿透的代码public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;//1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isNotBlank(json)) {//3.存在,直接返回return JSONUtil.toBean(json, type);}//判断命中的是否是空值,不为null 就为“” 因为存入的为“”  解决缓存穿透if(json != null){return null;}//4.不存在,根据id查询数据库QueryWrapper<Shop> queryWrapper = new QueryWrapper<>();queryWrapper.eq("id", id);R r = dbFallback.apply(id);//5.不存在,返回错误if(r == null){//将空值写入redisstringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);return null;}//6.存在,写入redisthis.set(key, r, time, unit);//7.返回return r;}//创建指定上线的线程池private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);//逻辑过期解决缓存击穿public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {String key = keyPrefix + id;//1.从redis查询商铺缓存String json = stringRedisTemplate.opsForValue().get(key);//2.判断是否存在if (StrUtil.isBlank(json)) {return null;}//4.命中,先把json反序列化为对象RedisData redisData = JSONUtil.toBean(json, RedisData.class);R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime = redisData.getExpireTime();//5.判断缓存是否过期if(expireTime.isAfter(LocalDateTime.now())){//5.1未过期,直接返回店铺信息return r;}//5.2已过期,需要缓存重建//6.缓存重建//6.1获取互斥锁boolean isLock = tryLock(LOCK_SHOP_KEY + id);//6.2判断是否获取互斥锁成功if(isLock){//6.3成功,开启独立线程,实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() ->{try {//查询数据库R r1 = dbFallback.apply(id);//写入redisthis.setWithLogicalExpire(key, r1, time, unit);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放锁unlock(LOCK_SHOP_KEY + id);}});}//6.4失败与成功,都返回过期的商铺信息return r;}//获取锁方法private boolean tryLock(String key){Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//方法的返回值为基本类型,需将b进行拆箱,拆箱过程中可能会出现空指针异常,所以要使用工具类return BooleanUtil.isTrue(b);//会进行自动拆箱(当传入的值是 null 时,它会返回 false。可以避免空指针异常,这里没用到)}//释放锁方法private void unlock(String key){stringRedisTemplate.delete(key);}
}

控制层调用

    @GetMapping("/{id}")public Result queryShopById(@PathVariable("id") Long id) {return shopService.queryById(id);}

服务层调用

Result queryById(Long id);
    @Overridepublic Result queryById(Long id){//缓存穿透
//        Shop shop = cacheClient.queryWithPassThrough(CACHE_SHOP_KEY, id ,Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);//互斥锁解决缓存击穿//Shop shop = queryWithMutex(id);//逻辑过期解决缓存击穿//缓存击穿测试时,需先用测试类添加数据到数据库Shop shop = cacheClient.queryWithLogicalExpire(CACHE_SHOP_KEY, id , Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);if(shop == null){return Result.fail("店铺不存在");}//返回return Result.ok(shop);}

常量工具类

public class RedisConstants {public static final String LOGIN_CODE_KEY = "login:code:";public static final Long LOGIN_CODE_TTL = 2L;public static final String LOGIN_USER_KEY = "login:token:";public static final Long LOGIN_USER_TTL = 36000L;public static final Long CACHE_NULL_TTL = 2L;public static final Long CACHE_SHOP_TTL = 30L;public static final String CACHE_SHOP_KEY = "cache:shop:";public static final String LOCK_SHOP_KEY = "lock:shop:";public static final Long LOCK_SHOP_TTL = 10L;
}

数据工具类

@Data
public class RedisData {private LocalDateTime expireTime;private Object data;
}

互斥锁解决缓存击穿

//互斥所解决缓存击穿public Shop queryWithMutex(Long id) {//1.从redis查询商铺缓存String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);//2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {//3.存在,直接返回return JSONUtil.toBean(shopJson, Shop.class);}//判断命中的是否是空值,不为null 就为“”  解决缓存穿透if(shopJson != null){return null;}Shop shop = null;try {//4.实现缓存重建//4.1 获取互斥锁boolean isLock = tryLock(LOCK_SHOP_KEY + id);//4.2 判断是否获取成功if(!isLock){//4.3 失败,则休眠并重试Thread.sleep(50);return queryWithMutex(id);}//4.4 成功,根据id查询数据库QueryWrapper<Shop> queryWrapper = new QueryWrapper<>();queryWrapper.eq("id", id);shop = shopMapper.selectOne(queryWrapper);//模拟重建的延时Thread.sleep(200);//5.不存在,返回错误if(shop == null){//将空值写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id,"",CACHE_NULL_TTL,TimeUnit.MINUTES);return null;}//6.存在,写入redisstringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop),CACHE_SHOP_TTL, TimeUnit.MINUTES);} catch (InterruptedException e) {throw new RuntimeException(e);} finally {//7.释放互斥锁unlock(LOCK_SHOP_KEY + id);}//7.返回return shop;}//获取锁方法private boolean tryLock(String key){Boolean b = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);//方法的返回值为基本类型,需将b进行拆箱,拆箱过程中可能会出现空指针异常,所以要使用工具类return BooleanUtil.isTrue(b);//会进行自动拆箱(当传入的值是 null 时,它会返回 false。可以避免空指针异常,这里没用到)}//释放锁方法private void unlock(String key){stringRedisTemplate.delete(key);}


http://www.mrgr.cn/news/39524.html

相关文章:

  • 如何使用 WSL 在 Windows 上安装 Linux
  • WingetUI:可视化Windows常用的命令行包管理工具
  • Vue3 + Vite 开发环境下解决跨域问题:配置代理服务器
  • postgreSql常用操作
  • 【拥抱AIGC】通义灵码网络代理配置
  • 《基于 Spring Boot 的健身房管理系统功能介绍》
  • 详细分析Java中的StopWatch基本知识(附Demo)
  • AI学习指南深度学习篇-批标准化的实现机制
  • Flume实战--Flume中的选择器、自动容灾(故障转移)、负载均衡的详解与操作
  • nginx的安装和使用
  • 1.8 软件业务测试
  • MySQL_触发器
  • 建筑物变化检测算法baseline工程,开箱即用,23年5月测试准确度超越阿里云aiearth
  • JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
  • 计算机视觉周边技术解析:从基础到前沿
  • Github 2024-09-30 开源项目周报 Top15
  • 初始MYSQL数据库(8)—— JDBC编程
  • jQuery UI 工作原理
  • 实战OpenCV之边缘检测
  • 【C++】多态(上)