22.缓存雪崩缓存击穿

news2024/11/15 23:03:04

定义

同一时段大量的缓存key同时失效或者redis服务宕机,导致大量请求到达数据库,带来巨大压力。

解决方案

1.给不同key的TTL添加随机值。

2.利用redis集群提高服务的可用性。

3.给缓存业务添加降级限流策略。

4.给业务添加多级缓存。

缓存击穿

缓存击穿也叫做热点key问题,被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求会在瞬间给数据库带来巨大冲击。

解决方案

1.互斥锁

缺点:线程相互等待,性能差。可能有死锁风险。

优点:保证强一致性,没有额外的内存消耗,不用存储逻辑过期时间字段,实现简单。

2.逻辑过期

优点:线程无需等待,性能较好。不保证一致性,有额外内存消耗,实现复杂。

基于互斥锁方式解决缓存击穿

这里的互斥锁采用setnx命令,当这个key不存在的时候才能设置值,如果key已经存在了,则设置值失败。

127.0.0.1:6379> setnx lock 1
(integer) 1
127.0.0.1:6379> setnx lock 2
(integer) 0
127.0.0.1:6379> get lock
"1"

加锁是 setnx key 命令->赋值

释放锁 del key 命令->删除

如果设置锁,然后迟迟没有去删除,也就是没有释放锁。所以在设置setnx锁的时候,会加一个有效期。

 /**
     * 尝试加锁
     * @return
     */
    public boolean tryLock(String key) {
        //setnx命令
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        //这里会自动拆箱,防止flag为null报空指针异常
        return BooleanUtil.isTrue(flag);
    }

    /**
     * 释放锁
     * @param key
     */
    public void unLock(String key) {
        stringRedisTemplate.delete(key);
    }
 /**
     * 防止缓存击穿-带互斥锁
     * @return
     */
    public Shop queryWithMutex(Long id) {
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        //从redis中查询商铺缓存
        String shopJsonStr = stringRedisTemplate.opsForValue().get(key);
        //redis中有数据直接返回
        if(StrUtil.isNotBlank(shopJsonStr)) {
            return JSONUtil.toBean(shopJsonStr, Shop.class);
        }
        //判断命中的是否为空值
        if(shopJsonStr != null) {
            //说明命中空字符串,不会去查数据库
            return null;
        }
        Shop shop = null;
        String lockKey = "lock:shop:"+id;
        try {
            //未命中,实现缓存重建
            //1.获取互斥锁
            boolean isLock = tryLock(lockKey);
            //2.判断是否获取互斥锁成功
            //3.失败则休眠重试
            if(!isLock) {
                Thread.sleep(50);
                return queryWithMutex(id);
            }
            //4.成功则,redis中没有数据,继续查询数据库
            shop = getById(id);
            if(ObjectUtil.isNull(shop)) {
                //将空值写入redis
                stringRedisTemplate.opsForValue().set(key, "", RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
                //数据库没有查询到数据,返回错误
                return null;
            }
            //数据库中查询到数据,存入redis,再返回数据;设置超时时间
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);

        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //5.释放互斥锁
            unLock(lockKey);
        }
        return shop;
    }

逻辑过期解决缓存击穿

向redis中添加数据时,额外添加一个时间字段。

逻辑过期,其实旧的数据还存在于redis中,只是是旧数据而已,还是可以在缓存重建的过程中返回旧数据。这样就不至于给数据库带来巨大压力。

 

 缓存数据预热

@Data
public class RedisData {
    /**
     * 逻辑过期时间
     */
    private LocalDateTime expireTime;
    private Object data;
}
/**
     * 创造热点数据带逻辑过期,提前预热
     * @param id
     */
    public void saveShop2Redis(Long id, Long expiredSeconds) {
        //1.查询店铺信息
        Shop shop = getById(id);
        //2.封装逻辑过期时间
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        RedisData redisData = new RedisData();
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expiredSeconds));
        redisData.setData(shop);
        //3.写入redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }
@Test
    void testSaveShop() {
        shopService.saveShop2Redis(1L, 10L);
    }

