Redis三剑客:缓存雪崩、缓存穿透、缓存击穿

news2024/11/22 22:15:38

文章目录

  • 缓存雪崩
  • 缓存穿透
  • 缓存击穿

缓存雪崩

缓存雪崩产生原因:由于缓存在同一时间大面积失效或者Redis宕机导致大量请求落入数据库,给数据库造成巨大的压力
解决方案:
1、当将数据添加到缓存中时,给缓存时间添加随机的过期时间。可以防止缓存在同一时间大面积失效。
2、使用Redis集群。当有节点宕机时使用其他节点恢复数据。
3、做好限级限流的策略
4、使用多级缓存

缓存穿透

缓存穿透产生原因:被恶意者攻击,发送大量不存在的数据请求,导致请求不会命中缓存,直接落到数据库,给数据库带来巨大的压力
解决方案:
1、缓存空值键到缓存中,适用于大量请求所带的id相同。为了避免内存的浪费,可以给空值键设置一个过期时间
优点:实现简单
缺点:当大量请求所带的id不同时,需要缓存多个空值键,占用额外的内存空间

保证空键值的实现:

    @Autowired
    private StringRedisTemplate redisTemplate;

    public void set(String key, Object o, Long time, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(o), time, unit);
    }
    /**
     * 缓存空值键(解决缓存穿透)
     */
    public  <R, Id> R querySaveNull(String prefixKey, Id id, Class<R> type, Function<Id, R> dbFunction, Long time, TimeUnit unit) {
        System.out.println("缓存空值");
        String cacheKey = prefixKey + id;
        String objectStr = redisTemplate.opsForValue().get(cacheKey);
        if (StrUtil.isNotBlank(objectStr)) {
            return JSONUtil.toBean(objectStr, type);
        }
//        shopStr不为null说明为'',即命中空值键的缓存
        if (objectStr != null) {
            return null;
        }
        R r = dbFunction.apply(id);
//        当查出来的数据为null时,往redis中添加空值的键
        if (r == null) {
            redisTemplate.opsForValue().set(cacheKey, "", CACHE_NULL_TTL,TimeUnit.SECONDS);
            return null;
        }
        set(cacheKey,r,time,unit);
        return r;
    }

2、适用布隆过滤器。将数据库所有数据的id放入布隆过滤器中,当有请求到达时,先经过布隆过滤器,判断布隆过滤器中是否存在请求id,如果存在则通过,如果不存在则直接返回。
缺点:实现复杂,存在误差
3、可以增加id的复杂性,添加请求所带id的检验,可以防止被攻击者猜到id并发出攻击

缓存击穿

缓存击穿产生原因:存在高并发的热点键,并且其重建缓存业务复杂,重建缓存耗时长,当热点键过期失效时,大量线程进入重建缓存,查询数据库,导致数据库压力暴涨
解决方案:
1、基于互斥锁的实现。
基于Redis的setnx命令实现互斥锁,当数据不存在时可以添加数据成功,数据存在时添加数据失败,当线程适用setnx命令添加成功数据时则成功抢到了锁,添加失败则获取锁失败,线程执行结束后需要及时释放锁,为了防止因为其他原因锁没有被释放,可以给锁添加一个过期时间。


    /**
     * 获取互斥锁
     */
    private Boolean getLock(String lockKey) {
//        返回的布尔类型使包装型,可能为null值,调用布尔工具类,当包装类为true时才返回true,否则返回false
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", LOCK_SHOP_TTL, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(aBoolean);
    }

    /**
     * 释放互斥锁
     */
    private void releaseLock(String lockKey) {
        redisTemplate.delete(lockKey);
    }

