redis之缓存击穿

news2025/4/15 12:31:05

一、前言

        本期我们聊一下缓存击穿,其实缓存击穿和缓存穿透很相似,区别就是,缓存穿透是一些黑客故意请求压根不存在的数据从而达到拖垮系统的目的,是恶意的,有针对性的。缓存击穿的情况是,数据确实存在,只不过因为某些原因导致缓存中数据消失了,导致请求到达了数据库,从而导致数据库的崩,下面我们看看这个问题是怎么个情况。

二、问题描述

        我们之前也介绍过,缓存和数据库双写一致的事情,一般是如果缓存中不存在的话,就从数据库中再查询一遍,然后将查询到的数据维护到缓存中,等下次请求的时候,就能直接从缓存中获取到了,正常是这么个套路,没毛病。但是我们之前也介绍过,有一些热点数据,在启动的时候就需要加载到缓存中,热点数据的特点就是访问频繁,为了减少数据库压力才放到缓存中,那么如果是热点数据在缓存中失效,那么数据库访问压力就会剧增,情况再糟糕一点,热点数据访问量巨大,那么数据库被拖垮那也是板上钉钉的事情。

        那么热点数据为什么会失效?

        其中一个原因是缓存过期了,热点数据被设置了过期时间,一旦过期,大量请求达到数据库,压垮数据库。

        还有别的原因,缓存搭建了主从结构,主从的数据同步存在延时,导致客户端请求从节点时拿不到数据,或者是主节点使用了rdb备份方式,恰好宕机丢失了一部分数据中包含了热点数据等等,缓存失效的可能原因我们先暂时讨论这么多,这不是我们本次讨论的内容。

三、解决方案

3.1、热点数据设置永不过期

        对于热点数据,如果担心因为缓存过期引起击穿,我们可以尝试不设置过期时间,这样就不用担心因缓存过期导致缓存击穿了。

        不设置过期时间是个好办法,不过我想既然给设置了过期时间,那么说明确实需要设置过期时间,那么在这种情况下,不设置过期时间就不适用了,我们采取另一种方案。

3.2、互斥锁方案

       我们再唠叨一下缓存击穿的特点,大量请求达到数据库导致数据库崩溃。那我们简单分析一下,核心问题是大量请求到达数据库,而且这些请求的都是同一个key的数据。

        基于上述两点可以想想办法:

        首先我们可以基于同一个key进行加锁,保证同一时间只会有一个线程访问数据库。如果某个线程先获取到了锁,先进行一次缓存读取,如果确实没有数据,再去访问数据库,将数据库中的数据加载到缓存中并返回,之后将锁释放。依次往复。为什么要这么做,数据库访问过一次之后,缓存中就会维护好数据,其他的请求当然也没有再访问数据库并维护缓存的必要了。

    @Test
    public void cachePenetrate() throws Exception{
        int countNumber = 5;
        CountDownLatch countDownLatch = new CountDownLatch(countNumber);
        //查询数据
        for (int i = 0; i < countNumber; i++) {
            new Thread(()->{
                Long id = 3L;
                String cacheKey = "mutualExclusion:"+id;
                String lockKey = "mutualExclusionLock:"+id;
                //线程从缓存中获取数据
                Object valObj = redisTemplate.opsForValue().get(cacheKey);
                String code = "";
                if(Objects.isNull(valObj)){
                    code = mutualExclusion(cacheKey,lockKey, id);
                }else {
                    code = valObj.toString();
                }
                System.out.println(Thread.currentThread().getName()+"已获取到值:"+code);
                countDownLatch.countDown();
            },"线程"+(i+1)).start();
        }
        countDownLatch.await();
    }

    /**
     * 功能描述: 互斥锁解决缓存穿透
     * @Author:huhy
     * @Date: 2025/4/10 22:38
     */
    private String mutualExclusion(String cacheKey,String lockKey,Long id){
        //开启分布式锁
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock();
            //在获取锁之后,从缓存中获取一次数据,看看是否已有其他线程写入
            Object valObj = redisTemplate.opsForValue().get(cacheKey);
            //如果缓存中有数据,则直接返回
            if(Objects.nonNull(valObj)){
                System.out.println("缓存中已有数据,线程【"+Thread.currentThread().getName()+"】未进行数据库查询");
                return valObj.toString();
            }
            System.out.println("缓存中没有数据,线程【"+Thread.currentThread().getName()+"】正在进行数据库查询并设置缓存");
            TSCodeRule tsCodeRule = codeRuleService.selectTSCodeRuleById(id);
            //设置过期时间为5秒
            redisTemplate.opsForValue().set(cacheKey,tsCodeRule.getCodePrefix(),5,TimeUnit.SECONDS);
        }finally {
            lock.unlock();
        }
        return null;
    }

       

        测试代码中,设置了5个线程,只有第一个线程进行了数据库查询,后边的线程都读取到了线程1设置好的缓存数据,减少了数据库的访问压力,算是解决了缓存击穿的问题。

        T哥:什么叫算是解决了,那到底解决没啊。

        小永哥:被你发现了,解决是解决了,但是我们设置缓存的初衷是什么?不就是看中它快嘛,热点数据本来访问量就大,放到缓存本来就是为了快速响应的,现在好了,加上了锁,都串行化了,那势必会牺牲掉性能。

        T哥:别卖关子了,我知道你还有招,赶紧的吧........

        小永哥:好的。

