#maven依赖

cn.hutool

hutool-all

5.8.12

import cn.hutool.core.date.DateTime;

import cn.hutool.core.util.BooleanUtil;

import cn.hutool.core.util.StrUtil;

import cn.hutool.json.JSONObject;

import cn.hutool.json.JSONUtil;

import com.hmdp.entity.Shop;

import com.sun.org.apache.bcel.internal.generic.NEW;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;

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;

@Slf4j

@Component

/**

* 用于解决缓存击穿和缓存穿透的工具类

*/

public class CacheClient {

@Autowired

private StringRedisTemplate stringRedisTemplate;

/**

* 添加数据到redis

*

* @param key 名称

* @param 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);

}

/**

* 设置逻辑过期添加数据到redis

*

* @param key 名称

* @param 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)));

//写入redis

stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));

}

/**

* 缓存穿透

* @param keyPrefix key的前缀

* @param id id

* @param type 返回类型

* @param dbFallback 数据库查询

* @param time 时间

* @param unit 时间单位

* @param 返回值

* @param ID

* @return

*/

public R queryWithPassThrough(String keyPrefix, ID id, Class type, Function dbFallback, Long time, TimeUnit unit) {

String key = keyPrefix + id;

//1.从redis查询商品缓存

String json = stringRedisTemplate.opsForValue().get(key);

//2.判断是否存在

if (StrUtil.isNotBlank(json)) {

//3。存在直接返回

R content = JSONUtil.toBean(json, type);

return content;

}

//判断是否是空值(上面已经判断了数据存在的情况,所以这里直接使用!=null就能查出空值)

if (json != null) {

return null;

}

//4.不存在,查询数据库

R r = dbFallback.apply(id);

//5.数据库不存在返回错误

if (r == null) {

//为了防止缓存穿透,将空值写入redis

stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);

//返回

return null;

}

//6。存在写入redis

stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(r), time, unit);

//7.返回

return r;

}

/**

* 缓存击穿

*/

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

public R queryWithLogicExpiredTime(String keyPrefix, ID id, Class type, Function dbFallback, Long time, TimeUnit unit) {

String key = keyPrefix + id;

//1.从redis查询商品缓存

String json = stringRedisTemplate.opsForValue().get(key);

//2.判断是否命中(未命中返回)

if (StrUtil.isBlank(json)) {

return null;

}

//2.1命中-判断是否过期

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 = RedisConstants.LOCK_SHOP_KEY + id;

try {

//2.2过期-获取互斥锁

boolean isLock = tryLock(lockKey);

//3.判断锁是否拿到

if (isLock) {

//4.是-开启独立线程

CACHE_THREAD_POOL.submit(() -> {

//重建缓存2步走,先查数据库,再写redis

//查询数据库

R r1 = dbFallback.apply(id);

//写入redis

this.setWithLogicalExpire(key,r1,time,unit);

});

}

} catch (Exception e) {

throw new RuntimeException(e);

} finally {

//4.2释放锁

delLock(lockKey);

}

//5.返回数据

return r;

}

//利用redis中的setnx来设置锁

private boolean tryLock(String key) {

Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10L, TimeUnit.SECONDS);

//自动拆箱有可能返回空值,所以用工具类

return BooleanUtil.isTrue(flag);

}

//删除锁

private void delLock(String key) {

stringRedisTemplate.delete(key);

}

}

好文推荐

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。