【Redis】封装Redis缓存工具解决缓存穿透与缓存击穿问题

基于StringRedisTemplate封装一个缓存工具,主要有一下几个方法

方法1:将任意Java对象序列化为json并存储在String的指定key中且设置TTL

方法2:将任意Java对象序列化为json并存储在String的指定key中,并可以设置逻辑过期时间,用户处理缓存击穿问题

方法3:根据指定的key进行查询缓存,并反序列化为指定类型,利用缓存空值的办法解决缓存穿透问题

方法4:根据指定的key进行查询缓存,并反序列化为指定类型,需要利用逻辑过期解决缓存击穿问题

此处使用到的互斥锁加锁解锁方法,在前面的文章有提及【Redis】Java通过redis实现简单的互斥锁_1373i的博客-CSDN博客https://blog.csdn.net/qq_61903414/article/details/130509874?spm=1001.2014.3001.5501



import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
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.TimeUnit;
import java.util.function.Function;

@Data
class RedisData {
    private LocalDateTime expireTime;
    private Object data;

    public RedisData(LocalDateTime expireTime, Object data) {
        this.expireTime = expireTime;
        this.data = data;
    }
}

/**
 * 封装解决redis 缓存击穿、缓存雪崩、缓存穿透等问题的方法
 */
@Component
@Slf4j
public class RedisUtil {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 存入时设置过期时间
     * @param key
     * @param value
     * @param time
     * @param unit
     */
    public void set(String key, Object value, Long time, TimeUnit unit) throws JsonProcessingException {
        stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(value),time,unit);
    }

    /**
     * 存入时设置逻辑过期
     * @param key
     * @param value
     * @param time
     * @param unit
     */
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) throws JsonProcessingException {
        RedisData data = new RedisData(LocalDateTime.now().plusSeconds(unit.toSeconds(time)),value);
        stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(data));
    }

    /**
     * 查询缓存,缓存穿透时缓存null解决
     * @param id  要查询的id
     * @param keyPrefix   redis key的前缀
     * @param type        要返回的类型
     * @param dbFallback  查询数据库的方法的结果
     * @param 
     * @param 
     * @return
     * @throws JsonProcessingException
     */
    public  R getWithPassThrough(I id, String keyPrefix, Class type, Function dbFallback,Long time,TimeUnit unit) throws JsonProcessingException {
        // 生成redis中的key
        String key = keyPrefix + id;

        // 查询redis
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            // 存在缓存,则返回
            return objectMapper.readValue(json,type);
        }

        // 不存在时,判断是否是空值
        if (json != null) {
            // 返回错误信息
            return null;
        }

        // 查询数据库
        R result = dbFallback.apply(id);
        // 判空
        if (result == null) {
            // 发送缓存穿透,缓存null
            stringRedisTemplate.opsForValue().set(key,"",time,unit);
            return null;
        }

        // 存在则重建缓存
        stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(result),time,unit);
        return result;
    }

    /**
     * 查询缓存,逻辑过期后异步重建缓存
     * @param id
     * @param keyPrefix
     * @param type
     * @param dbFallback
     * @param time
     * @param unit
     * @param 
     * @param 
     * @return
     * @throws JsonProcessingException
     */
    public  R get(I id, String keyPrefix,Class type,Function dbFallback,Long time,TimeUnit unit) throws JsonProcessingException {
        // 构建查询redis的key
        String key = keyPrefix + id;

        // 查询redis
        String json = stringRedisTemplate.opsForValue().get(key);
    

        // 反序列化转为RedisData对象
        RedisData data = objectMapper.readValue(json,RedisData.class);
        // 获取数据
        R r = JSONUtil.toBean((JSONObject) data.getData(),type);
        // 获取逻辑时间
        LocalDateTime expireTime = data.getExpireTime();

        // 判断是否过期
        if (expireTime.isAfter(LocalDateTime.now())) {
            // 没有过期
            return r;
        }

        // 到达此处已过期,重建缓存:加互斥锁--》查询数据库--》重建--》释放互斥锁
        // 1.获取互斥锁
        String lockKey = id + "_lock";
        boolean isLock = LockByRedis.tryLock(lockKey);

        if (isLock) {
            // 2.加锁成功,开启线程重建缓存
            new Thread(){
                @Override
                public void run() {

                    try {
                        // 3.查询数据库
                        R db = dbFallback.apply(id);
                        // 4.重建
                        stringRedisTemplate.opsForValue().set(key,objectMapper.writeValueAsString(db),time,unit);
                    } catch (JsonProcessingException e) {
                        e.printStackTrace();
                    } finally {
                        // 5.释放互斥锁
                        LockByRedis.unlock(lockKey);
                    }
                }
            }.start();
        }

        // 返回逻辑过期的数据
        return r;
    }
}

难点:在重建缓存时,我们需要去查询数据库,而查询数据库不同的reids缓存重建所需要查询的数据库表可能不同,其方法也可能不同。

解决:通过Function<参数类型,返回值>来把该查询数据库的方法交给方法的调用者进行传入

你可能感兴趣的:(Java,Redis,redis,缓存,java)