3.3、逻辑过期方案

        逻辑过期的意思是:不再设置过期时间,将过期时间与值一起保存到redis中,当应用程序读取到缓存中值的时候,将值中包含的过期时间读取出来进行校验,如果已过期,那么开启新的线程异步去维护缓存数据,而本次还是返回旧数据。这么做的好处是应用程序依旧能保持很高的响应,缺点就是,如果数据库中数据被修改过的话,无法及时返回最新的数据。

        

       

    @Test
    public void logicExpireTest() throws Exception{
        int countNumber = 50;
        CountDownLatch countDownLatch = new CountDownLatch(countNumber);
        //查询数据
        for (int i = 0; i < countNumber; i++) {
            new Thread(()->{
                random = new Random();
                try {
                    TimeUnit.SECONDS.sleep(random.nextInt(3) + 1);
                }catch (Exception e){}
                System.out.println(Thread.currentThread().getName()+"已获取到值:"+getCacheValueByKey(4L));
                countDownLatch.countDown();
            },"线程"+(i+1)).start();
        }
        countDownLatch.await();
    }

    String getCacheValueByKey(Long id){
        //从缓存中获取值
        String cacheKey = "logicExpireTest:"+id;
        String jsonStr = redisTemplate.opsForValue().get(cacheKey).toString();
        Map<String,String> cacheCodeMap = JSON.parseObject(jsonStr,Map.class);
        //校验是否已超时
        long startTime = Long.parseLong(cacheCodeMap.get("startTime"));
        long expire = Long.parseLong(cacheCodeMap.get("expire"));
        boolean isExpire = (System.currentTimeMillis() - startTime) > expire;
        //如果超时,另起线程去维护redis
        if(isExpire){
            new Thread(()->{
                TSCodeRule tsCodeRule = codeRuleService.selectTSCodeRuleById(id);
                Map<String,String> codeInfoMap = new HashMap<>();
                codeInfoMap.put("code",tsCodeRule.getCodePrefix());
                codeInfoMap.put("expire",String.valueOf(3000));
                codeInfoMap.put("startTime",String.valueOf(System.currentTimeMillis()));
                redisTemplate.opsForValue().set(cacheKey,JSON.toJSONString(codeInfoMap));
            }).start();
        }
        //始终返回从缓存中读取到的数据
        return cacheCodeMap.get("code");
    }

        经过运行测试代码,我们可以看到,有的线程是新值,有的是旧值。

四、结语

        缓存穿透我们讨论了三种解决方式,这两种方式并没有谁比谁更优秀,没有哪种解决方案是完美的,只有适合应用场景的解决方案,在日常应用时需要我们灵活选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2334167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

txt、Csv、Excel、JSON、SQL文件读取(Python)

