封装Redis缓存工具类
2026年2月20日大约 2 分钟
封装Redis缓存工具类
为了简化缓存操作,封装了一个Redis缓存工具类CacheClient,提供一些常用的方法来操作Redis缓存,例如存入缓存对象、查询缓存对象等。
相关信息
- 对缓存穿透的解决方法进行了封装,提供了
queryWithPassThrough方法来查询缓存并解决缓存穿透问题。 - 对缓存击穿(热点key问题)的解决方法进行了封装,提供了
queryWithLogicalExpire方法来查询缓存并解决缓存击穿问题。
注意
使用了第三方工具类cn.hutool,用于简化JSON序列化和反序列化的操作,以及字符串操作和布尔值处理。
@Slf4j
@Component
public class CacheClient {
private final StringRedisTemplate stringRedisTemplate;
public CacheClient(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 存入缓存对象到Redis
*/
public void set(String key, Object value, Long time, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, timeUnit);
}
/**
* 存入逻辑过期的缓存对象到Redis
*/
public void setWithLogical(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));
}
/**
* 通过Key查询缓存-并解决缓存穿透
*/
public <R,E> R queryWithPassThrough(String keyPrefix, E id, Class<R> type, Function<E, R> dbFallback, Long time, TimeUnit unit) {
String key = keyPrefix + id;
// 从Redis中查询商铺
String json = stringRedisTemplate.opsForValue().get(key);
if (StrUtil.isNotEmpty(json)) {
return JSONUtil.toBean(json, type);
}
// 如果未空字符串,返回不存在
if (json != null) {
return null;
}
// 没有命中缓存,去数据库查询
R r = dbFallback.apply(id);
if (r == null) {
// 防止缓存穿透
stringRedisTemplate.opsForValue().set(key, "",CACHE_NULL_TTL,TimeUnit.MINUTES);
return null;
}
// 存入缓存,并且返回
this.set(key, r, time, unit);
return r;
}
/**
* 通过Key查询缓存-并解决击穿
*/
private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
public <R,E> R queryWithLogicalExpire(String keyPrefix, E id,Class<R> type,Function<E,R> dbFallback, Long time, TimeUnit unit) {
String key = keyPrefix+id;
// 1. 从Redis中查询商铺
String json = stringRedisTemplate.opsForValue().get(key);
// 2. 判断是否存在缓存
if (StrUtil.isBlank(json)) {
// 3. 不存在直接返回
return null;
}
// 4. 命中,判断过期时间
// 5. 反序列化为对象
RedisData rd = JSONUtil.toBean(json, RedisData.class);
R r = JSONUtil.toBean((JSONObject) rd.getData(), type);
LocalDateTime expireTime = rd.getExpireTime();
// 6. 判断是否过期
if(expireTime.isAfter(LocalDateTime.now())){
// 未过期直接返回
return r;
}
// 已过期 缓存重建
// 7. 获取互斥锁
String lockKey = LOCK_SHOP_KEY+id;
boolean isLock = tryLock(lockKey);
// 8. 判断锁是否获取成功
if (isLock){
// 重新从Redis读取缓存 doubleCheck
String shopJson2 = stringRedisTemplate.opsForValue().get(key);
RedisData rd2 = JSONUtil.toBean(shopJson2, RedisData.class);
r = JSONUtil.toBean((JSONObject) rd2.getData(), type);
LocalDateTime expireTime2 = rd2.getExpireTime();
// 如果缓存已被更新,直接返回,不需要重建
if (expireTime2.isAfter(LocalDateTime.now())) {
return r;
}
// 成功,独立线程缓存重建
CACHE_REBUILD_EXECUTOR.submit(() -> {
try {
dbFallback.apply(id);
this.setWithLogical(lockKey,id,time,unit);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
// 释放锁
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);
}
}