Redis解决缓存击穿问题的两种方法

2025-03-21 13:50

本文主要是介绍Redis解决缓存击穿问题的两种方法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《Redis解决缓存击穿问题的两种方法》缓存击穿问题也叫热点Key问题,就是⼀个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击,本文给大家介绍了Re...

引言

缓存击穿:给某一个key设置了过期时间,当key过期的时候,恰好这个时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮

解决办法

互斥锁(强一致,性能差)

Redis解决缓存击穿问题的两种方法

根据图片就可以看出,我们的思路就是只能让一个线程能够进行访问Redis,要想实现这个功能,我们也可以使用Redis自带的setnx

封装两个方法,一个写key来尝试获取锁另一个删key来释放锁

/**
 * 尝试获取锁
 *
 * @param key
 * @return
 */
private boolean tryLock(String key) {
    Boolean flag = stringRedisTemplate.opsForValue().sewww.chinasem.cntIfAbsent(key, "1", 10, TimeUnit.SECONDS);
    return BooleanUtil.isTrue(flag);
}
 
/**
 * 释放锁
 *
 * @param key
 */
private void unlock(String key) {
    stringRedisTemplate.delete(key);
}

在并行情况下每当其他线程想要获取锁,来访问缓存都要通过将自己的key写到tryLock()方法里,setIfAbsent()返回false则说明有线程在在更新缓存数据,锁未释放。若返回true则说明当前线程拿到锁了可以访问缓存甚至操作缓存。

我们在下面一个热门的查询场景中用代码用代码来实现互斥锁解决缓存击穿,代码如下:

    /**
     * 解决缓存击穿的互斥锁
     * @param id
     * @return
     */
    public Shop queryWithMutex(Long id) {
        String key = CACHE_SHOP_KEY + id;
        //1.从Redis查询缓存
        String shopjson = stringRedisTemplate.opsForValue().get(key);  //JSON格式
        //2.判断是否存在
        if (StrUtil.isNotBlank(shopJson)) { //不为空就返回 此工具类API会判断" "为false
            //存在则直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            //return Result.ok(shop);
            return shop;
        }
        //3.判断是否为空值 这里过滤 " "的情况,不用担心会一直触发这个条件因为他有TTL
        if (shopJson != null) {
            //返回一个空值
  android          return null;
        }
        //4.缓存重建 Redis中值为null的情况
        //4.1获得互斥锁
        String lockKey = "lock:shop"+id;
        Shop shopById=null;
        try {
            boolean isLock = tryLock(lockKey);
            //4.2判断是否获取成功
            if (!isLock){
                //4.3失败,则休眠并重试
                Thread.sleep(50);
               return queryWithMutex(id);
            }
            //4.4成功,根据id查询数据库
            shopById = getById(id);
            //5.不存在则返回错误
            if (shopById == null) {
                //将空值写入Redis
                stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                //为什么这里要存一个" "这是因为如果后续DB中有数据补充的话还可以去重建缓存
                //return Result.fail("暂无该商铺信息");
                return null;
            }
            //6.存在,写入Redis
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shopById), CACHE_SHOP_TTL, TimeUnit.MINUTES);
 http://www.chinasem.cn       } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            //7.释放互斥锁
            unlock(lockKey);
        }
 
        return shopById;
    }

逻辑过期(高可用,性能优)

方案:用户查询某个热门产品信息,如果缓存未命中(即信息为空),则直接返回空,不去查询数据库。如果缓存信息命中,则判断是否逻辑过期,未过期返回缓存信息,过期则重建缓存,尝试获得互斥锁,获取失败则直接返回已过期缓存数据,获取成功则开启独立线程去重构缓存然后直接返回旧的缓存信息,重构完成之后就释放互斥锁。

Redis解决缓存击穿问题的两种方法

封装一个方法用来模拟更新逻辑过期时间与缓存的数据在测试类里运行起来达到数据与热的效果 

/**
 * 添加逻辑过期时间
 *
 * @param id
 * @param expireTime
 */
public void saveShopRedis(Long id, Long expireTime) {
    //查询店铺信息
    Shop shop = getById(id);
    //封装逻辑过期时间
    RedisData redisData = new RedisData();
    redisData.setData(shop);
    redisData.sethttp://www.chinasem.cnExpireTime(LocalDateTime.now().plusSeconds(expireTime));
    //将封装过期时间和商铺数据的对象写入Redis
    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
}

查询接口:

/**
 * 逻辑过期解决缓存击穿
 *
 * @param id
 * @return
 */