redis中存储的店铺带逻辑过期时间

{

"data": {

"area": "北京",

"openHours": "10:00-22:00",

"sold": 4215,

"images": "https://qcloud.dpfile.com/pc/jiclIsCKmOI2arxKN1Uf0Hx3PucIJH8q0QSz-Z8llzcN56-_QiKuOvyio1OOxsRtFoXqu0G3iT2T27qat3WhLVEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vfCF2ubeXzk49OsGrXt_KYDCngOyCwZK-s3fqawWswzk.jpg,https://qcloud.dpfile.com/pc/IOf6VX3qaBgFXFVgp75w-KKJmWZjFc8GXDU8g9bQC6YGCpAmG00QbfT4vCCBj7njuzFvxlbkWx5uwqY2qcjixFEuLYk00OmSS1IdNpm8K8sG4JN9RIm2mTKcbLtc2o2vmIU_8ZGOT1OjpJmLxG6urQ.jpg",

"address": "北京王府井19号",

"comments": 3035,

"avgPrice": 80,

"updateTime": 1724455437000,

"score": 37,

"createTime": 1640167839000,

"name": "画画茶餐厅",

"x": 120.149192,

"y": 30.316078,

"typeId": 1,

"id": 1

},

"expireTime": 1725022878389

}

//创建线程池
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    /**
     * 防止缓存击穿-逻辑过期+互斥锁
     * @return
     */
    public Shop queryWithLogicalExpired(Long id) {
        String key = RedisConstants.CACHE_SHOP_KEY + id;
        //从redis中查询商铺缓存
        String shopJsonStr = stringRedisTemplate.opsForValue().get(key);
        if(StrUtil.isBlank(shopJsonStr)) {
            //不存在,直接返回
            return null;
        }
        //redis中命中,需要将数据返序列化为java对象
        RedisData redisData = JSONUtil.toBean(shopJsonStr, RedisData.class);
        //判断是否逻辑过期
        LocalDateTime expireTime = redisData.getExpireTime();
        JSONObject data = (JSONObject)redisData.getData();
        Shop shop = JSONUtil.toBean(data, Shop.class);
        if(expireTime.isAfter(LocalDateTime.now())) {
            //未过期,返回店铺信息
            return shop;
        }
        //过期,需要重建缓存
        //这里可能会有大量的线程在这里等着,只有一个线程能够获取锁去完成缓存重建,完成缓存重建后释放锁,其他线程还会获取锁
        //所以在锁里面的逻辑需要再判断一次缓存是否过期,防止缓存反复的重建。
        //1.获取互斥锁
        String lockKey = RedisConstants.LOCK_SHOP_KEY + id;

        boolean lock = tryLock(lockKey);
        if(lock) {
            //获取锁成功
            //需要重新从redis中获取数据,再判断一次缓存是否过期,防止缓存反复的重建,DoubleCheck
            shopJsonStr = stringRedisTemplate.opsForValue().get(key);
            redisData = JSONUtil.toBean(shopJsonStr, RedisData.class);
            expireTime = redisData.getExpireTime();
            if(expireTime.isAfter(LocalDateTime.now())) {
                //未过期,返回店铺信息
                return shop;
            }

            //使用线程池去完成缓存重建(独立的线程)
            CACHE_REBUILD_EXECUTOR.submit(() -> {
                try{
                    saveShop2Redis(id, 10L);
                }catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    //在线程里面释放锁
                    unLock(lockKey);
                }
            });
        }
        //立即返回旧的逻辑过期数据
        return shop;
    }
这里可能会有大量的线程在这里等着,只有一个线程能够获取锁去完成缓存重建,完成缓存重建后释放锁,其他线程还会获取锁.所以在锁里面的逻辑需要再判断一次缓存是否过期,防止缓存反复的重建。需要重新从redis中获取数据,再判断一次缓存是否过期,防止缓存反复的重建,DoubleCheck。

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

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

相关文章

Redis持久化方式、常见问题及解决方案

