封装Redis工具类

基于StringRedisTemplate封装一个缓存工具类,满足以下需求:

  • 方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间
  • 方法2:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间,用于处理缓存击穿问题
  • 方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方法解决缓存穿透问题
  • 方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题

方法1,方法3对应是普通的缓存,解决缓存穿透,设置TTL

方法2,方法4结合起来是解决热点key,解决击穿问题.

java 复制代码
package com.hmdp.utils;

import lombok.Data;

import java.time.LocalDateTime;

@Data
public class RedisData {
    private LocalDateTime expireTime;//逻辑过期时间
    private Object data;//万能存储数据的对象 对原来实体不需要修改 也可以使用继承
}
java 复制代码
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 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.CACHE_SHOP_KEY;
import static com.hmdp.utils.RedisConstants.LOCK_SHOP_KEY;

@Slf4j
@Component //将来这个bean由Spring维护
public class CacheClient {
    private final StringRedisTemplate stringRedisTemplate;

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    //方法1
    public void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
    }

    //方法2
    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)));
        //写入redis
        stringRedisTemplate.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;
        //查询缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        //判断是否存在
        if(StrUtil.isNotBlank(json)){
            //存在,直接返回
            return JSONUtil.toBean(json,type);
        }
        //判断是否为空值
        if(json!=null){
            //返回一个错误信息
            return null;
        }

        //不存在,根据id查询数据库
        //传入函数有参有返回值的函数Function 参数类型ID 返回值类型R
        //函数式编程 就是定义的时候参数传了个接口,用到时传入接口的实现类
        R r = dbFallback.apply(id);
        //不存在,返回错误
        if(r==null){
            //将空值写入redis
            stringRedisTemplate.opsForValue().set(key,"",time,unit);
            //返回错误信息
            return null;
        }
        this.set(key,r,time,unit);
        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查询商铺缓存  也可以用springcache
        String json = stringRedisTemplate.opsForValue().get(key);
        //2.判断是否存在
        if (StrUtil.isBlank(json)) {
            //3.存在,直接返回
            return null;
        }
        //4.命中,需要先把json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        JSONObject data = (JSONObject) redisData.getData();
        R r = JSONUtil.toBean(data,type);
        LocalDateTime expireTime = redisData.getExpireTime();
        //5.判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())){
            //5.1 未过期 直接返回店铺信息
            return r;
        }

        //5.2 已过期,需要缓存重建

        //6.重建缓存
        //6.1 获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        //6.2 判断是否获取锁成功
        //注意: 获取锁成功应该再次检测redis缓存是否过期,做DoubleCheck.如果存在则无需重建缓存
        if(isLock){
            //6.3 成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() ->{
               //查询数据库
                R apply = dbFallback.apply(id);
                //写入redis
                this.setWithLogicalExpire(key,apply,time,unit);
            });
        }
        //6.4 返回过期商铺信息
        return r;
    }
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key,"1",10,TimeUnit.SECONDS);
        //需要转化为基本类型返回 如果直接return flag是有可能发生拆箱的,可能出现空指针
        return BooleanUtil.isTrue(flag);
    }

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

}

应用:

java 复制代码
@Resource
    private CacheClient cacheClient;
    @Override
    public Result queryById(Long id) {
        //缓存穿透
        //Shop shop = queryWIthPassThrough(id);
//        id2 -> getById(id2) == this::getById
//        Shop shop = cacheClient.
//                queryWithPassThrough(CACHE_SHOP_KEY,id,Shop.class,this::getById,CACHE_SHOP_TTL,TimeUnit.MINUTES);

        //互斥锁解决缓存击穿
//        Shop shop = queryWithMutex(id);

        //逻辑过期解决缓存击穿
//        Shop shop = queryWithLogicalExpire(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);
    }
相关推荐
ekskef_sef7 分钟前
Spring Boot——日志介绍和配置
java·数据库·spring boot
理想青年宁兴星36 分钟前
【RabbitMQ】rabbitmq广播模式的使用
java·rabbitmq·java-rabbitmq
北京_宏哥42 分钟前
《手把手教你》系列技巧篇(四十)-java+ selenium自动化测试-JavaScript的调用执行-下篇(详解教程)
java·selenium·前端框架
V+zmm101341 小时前
基于微信小程序的中国各地美食推荐平台的设计与实现springboot+论文源码调试讲解
java·微信小程序·小程序·毕业设计
magic 2451 小时前
JVM体系结构
java·开发语言·jvm·intellij-idea·idea
计算机-秋大田1 小时前
基于微信小程序的购物系统设计与实现(LW+源码+讲解)
java·后端·微信小程序·小程序·课程设计
GGBondlctrl1 小时前
【Spring Boot】掌握 Spring 事务:隔离级别与传播机制解读与应用
java·spring boot·spring·事务隔离级别·spring事务传播机制
neter.asia1 小时前
Redis和MongoDB的区别
redis·mongodb
DanceDonkey1 小时前
SpringCloud -根据服务名获取服务运行实例并进行负载均衡
java·spring cloud·负载均衡
呼啦啦啦啦啦啦啦啦1 小时前
【Redis】在Java中以及Spring环境下操作Redis
java·redis·spring