当多线程到达时,首先查看能否命中缓存,如果缓存失效我们尝试让这些线程竞争锁,竞争到锁的线程负责进行缓存重建的业务,其他的线程则使其休眠一段时间后重新执行业务。以下是基于Shop对象的实现。

  private Shop queryWithMutex(Long id) {
//       首先从缓存中查询数据
        String cacheKey = CACHE_SHOP_KEY + id;
        String shopStr = redisTemplate.opsForValue().get(cacheKey);
        if (StrUtil.isNotBlank(shopStr)) {
            return JSONUtil.toBean(shopStr, Shop.class);
        }
        String lockKey = LOCK_SHOP_KEY + id;
        Boolean aBoolean = getLock(lockKey);
        Shop shop = null;
//       如果获取锁失败,则进行递归重新执行
        try {
            if (!aBoolean) {
      
                Thread.sleep(50);
                return queryWithMutex(id);
            }
            Thread.sleep(500);
            shop = getById(id);
//        当查出来的数据为null时,往redis中添加空值的键
            if (shop == null) {
                redisTemplate.opsForValue().set(cacheKey, "", CACHE_NULL_TTL, TimeUnit.SECONDS);
                return null;
            }
            redisTemplate.opsForValue().set(cacheKey, JSONUtil.toJsonStr(shop), CACHE_SHOP_TTL, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
//            当抛出异常或者业务执行完毕之后释放锁
            releaseLock(lockKey);
        }
        return shop;
    }

优点:保证了数据的高一致性,所有请求都能够返回最新的数据
缺点:重建缓存耗时较长,其他线程都需要等待缓存重建消耗的时间

2、基于逻辑过期实现。我们不给键添加过期时间,让其一直存在缓存中,但是我们在将数据其添加进缓存中时,会给其添加一个逻辑过期的字段,当多线程到达时,我们首先命中缓存,取出逻辑过期的字段,判断数据是否过期,如果数据过期,让线程竞争锁,获取到锁的线程新创建一个线程来进行缓存重建的业务,然后主线程和其他未竞争到锁的线程直接返回过期的数据。以下时基于Shop对象的实现

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}
private Shop queryWithLogicalExpire(Long id) {
        String key = CACHE_SHOP_KEY + id;
        String redisDataStr = redisTemplate.opsForValue().get(key);
        if (StrUtil.isBlank(redisDataStr)) {
            return null;
        }
        RedisData redisData = JSONUtil.toBean(redisDataStr, RedisData.class);
        Shop shop = JSONUtil.toBean((JSONObject) redisData.getData(), Shop.class);
//        逻辑时间未过期
        LocalDateTime expireTime = redisData.getExpireTime();
        System.out.println(expireTime);
        System.out.println(LocalDateTime.now());
        if (expireTime.isAfter(LocalDateTime.now())) {
            return shop;
        }
//        逻辑时间过期后,竞争锁
        String lockKey = LOCK_SHOP_KEY + id;
//        竞争成功锁的线程new线程来进行缓存重建
        Boolean isLock = getLock(lockKey);
        if (isLock){
                executorService.execute(()->{
                    try {
                        Thread.sleep(500);
                        System.out.println("缓存重建");
                        saveShopRedis(id,CACHE_SHOP_TTL);
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        releaseLock(lockKey);
                    }
                });
            }
        return shop;
    }

    /**
      逻辑过期进行缓存重建
     */
    public void saveShopRedis(Long id, Long time) {
        String key = CACHE_SHOP_KEY + id;
        Shop shop = getById(id);
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(time));
        redisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

优点:线程无需等待,直接返回数据
缺点:不保证数据的高一致性,部分线程可能返回以及过期的数据

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

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

相关文章

《SpringBoot、Vue 组装exe与套壳保姆级教学》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Flowable第一篇、快速上手(Flowable安装、配置、集成)

目录 Flowable 概述Flowable的安装与配置 2.1. FlowableUI安装 2.2. Flowable BPMN插件下载 2.3 集成Spring Boot流程审核操作 3.3 简单流程部署 3.4 启动流程实例 3.5 流程审批 一、Flowable 概述 Flowable是一个轻量级、高效可扩展的工作流和业务流程管理&#xff08;BPM&…

Docker搭建有UI的私有镜像仓库

Docker搭建有UI的私有镜像仓库 一、使用这个docker-compose.yml文件&#xff1a; version: 3services:registry-ui:image: joxit/docker-registry-ui:2.5.7-debianrestart: alwaysports:- 81:80environment:- SINGLE_REGISTRYtrue- REGISTRY_TITLEAtt Docker Registry UI- DE…

容器安全检测和渗透测试工具

《Java代码审计》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484219&idx1&sn73564e316a4c9794019f15dd6b3ba9f6&chksmc0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene21#wechat_redirect Docker-bench-…

Day10_CSS过度动画

Day10_CSS过度动画 背景 : PC和APP项目我们已经开发完毕, 但是再真正开发的时候有些有些简易的动态效果我们可以使用CSS完成 ; 本节课我们来使用CSS完成基础的动画效果 今日学习目标 CSS3过度CSS3平面动态效果CSS3动画效果案例 1. CSS3过渡 ​ 含义 :过渡指的是元素从一种…

iOS应用网络安全之HTTPS

移动互联网开发中iOS应用的网络安全问题往往被大部分开发者忽略, iOS9和OS X 10.11开始Apple也默认提高了安全配置和要求. 本文以iOS平台App开发中对后台数据接口的安全通信进行解析和加固方法的分析. 1. HTTPS/SSL的基本原理 安全套接字层 (Secure Socket Layer, SSL) 是用来…

excel版数独游戏(已完成)

前段时间一个朋友帮那小孩解数独游戏&#xff0c;让我帮解&#xff0c;我看他用电子表格做&#xff0c;只能显示&#xff0c;不能显示重复&#xff0c;也没有协助解题功能&#xff0c;于是我说帮你做个电子表格版的“解题助手”吧&#xff0c;不能直接解题&#xff0c;但该有的…

金融数据中心容灾“大咖说” | 美创科技赋能“灾备一体化”建设

中国人民银行发布的《金融数据中心容灾建设指引》&#xff08;JR/T 0264—2024&#xff09;已于2024年7月29日正式实施。这一金融行业标准对金融数据中心容灾建设中的“组织保障、需求分析、体系规划、建设要求、运维管理”进行了规范和指导。面对不断增加的各类网络、业务、应…

