redis工具类(缓存的设置,缓存击穿,穿透)

bash 复制代码
#maven依赖
		<!--hutool-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.12</version>
        </dependency>
java 复制代码
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 <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;
        //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,ID> R queryWithLogicExpiredTime(String keyPrefix, ID id, Class<R> type, Function<ID, R> 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);
    }
}
相关推荐
2301_322414280420 小时前
基于SpringBoot+Vue的休闲零食超市管理系统的设计与实现59004-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·vue.js·spring boot·redis·mysql·决策树·推荐算法
catoop1 天前
API Key 设计与安全生成方案:结构拆解、数据库设计与 Java 实现
java·数据库·安全
Web极客码1 天前
我用 Claude 分析 Redis 哨兵日志,实现跨机房数据不一致自动对齐
数据库·redis·php
库库的数据库笔记1 天前
国产信创数据库的MySQL兼容性深度对比解析
大数据·数据库·mysql·oceanbase·政务
microrain1 天前
SagooIoT 的缓存换后端只改一行配置,文件后端连 Redis 都不需要
物联网·缓存·golang·sagooiot
姚远Oracle ACE1 天前
为什么无法连接到 PostgreSQL?
数据库·postgresql
ss2731 天前
AI时代首先数据库-PostgreSQL实战笔记 | 22 PostgreSQL实例与集群
数据库·笔记·postgresql
ss2731 天前
AI时代首先数据库-PostgreSQL实战笔记 | 21 PostgreSQL进程与内存架构介绍
数据库·笔记·postgresql
Henry-SAP1 天前
SAP PP模块全景概览
数据库