day04-商家查询缓存03

打印 上一主题 下一主题

主题 872|帖子 872|积分 2616

功能02-商铺查询缓存03

3.功能02-商铺查询缓存

3.6封装redis工具类

3.6.1需求说明

基于StringRedisTemplate封装一个工具列,满足下列需求:
方法1:将任意Java对象序列化为json,并存储在string类型的key中,并且可以设置TTL过期时间
方法2:将任意Java对象序列化为json,并存储在string类型的key中,并且可以设置逻辑过期时间,用户处理缓存击穿问题(针对热点key)
方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题(针对热点key)
3.6.2代码实现

(1)创建redis工具类,封装上述方法
  1. package com.hmdp.utils;
  2. import cn.hutool.core.util.BooleanUtil;
  3. import cn.hutool.core.util.StrUtil;
  4. import cn.hutool.json.JSONObject;
  5. import cn.hutool.json.JSONUtil;
  6. import lombok.extern.slf4j.Slf4j;
  7. import org.springframework.data.redis.core.StringRedisTemplate;
  8. import org.springframework.stereotype.Component;
  9. import javax.annotation.Resource;
  10. import java.time.LocalDateTime;
  11. import java.util.concurrent.ExecutorService;
  12. import java.util.concurrent.Executors;
  13. import java.util.concurrent.TimeUnit;
  14. import java.util.function.Function;
  15. import static com.hmdp.utils.RedisConstants.*;
  16. /**
  17. * @author 李
  18. * @version 1.0
  19. * 封装redis工具类
  20. */
  21. @Component
  22. @Slf4j
  23. public class CacheClient {
  24.     @Resource
  25.     private StringRedisTemplate stringRedisTemplate;
  26.     /**
  27.      * 将任意Java对象序列化为json,并存储在string类型的key中,并且可以设置TTL过期时间
  28.      *
  29.      * @param key   缓存的key值
  30.      * @param value 缓存的value值
  31.      * @param time  过期时间值
  32.      * @param unit  过期的时间单位
  33.      */
  34.     public void set(String key, Object value, Long time, TimeUnit unit) {
  35.         stringRedisTemplate.opsForValue()
  36.                 .set(key, JSONUtil.toJsonStr(value), time, unit);
  37.     }
  38.     /**
  39.      * 将任意Java对象序列化为json,并存储在string类型的key中,
  40.      * 并且可以设置逻辑过期时间,用户处理缓存击穿问题(针对热点key)
  41.      *
  42.      * @param key   缓存的key值
  43.      * @param value 缓存的value值
  44.      * @param time  过期时间值
  45.      * @param unit  过期的时间单位
  46.      */
  47.     public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {
  48.         //设置逻辑过期时间
  49.         RedisData redisData = new RedisData();
  50.         redisData.setData(value);
  51.         //逻辑过期时间=当前时间+指定的时间
  52.         redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
  53.         stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
  54.     }
  55.     /**
  56.      * 根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
  57.      *
  58.      * @param keyPrefix  查询的key值的前缀
  59.      * @param id         查询的key值的后缀
  60.      * @param type       要转换的Class类型
  61.      * @param dbFallback 传入的函数
  62.      * @param time       过期时间值
  63.      * @param unit       时间单位
  64.      * @param <R>        泛型
  65.      * @param <ID>       泛型
  66.      * @return 返回指定的类型对象
  67.      */
  68.     public <R, ID> R queryWithPassThrough(
  69.             String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
  70.         String key = keyPrefix + id;
  71.         //redis查询缓存
  72.         String json = stringRedisTemplate.opsForValue().get(key);
  73.         //判断json是否存在
  74.         if (StrUtil.isNotBlank(json)) {
  75.             //存在,转为java对象并返回
  76.             return JSONUtil.toBean(json, type);
  77.         }
  78.         //判断是否为"",如果是,说明该key是为了解决缓存穿透设置的空值
  79.         if ("".equals(json)) {
  80.             //返回错误信息
  81.             return null;
  82.         }
  83.         //不存在,根据id查询数据库——使用函数式编程
  84.         R r = dbFallback.apply(id);
  85.         if (r == null) {//说明数据库中没有该数据
  86.             //缓存空值,应对缓存穿透
  87.             stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
  88.             //返回错误信息
  89.             return null;
  90.         }
  91.         //r存在,则将其写入redis
  92.         this.set(key, r, time, unit);
  93.         return r;
  94.     }
  95.     private static final ExecutorService CACHE_REBUILD_EXECUTOR =
  96.             Executors.newFixedThreadPool(10);
  97.     /**
  98.      * 根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题(针对热点key)
  99.      * @param keyPrefix  查询的key值的前缀
  100.      * @param id         查询的key值的后缀
  101.      * @param type       要转换的Class类型
  102.      * @param dbFallback 传入的函数
  103.      * @param time       过期时间值
  104.      * @param unit       时间单位
  105.      * @param <R>        泛型
  106.      * @param <ID>       泛型
  107.      * @return 返回指定的类型对象
  108.      */
  109.     public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,
  110.                                             Function<ID, R> dbFallback, Long time,
  111.                                             TimeUnit unit) {
  112.         String key = keyPrefix + id;
  113.         String json = stringRedisTemplate.opsForValue().get(key);
  114.         //这里不再考虑缓存穿透问题,因为key永不过期
  115.         if (StrUtil.isBlank(json)) {
  116.             //如果未命中,说明不是热点key,直接返回null
  117.             return null;
  118.         }
  119.         //如果命中
  120.         //先把json反序列化为对象
  121.         RedisData redisData = JSONUtil.toBean(json, RedisData.class);
  122.         R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
  123.         LocalDateTime expireTime = redisData.getExpireTime();
  124.         //判断是否逻辑过期
  125.         if (expireTime.isAfter(LocalDateTime.now())) {
  126.             //未过期,直接返回信息
  127.             return r;
  128.         }
  129.         //过期,获取互斥锁
  130.         String lockKey = LOCK_SHOP_KEY + id;
  131.         boolean isLock = tryLock(lockKey);
  132.         if (isLock) {//成功获取互斥锁
  133.             //开启独立线程
  134.             CACHE_REBUILD_EXECUTOR.submit(() -> {
  135.                 try {
  136.                     //重建缓存
  137.                     //先查询数据库
  138.                     R apply = dbFallback.apply(id);
  139.                     //再存入reids缓存
  140.                     this.setWithLogicalExpire(key, apply, time, unit);
  141.                 } catch (Exception e) {
  142.                     throw new RuntimeException(e);
  143.                 }
  144.                 //释放互斥锁
  145.                 unLock(lockKey);
  146.             });
  147.         }
  148.         //如果未获取互斥锁,直接返回旧数据
  149.         return r;
  150.     }
  151.     private boolean tryLock(String key) {
  152.         Boolean flag = stringRedisTemplate.opsForValue()
  153.                 .setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
  154.         return BooleanUtil.isTrue(flag);
  155.     }
  156.     private void unLock(String key) {
  157.         stringRedisTemplate.delete(key);
  158.     }
  159. }
复制代码
(2)修改ShopServiceImpl,调用封装好的方法,简化代码
  1. package com.hmdp.service.impl;
  2. import ...
  3. /**
  4. * 服务实现类
  5. *
  6. * @author 李
  7. * @version 1.0
  8. */
  9. @Service
  10. public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop>
  11.         implements IShopService {
  12.     @Resource
  13.     StringRedisTemplate stringRedisTemplate;
  14.     @Resource
  15.     private CacheClient cacheClient;
  16.     @Override
  17.     public Result queryById(Long id) {
  18.         //缓存穿透
  19.         //Shop shop =
  20.         //        cacheClient.queryWithPassThrough
  21.         //                (CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);
  22.         //缓存击穿方案(逻辑过期)
  23.         Shop shop = cacheClient.queryWithLogicalExpire
  24.                 (CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.MINUTES);
  25.         if (shop == null) {
  26.             return Result.fail("店铺不存在!");
  27.         }
  28.         return Result.ok(shop);
  29.     }
  30.     @Override
  31.     @Transactional
  32.     public Result update(Shop shop) {
  33.         ...
  34.     }
  35. }
复制代码
3.7缓存总结


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

水军大提督

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表