Qt:信号槽

一. 信号槽概念 信号槽 是 Qt 框架中一种用于对象间通信的机制 。它通过让一个对象发出信号&#xff0c;另一个对象连接到这个信号的槽上来实现通信。信号槽机制是 Qt 的核心特性之一&#xff0c;提供了一种灵活且类型安全的方式来处理事件和数据传递。 1. 信号的本质 QT中&a…

SpringBoot与MongoDB深度整合及应用案例

SpringBoot与MongoDB深度整合及应用案例 在当今快速发展的软件开发领域&#xff0c;NoSQL数据库因其灵活性和可扩展性而变得越来越流行。MongoDB&#xff0c;作为一款领先的NoSQL数据库&#xff0c;以其文档导向的存储模型和强大的查询能力脱颖而出。本文将为您提供一个全方位…

大数据调度组件之Apache DolphinScheduler

Apache DolphinScheduler 是一个分布式易扩展的可视化 DAG 工作流任务调度系统。致力于解决数据处理流程中错综复杂的依赖关系&#xff0c;使调度系统在数据处理流程中开箱即用。 主要特性 易于部署&#xff0c;提供四种部署方式&#xff0c;包括Standalone、Cluster、Docker和…

ThinkPHP6门面(Facade)

门面 门面&#xff08;Facade&#xff09; 门面为容器中的&#xff08;动态&#xff09;类提供了一个静态调用接口&#xff0c;相比于传统的静态方法调用&#xff0c; 带来了更好的可测试性和扩展性&#xff0c;你可以为任何的非静态类库定义一个facade类。 系统已经为大部分…

MySQL win安装 和 pymysql使用示例

目录 一、MySQL安装 下载压缩包&#xff1a; 编写配置文件&#xff1a; 配置环境变量&#xff1a; 初始化服务和账户 关闭mysql开机自启&#xff08;可选&#xff09; 建议找一个数据库可视化软件 二、使用pymysql操作数据库 安装pymysql 示例代码 报错处理 一、My…

Parker派克防爆电机在实际应用中的安全性能如何保证?

Parker防爆电机确保在实际应用中的安全性能主要通过以下几个方面来保证&#xff1a; 1.防爆外壳设计&#xff1a;EX系列电机采用强大的防爆外壳&#xff0c;设计遵循严格的防爆标准&#xff0c;能够承受内部可能发生的爆炸而不破损&#xff0c;利用间隙切断原理&#xff0c;防…

空间与单细胞转录组学的整合定位肾损伤中上皮细胞与免疫细胞的相互作用

result 在空间转录组图谱中对人类肾脏进行无监督映射和细胞类型鉴定 我们试图在H&E染色的人类参考肾切除标本组织切片上直接映射转录组特征。该组织来自一名59岁的女性&#xff0c;其肾小球闭塞和间质纤维化程度最低&#xff08;分别影响不到10%的肾小球或肾实质&#xff…

greater<>() 、less<>()及运算符 < 重载在排序和堆中的使用

简略图 greater<>()(a, b) a > b 返回true&#xff0c;反之返回false less<>()(a, b) a < b 返回true&#xff0c;反之返回false 在cmp中使用&#xff08;正着理解&#xff09; 规则返回true时a在前&#xff0c;反之b在前 在priority_queue中使用 &#xff…

详细描述一下Elasticsearch索引文档的过程?

大家好&#xff0c;我是锋哥。今天分享关于【详细描述一下Elasticsearch索引文档的过程&#xff1f;】面试题。希望对大家有帮助&#xff1b; 详细描述一下Elasticsearch索引文档的过程&#xff1f; Elasticsearch的索引文档过程是其核心功能之一&#xff0c;涉及将数据存储到…

入门车载以太网(6) -- XCP on Ethernet

目录 1.寻址方式 2.数据帧格式 3.特殊指令 4.使用实例 了解了SOME/IP之后&#xff0c;继续来看看车载以太网在汽车标定领域的应用。 在汽车标定领域XCP是非常重要的协议&#xff0c;咱们先来回顾下基础概念。 XCP全称Universal Measurement and Calibration Protocol&a…

Python中常用的函数介绍

Python中常用的几种函数 1、input函数 input()函数&#xff1a;主要作用是让用户输入某个内容并接收它。 #输入你的年龄 >>> age input("my age is :") my age is :20 执行代码后输入年龄&#xff0c;年龄被存放到age变量中&#xff0c;执行print后终端会…

Ubuntu从入门到精通(二)远程和镜像源配置齐全

Ubuntu从入门到精通(二) 1 常见操作配置 1.1 英文语言配置 1.1.1 打开设置 1.1.2 设置语言为英文 1.1.3 重启生效 1.1.4 再次进入,选择更新名字 1.1.5 再次进入,发现已经变成了英文 1.2 输入法配置 1.3 rustdesk安装 1.3.1 Windows系统配置 登陆:https://github.com…