首页 > 其他分享 >day04-商家查询缓存03

day04-商家查询缓存03

时间:2023-04-21 22:11:25浏览次数:35  
标签:03 缓存 过期 param day04 json key import

功能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工具类,封装上述方法

package com.hmdp.utils;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

import static com.hmdp.utils.RedisConstants.*;

/**
 * @author 李
 * @version 1.0
 * 封装redis工具类
 */
@Component
@Slf4j
public class CacheClient {
    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 将任意Java对象序列化为json,并存储在string类型的key中,并且可以设置TTL过期时间
     *
     * @param key   缓存的key值
     * @param value 缓存的value值
     * @param time  过期时间值
     * @param unit  过期的时间单位
     */
    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue()
                .set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    /**
     * 将任意Java对象序列化为json,并存储在string类型的key中,
     * 并且可以设置逻辑过期时间,用户处理缓存击穿问题(针对热点key)
     *
     * @param key   缓存的key值
     * @param value 缓存的value值
     * @param time  过期时间值
     * @param 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)));
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    /**
     * 根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
     *
     * @param keyPrefix  查询的key值的前缀
     * @param id         查询的key值的后缀
     * @param type       要转换的Class类型
     * @param dbFallback 传入的函数
     * @param time       过期时间值
     * @param unit       时间单位
     * @param <R>        泛型
     * @param <ID>       泛型
     * @return 返回指定的类型对象
     */
    public <R, ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit) {
        String key = keyPrefix + id;
        //redis查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        //判断json是否存在
        if (StrUtil.isNotBlank(json)) {
            //存在,转为java对象并返回
            return JSONUtil.toBean(json, type);
        }
        //判断是否为"",如果是,说明该key是为了解决缓存穿透设置的空值
        if ("".equals(json)) {
            //返回错误信息
            return null;
        }
        //不存在,根据id查询数据库——使用函数式编程
        R r = dbFallback.apply(id);
        if (r == null) {//说明数据库中没有该数据
            //缓存空值,应对缓存穿透
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            //返回错误信息
            return null;
        }
        //r存在,则将其写入redis
        this.set(key, r, time, unit);
        return r;
    }

    private static final ExecutorService CACHE_REBUILD_EXECUTOR =
            Executors.newFixedThreadPool(10);

    /**
     * 根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题(针对热点key)
     * @param keyPrefix  查询的key值的前缀
     * @param id         查询的key值的后缀
     * @param type       要转换的Class类型
     * @param dbFallback 传入的函数
     * @param time       过期时间值
     * @param unit       时间单位
     * @param <R>        泛型
     * @param <ID>       泛型
     * @return 返回指定的类型对象
     */
    public <R, ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type,
                                            Function<ID, R> dbFallback, Long time,
                                            TimeUnit unit) {
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        //这里不再考虑缓存穿透问题,因为key永不过期
        if (StrUtil.isBlank(json)) {
            //如果未命中,说明不是热点key,直接返回null
            return null;
        }
        //如果命中
        //先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        LocalDateTime expireTime = redisData.getExpireTime();
        //判断是否逻辑过期
        if (expireTime.isAfter(LocalDateTime.now())) {
            //未过期,直接返回信息
            return r;
        }
        //过期,获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        if (isLock) {//成功获取互斥锁
            //开启独立线程
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try {
                    //重建缓存
                    //先查询数据库
                    R apply = dbFallback.apply(id);
                    //再存入reids缓存
                    this.setWithLogicalExpire(key, apply, time, unit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
                //释放互斥锁
                unLock(lockKey);
            });
        }
        //如果未获取互斥锁,直接返回旧数据
        return r;
    }

    private boolean tryLock(String key) {
        Boolean flag = stringRedisTemplate.opsForValue()
                .setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unLock(String key) {
        stringRedisTemplate.delete(key);
    }
}

(2)修改ShopServiceImpl,调用封装好的方法,简化代码

package com.hmdp.service.impl;

import ...