public Shop queryWithLogicalExpire(Long id) throws InterruptedException {
    String key = CACHE_SHOP_KEY + id;
    Thread.sleep(200);
    //1.从Redis查询缓存
    String shopJson = stringRedisTemplate.opsForValue().get(key);  //JSON格式
    //2.判断是否存在
    if (StrUtil.isBlank(shopJson)) {
        //不存在则直接返回
        return null;
    }
    //3.判断是否为空值
    if (shopJson != null) {
        //返回一个空值
        //return Result.fail("店铺不存在!");
        return null;
    }
    //4.命中
    //4.1将JSON反序列化为对象
    RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
    Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
    LocalDateTime expireTime = redisData.getExpireTime();
    //4.2判断是否过期
    if (expireTime.isAfter(LocalDateTime.now())) {
        //5.未过期则返回店铺信息
        return shop;
    }
    //6.过期则缓存重建
    //6.1获取互斥锁
    String LockKey = LOCK_SHOP_KEY + id;
    boolean isLock = tryLock(LockKey);
    //6.2判断是否成功获得锁
    if (isLock) {
        //6.3成功,开启独立线程,实现缓存重建
        CACHE_REBUILD_EXECUTOR.submit(() -> {
            try {
                //重建缓存
                this.saveShop2Redis(id, 20L);
 
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                //释放锁
                unlock(LockKey);
            }
        });
    }
    //6.4返回商铺信息
    return shop;
}

设计逻辑过期时间

可以用这个方法设置逻辑过期时间

import org.redisson.Redisson;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.copythonnfig.Config;
 
public class RedissonExample {
    public static void main(String[] args) {
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
 
        RedissonClient redisson = Redisson.create(config);
 
        String key = "exampleKey";
        String value = "exampleValue";
        int timeout = 10; // 过期时间(秒)
 
        // 获取RBucket对象
        RBucket<String> bucket = redisson.getBucket(key);
 
        // 设置值并指定过期时间
        bucket.set(value, timeout, TimeUnit.SECONDS);
 
        System.out.println("设置成功");
        redisson.shutdown();
    }
}

大家可以看到,逻辑过期锁就是可以实现并发,所以他的效率更快,性能更好

但是

  • 牺牲了数据的实时性,以保证高并发场景下的服务可用性和数据库的稳定性。

  • 在实际应用中,需要确保获取互斥锁的操作是原子的,并且锁具有合适的超时时间,以避免死锁的发生。

  • 逻辑过期策略适用于那些对数据实时性要求不高,但要求服务高可用性的场景。

到此这篇关于Redis解决缓存击穿问题的两种方法的文章就介绍到这了,更多相关Redis解决缓存击穿内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于Redis解决缓存击穿问题的两种方法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1153872

相关文章

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

解决pandas无法读取csv文件数据的问题

《解决pandas无法读取csv文件数据的问题》本文讲述作者用Pandas读取CSV文件时因参数设置不当导致数据错位,通过调整delimiter和on_bad_lines参数最终解决问题,并强调正确参... 目录一、前言二、问题复现1. 问题2. 通过 on_bad_lines=‘warn’ 跳过异常数据3

解决RocketMQ的幂等性问题

《解决RocketMQ的幂等性问题》重复消费因调用链路长、消息发送超时或消费者故障导致,通过生产者消息查询、Redis缓存及消费者唯一主键可以确保幂等性,避免重复处理,本文主要介绍了解决RocketM... 目录造成重复消费的原因解决方法生产者端消费者端代码实现造成重复消费的原因当系统的调用链路比较长的时

深度解析Nginx日志分析与499状态码问题解决

《深度解析Nginx日志分析与499状态码问题解决》在Web服务器运维和性能优化过程中,Nginx日志是排查问题的重要依据,本文将围绕Nginx日志分析、499状态码的成因、排查方法及解决方案展开讨论... 目录前言1. Nginx日志基础1.1 Nginx日志存放位置1.2 Nginx日志格式2. 499

SpringBoot监控API请求耗时的6中解决解决方案

《SpringBoot监控API请求耗时的6中解决解决方案》本文介绍SpringBoot中记录API请求耗时的6种方案,包括手动埋点、AOP切面、拦截器、Filter、事件监听、Micrometer+... 目录1. 简介2.实战案例2.1 手动记录2.2 自定义AOP记录2.3 拦截器技术2.4 使用Fi

kkFileView启动报错:报错2003端口占用的问题及解决

《kkFileView启动报错:报错2003端口占用的问题及解决》kkFileView启动报错因office组件2003端口未关闭,解决:查杀占用端口的进程,终止Java进程,使用shutdown.s... 目录原因解决总结kkFileViewjavascript启动报错启动office组件失败,请检查of

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

SQL Server安装时候没有中文选项的解决方法

《SQLServer安装时候没有中文选项的解决方法》用户安装SQLServer时界面全英文,无中文选项,通过修改安装设置中的国家或地区为中文中国,重启安装程序后界面恢复中文,解决了问题,对SQLSe... 你是不是在安装SQL Server时候发现安装界面和别人不同,并且无论如何都没有中文选项?这个问题也

Java Thread中join方法使用举例详解

《JavaThread中join方法使用举例详解》JavaThread中join()方法主要是让调用改方法的thread完成run方法里面的东西后,在执行join()方法后面的代码,这篇文章主要介绍... 目录前言1.join()方法的定义和作用2.join()方法的三个重载版本3.join()方法的工作原

Redis MCP 安装与配置指南

《RedisMCP安装与配置指南》本文将详细介绍如何安装和配置RedisMCP,包括快速启动、源码安装、Docker安装、以及相关的配置参数和环境变量设置,感兴趣的朋友一起看看吧... 目录一、Redis MCP 简介二、安www.chinasem.cn装 Redis MCP 服务2.1 快速启动(推荐)2.