1、实体类
/**
* @author syp
* @version 1.0
* @date 2022/11/16 20:37
*/
@Data
public class RedisData {
private LocalDateTime expireTime;
private Object data;
}
2、工具封装
package com.example.springbootsessiondemo.utils;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.example.springbootsessiondemo.pojo.RedisData;
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;
/**
* @author syp
* @version 1.0
* @date 2022/11/16 20:21
*/
@Component
public class RedisUtil {
@Resource
private StringRedisTemplate stringRedisTemplate;
//线程池
private static final ExecutorService CACHE_REBULID_EXECUTOR = Executors.newFixedThreadPool(10);
/**
* 将任意java对象序列化为json并存储在string类型的key中,并设置过期时间
* @param key redis string类型的 key
* @param value redis string类型的 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中,并设置逻辑过期时间,用于处理缓存击穿问题
*
* @param key
* @param value
* @param time
* @param unit
*/
public void setWithlogicaExpire(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));
}
/**
* 解决缓存穿透
* @param redisKey
* @param id
* @param type
* @param <R>
* @return
*/
public <R,ID> R queryWithpassThrouth(String redisKey, ID id, Class<R> type, Long time, TimeUnit unit, Function<ID,R> dbfallback){
//从redis取出数据
String json=stringRedisTemplate.opsForValue().get(redisKey);
//判断json是否存在
if(StrUtil.isNotBlank(json)){
//存在直接返回
return JSONUtil.toBean(json,type);
}
//判断命中的是否为空字符串
if("".equals(json)){
//返回错误信息
return null;
}
R r=dbfallback.apply(id);
//判断value是否为空值
if(r == null){
//将空值写入redis
stringRedisTemplate.opsForValue().set(redisKey,"",20,TimeUnit.SECONDS);
//返回错误信息
return null;
}
//存在写入redis
this.set(redisKey,r,time,unit);
return r;
}
/**
* 解决缓存击穿
* @param redisKey
* @param id
* @param value
* @param type
* @param dbfallback
* @param time
* @param unit
* @param <R>
* @param <ID>
* @return
*/
public <R,ID> R queryWithLogicExpire(String redisKey,ID id,Object value,Class<R> type,Function<ID,R> dbfallback,Long time, TimeUnit unit){
//从redis取出数据
String json=stringRedisTemplate.opsForValue().get(redisKey);
//判断json是否存在
if(StrUtil.isBlank(json)){
//存在直接返回
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;
}
//已过期需要重建缓存
boolean islock=tryLock(redisKey);
//判断是否获取到锁,获取到开启线程重建缓存
if(islock){
//开启独立线程重建缓存,
CACHE_REBULID_EXECUTOR.submit(()->{
try {
//查询数据库
R r1 = dbfallback.apply(id);
//写入redis
this.setWithlogicaExpire(redisKey,r1,time,unit);
} catch (Exception e) {
e.printStackTrace();
} finally {
delLock(redisKey);
}
});
}
return r;
}
//加互斥锁
private boolean tryLock(String lockKey){
//setnx 只有当key不存在时才会写入缓存
Boolean aboolean=stringRedisTemplate.opsForValue().
setIfAbsent(lockKey,"lock",10, TimeUnit.MINUTES);
return BooleanUtil.isTrue(aboolean);
}
//释放锁
private void delLock(String lockKey){
stringRedisTemplate.delete(lockKey);
}
}