在现代电商交易系统中,Redis作为一种高性能的内存数据库,被广泛用于缓存和数据持久化。然而,Redis作为内存数据库,面临着数据持久化和数据与持久化存储如MySQL之间的一致性问题。本文将详细讲解Redis的持久化方式、常见问题及其解…

常见框架报错信息

一、报错信息(不同类型转换) 2024-08-28 14:57:15.450 ERROR 8272 --- [io-8080-exec-12] c.w.common.exception.RRExceptionHandler : class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer ar…

IO进程练习:请在linux 利用c语言编程实现两个线程按照顺序依次输出”ABABABAB......“

例如&#xff1a;a线程输出”A”之后b线程输出”B”&#xff0c;然后a线程输出“A”&#xff0c;再b线程输出”B”&#xff0c;之后往复循环。 【1】使用信号量实现 代码展示&#xff1a; #include <stdio.h> #include <pthread.h> #include <string.h> #inc…

Java 输入与输出之 NIO.2【AIO】【Path、Paths、Files】【walkFileTree接口】探索之【三】

在JDK 1.7 版本中对NIO进行了完善&#xff0c;推出了NIO.2&#xff0c;也称为AIO&#xff08;异步IO&#xff09;&#xff0c;在处理大量并发请求时具有优势&#xff0c;特别是在网络编程和高并发场景下&#xff0c;表现得更为出色。 对于输出流和输入流而言&#xff0c;操作的…

sipp模拟uas发送update

概述 freeswitch是一款简单好用的VOIP开源软交换平台。 但是fs在处理update消息时候有BUG&#xff0c;为了复现问题&#xff0c;使用sipp模拟uas&#xff0c;发送update并发送DTMF码。 本文档记录sipp的配置方案。 环境 CentOS 7.9 freeswitch 1.10.7 sipp.3.6.2 问题描…

【Hexo系列】【6】NexT主题使用

本期将为大家讲解Hexo NexT主题的使用。 1. NexT介绍 NexT是Hexo的知名第三方主题&#xff0c;黑白极简风格四合一&#xff0c;有相当多的使用者&#xff0c;维护也相当给力&#xff0c;数年来一直都在频繁更新。 Next主题官网&#xff1a;https://github.com/next-theme/he…

Mysql基础练习题 584.寻找用户推荐人 (力扣)

找出那些 没有被 id 2 的客户 推荐 的客户的姓名。 建表插入数据&#xff1a; Create table If Not Exists Customer (id int, name varchar(25), referee_id int) Truncate table Customer insert into Customer (id, name, referee_id) values (1, Will, None) insert int…

大模型技术如何重塑物流供应链

一、大模型技术与物流供应链的融合现状 在物流供应链领域&#xff0c;大模型技术正逐渐崭露头角&#xff0c;并在各个环节实现了初步渗透。在需求预测环节&#xff0c;大模型凭借其强大的数据处理和分析能力&#xff0c;能够整合多种复杂的数据源&#xff0c;如历史销售数据、…

Unet改进14:添加SEAttention||减少冗余计算和同时存储访问

本文内容:在不同位置添加SEAttention注意力机制 目录 论文简介 1.步骤一 2.步骤二 3.步骤三 4.步骤四 论文简介 卷积算子是卷积神经网络(cnn)的核心组成部分,它使网络能够通过融合每层局部接受域内的空间和通道信息来构建信息特征。之前的广泛研究已经调查了这种关系的…

主流AI绘画工具-StableDiffusion本地部署方法(mac电脑版本)

Stable Diffusion是一款强大的AI生成图像模型&#xff0c;它可以基于文本描述生成高质量的图像。对于想要在本地运行此模型的用户来说&#xff0c;使用Mac电脑部署Stable Diffusion是一个非常吸引人的选择&#xff0c;特别是对于M1或M2芯片的用户。本文将详细介绍如何在Mac上本…

零基础入门天池镜像提交--windows场景VirtualBox虚拟机安装linux系统并ssh远程登录,直至镜像的制作及提交