txt、Csv、Excel、JSON、SQL文件读取&#xff08;Python&#xff09; txt文件读写 创建一个txt文件 fopen(rtext.txt,r,encodingutf-8) sf.read() f.close() print(s)open( )是打开文件的方法 text.txt’文件名 在同一个文件夹下所以可以省略路径 如果不在同一个文件夹下 ‘…

【NLP解析】多头注意力+掩码机制+位置编码:Transformer三大核心技术详解

目录 多头注意力&#xff1a;让模型化身“多面手” 技术细节&#xff1a;多头注意力如何计算&#xff1f; 实际应用&#xff1a;多头注意力在Transformer中的威力 为什么说多头是“非线性组合”&#xff1f; 实验对比&#xff1a;多头 vs 单头 进阶思考&#xff1a;如何设计更高…

Downlink Sensing in 5G-Advanced and 6G: SIB1-assisted SSB Approach

摘要——本文研究了利用现有5G NR信号进行网络侧集成感知与通信&#xff08;ISAC&#xff09;的潜力。通常&#xff0c;由于其频繁的周期性可用性和波束扫描特性&#xff0c;同步信号块&#xff08;SSB&#xff09;是适合用于下行感知的候选信号。然而&#xff0c;正如本文所示…

设计模式 Day 8:策略模式(Strategy Pattern)完整讲解与实战应用

&#x1f504; 前情回顾&#xff1a;Day 7 重点回顾 在 Day 7 中&#xff0c;我们彻底讲透了观察者模式&#xff1a; 它是典型的行为型模式&#xff0c;核心理念是“一变多知”&#xff0c;当一个对象状态变化时&#xff0c;自动通知所有订阅者。 我们通过 RxCpp 实现了工业…

ONVIF/RTSP/RTMP协议EasyCVR视频汇聚平台RTMP协议配置全攻略 | 直播推流实战教程

在现代化的视频管理和应急指挥系统中&#xff0c;RTMP协议作为一种高效的视频流传输方式&#xff0c;正变得越来越重要。无论是安防监控、应急指挥&#xff0c;还是物联网视频融合&#xff0c;掌握RTMP协议的接入和配置方法&#xff0c;都是提升系统性能和效率的关键一步。 今天…

《微服务与事件驱动架构》读书分享

《微服务与事件驱动架构》读书分享 Building Event-Driver Microservices 英文原版由 OReilly Media, Inc. 出版&#xff0c;2020 作者&#xff1a;[加] 亚当 • 贝勒马尔 译者&#xff1a;温正东 作者简介&#xff1a; 这本书由亚当贝勒马尔&#xff08;Adam Bellemare…

每日一题(小白)暴力娱乐篇26

我们先直接尝试暴力循环四轮看能不能得到答案&#xff0c;条件&#xff1a;四个数的平方相加等于这个数 ①接收答案result ②循环四轮i&#xff0c;j&#xff0c;k&#xff0c;l ③如果i*ij*jk*kl*lresult ④按照要求的格式输出这四个数字 代码如下&#x1f447; public s…

如何使用AI辅助开发R语言

R语言是一种用于统计计算和图形生成的编程语言和软件环境&#xff0c;很多学术研究和数据分析的科学家和统计学家更青睐于它。但对与没有编程基础的初学者而言&#xff0c;R语言也是有一定使用难度的。不过现在有了通义灵码辅助编写R语言代码&#xff0c;我们完全可以用自然语言…

Git版本管理系列:(三)远程仓库

目录 与远程仓库平台(github\gitee等)建立连接本地仓库关联远程仓库本地仓库内容推送远程仓库&#xff1a;PUSH将远程仓库的更新拉取到本地:PULL语法总结 与远程仓库平台(github\gitee等)建立连接 远程仓库平台相当于一个网盘&#xff0c;我们可以把自己的代码上传上去。就像网…

React Hooks: useRef,useCallback,useMemo用法详解

1. useRef&#xff08;保存引用值&#xff09; useRef 通常用于保存“不会参与 UI 渲染&#xff0c;但生命周期要长”的对象引用&#xff0c;比如获取 DOM、保存定时器 ID、WebSocket等。 新建useRef.js组件&#xff0c;写入代码&#xff1a; import React, { useRef, useSt…

[wifi SAE]wpa3-personal