/**
 * 服务实现类
 *
 * @author 李
 * @version 1.0
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop>
        implements IShopService {
    @Resource
    StringRedisTemplate stringRedisTemplate;

    @Resource
    private CacheClient cacheClient;

    @Override
    public Result queryById(Long id) {
        //缓存穿透
        //Shop shop =
        //        cacheClient.queryWithPassThrough
        //                (CACHE_SHOP_KEY, id, Shop.class, this::getById, CACHE_SHOP_TTL, TimeUnit.MINUTES);

        //缓存击穿方案(逻辑过期)
        Shop shop = cacheClient.queryWithLogicalExpire
                (CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.MINUTES);

        if (shop == null) {
            return Result.fail("店铺不存在!");
        }
        return Result.ok(shop);
    }

    @Override
    @Transactional
    public Result update(Shop shop) {
        ...
    }
}

3.7缓存总结

标签:03,缓存,过期,param,day04,json,key,import
From: https://www.cnblogs.com/liyuelian/p/17342010.html

相关文章

  • 03 客户端、创建数据库和表
    03客户端、创建数据库和表1.掌握建库、表、删库、查询库操作2.理解表的数据类型3.掌握建表操作按《刑法》第286条和相关司法解释,“删库跑路”等行为,如果造成10台以上的系统无法正常运行就可以判刑,如果造成50台以上则至少判处5年。一.连接MySQLCLI方式:启......
  • 003CCE Turbo配置容器网卡动态预热
    更新时间:2023-04-13GMT+08:00Reference: CCETurbo配置容器网卡动态预热_云容器引擎CCE_最佳实践_网络_华为云(huaweicloud.com)在云原生网络2.0下,每个Pod都会分配(申请并绑定)一张弹性网卡或辅助弹性网卡(统一称为:容器网卡)。由于容器场景下Pod的极速弹性与慢速的容器网卡创建......
  • tomcat提示静态文件缓存超限,造成日志爆满的问题
    日志片段:21-Apr-202311:20:47.215警告[http-nio-80-exec-5308]org.apache.catalina.webresources.Cache.getResourceUnabletoaddtheresourceat[/FileUploads/www/site/2022/11/30/ZZVRQAHD08ZX4GOW47.jpg]tothecacheforwebapplication[]becausetherewasin......
  • CentOS网卡无法启动返回'Failed to start LSB:Bring up/down networking.'
    装了一台虚机,配置docker服务的时候发现忘了开CPU虚拟化,关机开启后再登录,发现网卡down了,重启网卡报错。1.journalctl-ex  #查看日志,发现返回错误'FailedtostartLSB:Bringup/downnetworking.';2.vi/var/long/messages  #再查看系统日志,发现有关于NetworkManager的信......
  • 'props' is not defined.
    这个错误表明在组件中,变量props没有被定义。通常情况下,我们需要在组件选项中指定props,并传递它们给setup()函数:'props'isnotdefined.exportdefaultdefineComponent({name:"message-out",props:{image:String,time:Number,text:String,......
  • Java技术_基础技术(0003)_类执行顺序详解+实例(阿里面试题)+详细讲解+流程图
    一、总体原则列出执行顺序的原则(这里本人出了简化,比较明了。可能有漏的,请帮忙补充,但应付该实例足以):  ==父类先于子类;  ==静态先于非静态;  ==变量和块先于构造方法;  ==变量声明先于执行(变量赋值、块执行);(这一点是根据数据在内存中是如何存储的得出的,基本类型、对象、......
  • ABB工业控制器 UNITROL 1020 3BHE030579R0003 UNS0119A-Z,V1 3BHE030579R0001
    W;1  ⑧0③0①⑦77⑤  9ABBUNITROL10203BHE030579R0003 UNS0119A-Z,V13BHE030579R0001 DDC779BE023BHE006805R0002 XO08R21SBP260109R1001 PM866K013BSE050198R1 CI858K013BSE018135R1 PU515A3BSE032401R1 励磁控制系统的主要任务是向发电机......
  • 人月神话读书笔记03
    本次阅读第七章 我过去是怎么做的在编程之前没有清晰的目标,写到什么就去做什么这种做法为什么不好思路不够清晰,导致编程没有逻辑性如何解决:7.为什么巴比伦塔会失败?关于巴比伦塔的故事:维基百科TowerofBabel7.1巴比伦塔的管理教训据《创世纪》记载,巴比伦塔是人类继......
  • Redis 缓存失效问题
    目录Redis缓存缓存击穿场景解决方案:缓存穿透场景解决方案缓存雪崩场景解决方案大量数据同时过期Redis故障宕机Redis缓存引入了缓存层,就会有缓存异常的三个问题,分别是缓存雪崩、缓存击穿、缓存穿透。它们的区别如下:缓存击穿场景高并发流量场景下,大量请求同时访问一个热点......
  • redis03 持久化方案 主从复制原理和方案 哨兵高可用
    今日内容详细目录今日内容详细1持久化方案1.1RDB1.2AOF方案1.3混合持久化2主从复制原理和方案3哨兵高可用1持久化方案#什么是持久化redis的所有数据保存在内存中,把内存中的数据同步到硬盘上这个过程称之为持久化#持久化的实现方式 快照:某时某刻数据的一个完成备份......