简单的Redis工具类
- Redis
- 时间:2022-11-26 21:20
- 3180人已阅读
简介
基于StringRedisTemplate封装一个缓存工具类,满足下列需求:方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间方法2:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓存击穿问题方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题方法4
🔔🔔🔔好消息!好消息!🔔🔔🔔
有需要的朋友👉:联系凯哥
基于StringRedisTemplate封装一个缓存工具类,满足下列需求:
方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间
方法2:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓
存击穿问题
方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题
方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题
将逻辑进行封装
package com.hmdp.utils.bean; 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.dto.Result; import com.hmdp.entity.Shop; import com.hmdp.utils.RedisData; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Function; import static com.hmdp.constants.RedisConstants.*; /** * @author 凯哥Java * @description 基于StringRedisTemplate封装一个缓存工具类,满足下列需求: * <p> * * 方法1:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置TTL过期时间 * * 方法2:将任意Java对象序列化为json并存储在string类型的key中,并且可以设置逻辑过期时间,用于处理缓 * <p> * 存击穿问题 * <p> * * 方法3:根据指定的key查询缓存,并反序列化为指定类型,利用缓存空值的方式解决缓存穿透问题 * * 方法4:根据指定的key查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题 * @company * @since 2022/11/26 21:31 */ @Component @Slf4j public class CacheClient { private final StringRedisTemplate stringRedisTemplate; private static final ExecutorService CACHE_EXECUTOR = Executors.newFixedThreadPool(10); public CacheClient(StringRedisTemplate stringRedisTemplate) { this.stringRedisTemplate = stringRedisTemplate; } /** * 将任意Java对象存放到缓存中-带有过期时间的 * * @param key key * @param value v * @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对象存放到缓存中-带有逻辑过期时间的 * * @param key key * @param value value * @param time 逻辑过期时间-xx后过期.比如30min后国庆。time = 30 unit=xx * @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))); stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData)); } /** * 封装处理缓存穿透问题的 * * @param keyPrefix 缓存的key的前缀 * @param id 对象ID * @param type 对象类型 * @param dbFallback 如果缓存不存在,就去数据库查询的函数式 * @param time 过期时间 * @param unit 单位 * @param <R> 返回对象 * @param <ID> id类型 * @return */ 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 (null != json) { //返回错误信息 return null; } //不存在,去数据库查询 R r = dbFallback.apply(id); //5:数据库不存在,提示错误 if (Objects.isNull(r)) { //防止缓存穿透,设置空对象 stringRedisTemplate.opsForValue().set(key, "", SHOP_CACHE_KEY_EMPTY_TTL, TimeUnit.MINUTES); // 返回错误信息 return null; } //写入缓存中 this.set(key, r, time, unit); //返回 return r; } public <R, ID> R queryWithLogicalExpire(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); //2:判断是否存在-不存在直接返回null if (StrUtil.isBlank(json)) { return null; } //从缓存中查询到,后在判断是否过期 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 = SHOP_LOCK_CACHE_KEY + id; boolean isLock = tryLock(lockKey); if (isLock) { //启动线程重新构建 CACHE_EXECUTOR.submit(() -> { try { R dbR = dbFallback.apply(id); //重新写入到Redis中 this.setWithLogicalExpire(keyPrefix, id, time, unit); } finally { //释放锁 unLock(lockKey); } }); } return r; } /** * 释放互斥锁 * * @param key */ private void unLock(String key) { stringRedisTemplate.delete(key); } /** * 获取互斥锁 * * @param key * @return */ private boolean tryLock(String key) { Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "v1", 10, TimeUnit.SECONDS); return BooleanUtil.isTrue(flag); } }
RedisData对象:
import lombok.Data; import java.time.LocalDateTime; @Data public class RedisData { private LocalDateTime expireTime; private Object data; }
使用场景:
1:缓存穿透方法的调用:
Shop shop = cacheClient.queryWithPassThrough(SHOP_CACHE_KEY,id,Shop.class,this::getById,SHOP_CACHE_KEY_EMPTY_TTL, TimeUnit.MINUTES);
2:缓存击穿的方法调用
从这个工具类中,我们学到了以下几个封装常用的:
1:带有返回值和入参的泛型
2:函数式编程可以作为参数传递