SAE &#xff1a;Simultaneous Authentication of Equals&#xff08;同等同时认证&#xff09; wpa2和wpa3之间最大的区别是认证过程的区别 WPA2不安全性 1.sta和ap预置psk(AP密码) 2.四次握手生成ptk用于后续数据加密的密钥 ptk计算基于psk、双方随机数&#xff1b; 双方都产…

电路方案分析(二十)TPS63xxx系列DC/DC电源EMI PCB设计方案

tips&#xff1a;资料来自网络&#xff0c;仅供学习使用。[TOC](TPS63xxx系列DC/DC电源EMI PCB设计方案) 1.概述 通过TPS63xxx系列DC/DC电源模块来分析降低直流/直流降压/升压转换器辐射 EMI 的来源以及相关PCB设计。 下面都以最常用的TPS63070为例说明&#xff1a; 典型应用…

Java 大厂面试题 -- JVM 深度剖析:解锁大厂 Offe 的核心密钥

最近佳作推荐&#xff1a; Java大厂面试高频考点&#xff5c;分布式系统JVM优化实战全解析&#xff08;附真题&#xff09;&#xff08;New&#xff09; Java大厂面试题 – JVM 优化进阶之路&#xff1a;从原理到实战的深度剖析&#xff08;2&#xff09;&#xff08;New&#…

目标追踪Hyperspectral Adapter for Object Tracking based on Hyperspectral Video

论文作者&#xff1a;Long Gao,Yunhe Zhang,Langkun Chen,Yan Jiang,Weiying Xie,Yunsong Li 作者单位&#xff1a;Xidian University;the University of Sheffield 论文链接&#xff1a;http://arxiv.org/abs/2503.22199v1 内容简介&#xff1a; 1&#xff09;方向&#x…

深度剖析SSD多段L2P表查找加速技术

在固态硬盘(SSD)控制器中,逻辑块地址(LBA)需要通过映射表(L2P Table)映射到NAND闪存的物理地址(PA)。随着SSD容量的增长,L2P表的大小也随之增加,这给查找操作带来了性能挑战。 在SSD控制器中,LBA需借助L2P表映射为NAND物理地址。映射表最小规模为 (O(n * \lg (n)))…

【MQTT-协议原理】

MQTT-协议原理 ■ MQTT-协议原理■ MQTT-服务器 称为"消息代理"&#xff08;Broker&#xff09;■ MQTT协议中的订阅、主题、会话■ 一、订阅&#xff08;Subscription&#xff09;■ 二、会话&#xff08;Session&#xff09;■ 三、主题名&#xff08;Topic Name&a…

PCIe 5.0光学SSD原型问世!

近日&#xff0c;Kioxia Corporation&#xff08;铠侠&#xff09;、AIO Core Co., Ltd. 和 Kyocera Corporation&#xff08;京瓷&#xff09;联合宣布成功开发了一款支持 PCIe 5.0 接口的光学 SSD 原型。该技术旨在通过光接口替换传统的电接口&#xff0c;从而显著增加计算设…

Raymarching Textures In Depth

本节课最主要的就是学会hlsl中使用纹理采样 float4 color Texture2DSample(Texobj, TexobjSampler, uv); return color; 课程中的代码&#xff08;没有这张图我就没做&#xff09; 课程代码产生深度的原因是uv偏移&#xff0c;黑色区域会不断向左偏移&#xff0c;直到找到白色…

文献总结:ECCV2022-BEVFormer

BEVFormer 一、文章基本信息二、文章背景三、BEVFormer架构(1) BEV 查询(2) 空间交叉注意力机制(3) 时间自注意力机制(4) BEV应用(5) 实施细节 四、实验五、总结 一、文章基本信息 标题BEVFormer: Learning Bird’s-Eye-view Representation from Multi-camera images via spa…

Openlayers:海量图形渲染之WebGL渲染

最近由于在工作中涉及到了海量图形渲染的问题&#xff0c;因此我开始研究相关的解决方案。我在网络上寻找相关的解决方案时发现许多的文章都提到利用Openlayers中的WebGLPointsLayer类&#xff0c;可以实现渲染海量的点&#xff0c;之后我又了解到利用WebGLVectorLayer类可以渲…