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;
}
相关推荐
用户3169353811834 天前
Java连接Redis
redis
小小工匠6 天前
Redis - 事务机制:能实现 ACID 属性吗
数据结构·redis·性能优化·并发·持久化
ofoxcoding6 天前
在AI API聚合平台配置DeepSeek V3.2提示词缓存实战:快速接入与成本优化指南
人工智能·spring·缓存·ai
NeilYuen6 天前
gRPC结合FAISS构建AI助手语义缓存模块(一):设计
人工智能·缓存·faiss
taocarts_bidfans6 天前
反向海淘跨境缓存架构优化:taocarts Redis分层缓存实战技术
redis·缓存·架构·反向海淘·taocarts
退休倒计时6 天前
【每日一题】LeetCode 146. LRU 缓存 TypeScript
算法·leetcode·缓存·typescript
炘爚6 天前
Linux——Redis
数据库·redis·缓存
csjane10796 天前
Redisson 限流原理
java·redis
ThanksGive6 天前
Go 服务里的 Redis 锁惊群问题:一次本地合流优化实践
redis