Redis 查询接口加缓存 缓存雪崩 缓存穿透 缓存击穿 精彩!精彩!

Redis 查询接口加缓存 缓存雪崩 缓存穿透 缓存击穿 精彩!精彩!

项目地址 Gitee

https://gitee.com/yangjunbo-jetli/hmdp-redis.git

一、Redis 的作用

1.1、基于 session 保存用户登录信息,集群环境下没有共享





1.2、基于 Redis 实现集群共享登录信息














1.3、查询接口加 Redis 缓存提高性能




1.4、解决缓存穿透问题


1.5、解决缓存雪崩问题

1.6、利用互斥锁解决缓存击穿问题








1.7、利用逻辑过期解决缓存击穿问题






1.8、封装一个 Redis 操作工具类

java 复制代码
package com.hmdp.utils;

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.RedisData;
import com.hmdp.entity.Shop;
import lombok.extern.slf4j.Slf4j;
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;

import static com.hmdp.utils.RedisConstants.*;

@Slf4j
@Component
public class CacheClient {

    //使用构造方法注入 StringRedisTemplate
    private final StringRedisTemplate stringRedisTemplate;

    /**
     * 构造方法注入 StringRedisTemplate
     * @param stringRedisTemplate
     */
    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    //封装一个给 Redis 中保存数据并设置过期时间方法
    /**
     * 给 Redis 中保存数据并设置过期时间方法
     * @param key
     * @param value
     * @param time
     * @param timeUnit
     */
    public void set(String key, Object value, Long time, TimeUnit timeUnit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,timeUnit);
    }

    //封装一个给 Redis 中保存数据并设置逻辑过期时间方法
    /**
     * 给 Redis 中保存数据并设置逻辑过期时间方法
     * @param key
     * @param value
     * @param time
     * @param timeUnit
     */
    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit timeUnit){
        //设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));
        //存入 Redis
        stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData));
    }

    //封装一个处理过缓存穿透问题的查询方法
    /**
     * 处理过缓存穿透问题的查询方法
     * @param keyPrefix
     * @param id
     * @param type
     * @param dbFallback
     * @param time
     * @param timeUnit
     * @return
     * @param <R>
     * @param <ID>
     */
    public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit timeUnit){
        //1、从 Redis 查询商铺缓存
        String key = keyPrefix + id;
        String json = stringRedisTemplate.opsForValue().get(key);
        //2、判断是否存在
        if(StrUtil.isNotBlank(json)){
            //3、存在直接返回
            return JSONUtil.toBean(json,type);
        }
        //4、判断是否是 "" 空值
        //走到这里 json 是 null 或 "",如果不是 null 那就是 ""。
        if(json != null){
            return null;
        }
        //5、不存在,根据 id 查询数据库。
        R r = dbFallback.apply(id);
        //6、数据库不存在把空值 "" 写入 Redis 并返回错误
        if(r == null){
            stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL,TimeUnit.MINUTES);
            return null;
        }
        //7、数据库存在,写入 Redis。
        this.set(key,r,time,timeUnit);
        return r;
    }

    //封装一个处理过缓存击穿的方法
    private  static  final ExecutorService CACHE_REBUILD_EXECUTOR =  Executors.newFixedThreadPool(5);

    /**
     * 处理过缓存击穿的方法
     * @param keyPrefix
     * @param id
     * @param type
     * @param dbFallback
     * @param time
     * @param timeUnit
     * @return
     * @param <R>
     * @param <ID>
     */
    public <R,ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID,R> dbFallback,Long time,TimeUnit timeUnit){
        //1、从 Redis 查询商铺信息缓存
        String key = CACHE_SHOP_KEY + id;
        String shopJson = stringRedisTemplate.opsForValue().get(key);
        //2、判断是否存在
        if(StrUtil.isBlank(shopJson)){
            //3、不存在,直接返回
            return null;
        }
        //4、命中,需要先把 JSON 序列化为对象。
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        R r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
        //5、判断是否过期
        LocalDateTime expireTime = redisData.getExpireTime();
        if(expireTime.isAfter(LocalDateTime.now())){
            //5.1、未过期,直接返回商铺信息。
            return r;
        }
        //5.2、已过期,需要重建缓存。
        //6、缓存重建
        //6.1、获取互斥锁
        String lockKey = LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        //6.2、判断是否获取互斥锁成功
        if(isLock){

            //6.3、抢到锁以后,可能别的线程已经重建好缓存了,再确认下缓存是否过期。
            shopJson = stringRedisTemplate.opsForValue().get(key);
            redisData = JSONUtil.toBean(shopJson, RedisData.class);
            r = JSONUtil.toBean((JSONObject) redisData.getData(), type);
            expireTime = redisData.getExpireTime();
            if(expireTime.isAfter(LocalDateTime.now())){
                return r;
            }

            //6.4、成功,开启独立线程,实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                //重建缓存
                try {
                    //查询数据库
                    R r1 = dbFallback.apply(id);
                    //写入 Redis
                    this.setWithLogicalExpire(key,r1,time,timeUnit);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    //释放锁
                    unLock(lockKey);
                }
            });
        }
        //6.5、返回过期的商铺信息
        return r;
    }

    //尝试获取 Redis 互斥锁方法
    //哪个线程在 Redis 保存数据成功等于哪个线程拿到互斥锁
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return flag;
    }

    //释放锁,删除 Redis 中的数据,别的线程又可以保存成功数据,抢到互斥锁了。
    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }

}
java 复制代码
package com.hmdp.entity;

import lombok.Data;

import java.time.LocalDateTime;

/**
 * 用来给 Redis 传输数据的实体类,封装了逻辑过期时间。
 */
@Data
public class RedisData {
    //过期时间
    private LocalDateTime expireTime;
    //数据
    private Object data;
}
相关推荐
身如柳絮随风扬1 小时前
门户服务缓存架构优化:从分级缓存到双缓存,彻底解决毛刺现象与一致性问题
spring·缓存·架构
Mr. zhihao1 小时前
[特殊字符] 从 Redis 缓存穿透到布隆过滤器,再到布谷鸟过滤器:一次穿透防护的进化之旅
数据库·redis·缓存
@小匠1 小时前
Redis 7 持久化机制
数据库·redis·缓存
phltxy2 小时前
Redis 核心数据类型之 String 详解
数据库·redis·bootstrap
码哥字节3 小时前
开多个 Agent 后 Claude Code 账单翻了 4 倍,一个配置解决了
redis·性能
未若君雅裁4 小时前
Redis Key 过期后会立刻删除吗?过期删除与内存淘汰策略详解
java·redis
189228048614 小时前
H27QBG8GDAIR-BCB闪存H27QCG8HEAIR-BCB
大数据·科技·缓存
手握风云-4 小时前
Redis:不只是缓存那么简单(七)
redis·缓存
Irissgwe4 小时前
redis之集群(Cluster)
数据库·redis·缓存·集群·redis集群·数据分片算法