package com.gao.redisdemo.utils;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.cglib.core.internal.Function;
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;
/**
* @author gao
*/
@Component
@SuppressWarnings("all")
public class CacheClient {
@Resource
StringRedisTemplate stringRedisTemplate;
/*
设置TTL的方式,存储值进入redis
*/
public void set(String key, Object value, Long time, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, timeUnit);
}
/*
设置逻辑缓存的方式,存储值进入redis
*/
public void setWithLogicExpire(String key, Object value, Long time, TimeUnit timeUnit) {
//设置过期时间
RedisData redisData = new RedisData();
redisData.setValue(value);
redisData.setLogicExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));
//写入redis
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
}
/*
基于互斥锁解决缓存穿透问题
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
解决方案,将不存在的信息(客户端和服务端都没有相关数据)传入null进入缓存
*/
public <R, ID> R queryWithPassThrough(String prefix, ID id, Class<R> type, Function<ID, R> dbMethod, Long time, TimeUnit timeUnit) {
String cacheKey = prefix + id;
//根据id查询redis中是否有缓存值
String json = stringRedisTemplate.opsForValue().get(cacheKey);
//判断是否存在,只有"abc"才为true
if (StringUtils.isNotBlank(json)) {
return JSON.parseObject(json, type);
}
//判断是否为null,不是则为“”,空字符窜情况
if (json != null) {
return null;
}
//redis缓存中没有相关值,走查询书库
R r = dbMethod.apply(id);
//如果r不存在,则缓存“”进入redis
if (r == null) {
stringRedisTemplate.opsForValue().set(cacheKey, "", time, TimeUnit.MINUTES);
return null;
}
//存在就存入相关信息进入redis
this.set(cacheKey, r, time, timeUnit);
return r;
}
/*
基于互斥锁解决缓存击穿问题
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
解决方案,1.采用互斥锁 2.采用逻辑过期(以下方案采取逻辑过期方案)
*/
//创建线程池
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
public <R, ID> R queryWithLogicExpire(String prefix, ID id, Class<R> type, Function<ID, R> dbMethod, Long time, TimeUnit timeUnit) {
String cacheKey = prefix + id;
//根据id查询redis中是否有缓存值
String json = stringRedisTemplate.opsForValue().get(cacheKey);
//判断是否存在
if (StringUtils.isBlank(json)) {
return null;
}
//命中,把json转化为相应Bean对象
RedisData redisData = JSONUtil.toBean(json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) redisData.getValue(), type);
LocalDateTime logicExpireTime = redisData.getLogicExpireTime();
//判断是否过期,未过期直接返回信息
if (logicExpireTime.isAfter(LocalDateTime.now())){
return r;
}
//已经过期,缓存重建
String localKey = "lock:" + prefix + id;
//获取到互斥锁
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(localKey, "1", 10, TimeUnit.SECONDS);
if (BooleanUtil.isTrue(aBoolean)){
CACHE_REBUILD_EXECUTOR.submit(()->{
try {
//查询数据库
R result = dbMethod.apply(id);
//写入缓存
this.setWithLogicExpire(cacheKey,result,time,timeUnit);
} catch (Exception e) {
e.printStackTrace();
}finally {
//释放互斥锁
stringRedisTemplate.delete(localKey);
}
});
}
return r;
}
/*
逻辑缓存对象,包括实际值和逻辑存活时间
*/
@Data
@ToString
@AllArgsConstructor
@NoArgsConstructor
static
class RedisData {
private Object value;
private LocalDateTime logicExpireTime;
}
}
关于redis解决缓存穿透和缓存击穿问题(java)
最新推荐文章于 2023-04-18 21:03:11 发布