背景&#xff1a;由于本人只有一台windows,天池上的比赛需要提交镜像&#xff0c;自己试了好多方法给windows安装linux&#xff0c;但是始终没安装成功。最终采用在利用VirtualBox安装linux虚拟机&#xff0c;使用MobaXterm进行ssh登陆linux&#xff0c;镜像的制作、push、提交…

单片机-初识单片机(keil安装以及编写简单驱动)(一)

目录 一、嵌入式介绍 1.嵌入式系统&#xff1a; 2.嵌入式操作系统 3.单片机&#xff1a; 二、STM32F103ZET6简介 1.单片机的组成&#xff1a; 2.单片机外观&#xff1a; 3.ARM公司 4.ST公司--意法半导体 三、资料部分 1.安装工具&#xff1a; 2.破解软件&#xff1…

【学习笔记之vue】const fileFilter = options?.fileFilter || (() => true);

运行vue3时出现下面这个问题&#xff0c;大概就是不能识别出es6的格式 ERROR SyntaxError: Unexpected token . F:\front\node_modules\unimport\dist\shared\unimport.af6409e3.cjs:976const fileFilter options?.fileFilter || (() > true);检查了一下现node版本是12版…

Self-study Python Fish-C Note19 P62to63

类和对象 (part 2) 本节主要介绍 类和对象的构造函数、重写、钻石继承、Mixin及案例源码剖析&#xff08;原视频P62-63)\ 构造函数 之前我们在函数章节里说&#xff0c;函数是可以通过参数来进行个性化定制的。类在实例化的时候其实也是支持个性化定制对象的。 定义类的时候…

Vue.js入门系列(十八):利用浏览器本地存储实现TodoList数据持久化

个人名片 &#x1f393;作者简介&#xff1a;java领域优质创作者 &#x1f310;个人主页&#xff1a;码农阿豪 &#x1f4de;工作室&#xff1a;新空间代码工作室&#xff08;提供各种软件服务&#xff09; &#x1f48c;个人邮箱&#xff1a;[2435024119qq.com] &#x1f4f1…

基于Python的机器学习系列(17):梯度提升回归(Gradient Boosting Regression)

简介 梯度提升&#xff08;Gradient Boosting&#xff09;是一种强大的集成学习方法&#xff0c;类似于AdaBoost&#xff0c;但与其不同的是&#xff0c;梯度提升通过在每一步添加新的预测器来减少前一步预测器的残差。这种方法通过逐步改进模型&#xff0c;能够有效提高预测准…

欧洲游戏市场的文化和语言特征

欧洲游戏市场是一个由无数文化和语言特征塑造的丰富多样的景观。作为世界上最大、最多样化的游戏地区之一&#xff0c;欧洲为旨在创造与广大受众产生共鸣的内容的开发者带来了独特的挑战和机遇。 欧洲市场最引人注目的方面之一是语言的多样性。欧盟有24种官方语言和众多地区方…

开源软件指南

目录 什么是开源软件 开源软件的历史和哲学 什么是开源许可证 开源许可证的类型 ​编辑 开源合规性 版权和知识产权合规性 安全合规性 什么是开源软件政策 开源软件安全 库存和软件物料清单(SBOM) 依赖项更新 开源漏洞扫描 二进制存储库管理器 开源软件的当前趋势…

C#语言实现最小二乘法算法

最小二乘法&#xff08;Least Squares Method&#xff09;是一种常用的拟合方法&#xff0c;用于在数据点之间找到最佳的直线&#xff08;或其他函数&#xff09;拟合。以下是一个用C#实现简单线性回归&#xff08;即一元最小二乘法&#xff09;的示例代码。 1. 最小二乘法简介…

【kubernetes】金丝雀部署

概念&#xff1a; 金丝雀发布&#xff08;又称灰度发布、灰度更新&#xff09;&#xff1a;金丝雀发布一般先发1台&#xff0c;或者一个小比例&#xff0c;例如2%的服务器&#xff0c;主要做流量验证用&#xff0c;也称为金丝雀 (Canary) 测试 &#xff08;国内常称灰度测试&a…