redis缓存击穿,redisson分布式锁,redis逻辑过期

什么是缓存击穿:

缓存击穿是指在高并发环境下,某个热点数据的缓存过期,导致大量请求同时访问后端存储系统,引起系统性能下降和后端存储压力过大的现象。

解决方案:
1. redisson分布式锁

本质上是缓存重建的过程中,大量的请求访问到后端的数据库导致数据库压力过大
那么可以使用redisson分布式锁来对缓存重建的过程加锁
其它的线程只有缓存重建完毕之后才可以访问
缺点:所有的请求都要等待拿到锁的线程来进行缓存重建
优点:数据拥有高一致性,适用于某些涉及“钱”的业务,或者要求数据的强一致性的。

  1. 新建redisson子工程单独作为微服务名字叫redisson-starter
  2. 引入redisson相关依赖
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>3.17.5</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.18</version>
        </dependency>
  1. 项目结构
    在这里插入图片描述
    RedissonConfigProperties:一些redisson需要的配置项,如果是集群此处不能用这种方式
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Data
@ConfigurationProperties(prefix = "redisson-lock")
public class RedissonConfigProperties {

    private String redisHost;

    private String redisPort;

}

RedissonConfig:配置RedissonClient,并且加入了一些自动装配的配置

import com.yonchao.redisson.service.RedissonLockService;
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Data
@Configuration
@EnableConfigurationProperties({RedissonConfigProperties.class})
//当引入Service接口时
@ConditionalOnClass(RedissonLockService.class)
public class RedissonConfig {


    @Autowired
    private RedissonConfigProperties redissonConfigProperties;

    /**
     * 对 Redisson 的使用都是通过 RedissonClient 对象
     * @return
     */
    @Bean(destroyMethod="shutdown") // 服务停止后调用 shutdown 方法。
    public RedissonClient redisson() {
        // 1.创建配置
        Config config = new Config();
        // 集群模式
        // config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
        // 2.根据 Config 创建出 RedissonClient 示例。
        config.useSingleServer().setAddress("redis://"+redissonConfigProperties.getRedisHost()+":"+redissonConfigProperties.getRedisPort());
        return Redisson.create(config);
    }
}

spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.yonchao.redisson.service.RedissonLockService

业务类:

import com.yonchao.redisson.service.RedissonLockService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

@Service
public class RedissonLockServiceImpl implements RedissonLockService {
    @Autowired
    private RedissonClient redissonClient;
    /**
     * 加锁
     * @param lockKey
     * @return
     */
    public boolean acquireLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
    /**
     * 释放锁
     * @param lockKey
     * @return
     */
    public void releaseLock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }
}
  1. 其它微服务通过pom引入redisson-starter微服务
    在这里插入图片描述
  2. 重建缓存过程中使用分布式锁
    首先注入RedissonClient
    接着判断布隆过滤器
    接着从缓存中读数据
    读不到的话需要重建缓存
    重建缓存:
    首先获取分布式锁
    获取成功了就查询数据库并且重建缓存返回数据最后释放锁
    获取分布式锁失败就等待1秒接着递归这个方法,直到有一个线程重建缓存成功。
    /**
     * 使用逻辑过期的方式
     * @param id
     * @return
     */
    @Override
    public ResponseResult selectArticleLogicalExpiration(Long id) {
        // 首先经过布隆过滤器
        // 判断这个id是不是在布隆过滤器中
        boolean mightContain = bloomFilter.mightContain(id);

        // 不存在直接返回
        if (!mightContain) {
            return ResponseResult.okResult();
        }
        
        // 首先从缓存中获取数据
        Object articleObj = redisTemplate.opsForValue().get(ARTICLE_KEY + id);
        
        if (Objects.nonNull(articleObj)){

            String articleJSON = (String) articleObj;

            JSONObject jsonObject = JSON.parseObject(articleJSON);

            Long expired = jsonObject.getLong("expired");
            // 旧的文章对象
            ApArticle article = JSON.parseObject(articleJSON, ApArticle.class);
            if (Objects.nonNull(expired)){
                // 未过期直接返回
                if (expired - System.currentTimeMillis() > 0) {

                    return ResponseResult.okResult(article);
                }

                // 过期了进行缓存的重建
                boolean acquiredLock = redissonLockService.acquireLock(lockKeyRedisson);
                // 拿到锁了就 新开一个线程 进行缓存的重建 此处使用分布式锁,只会有一个线程抢占到缓存重建所以不用使用线程池
                if (acquiredLock) {
                    try {
                        new Thread(() -> {
                            // 直接重建缓存,不关心返回值
                            rebuildCache(id);
                        }).start();

                    } finally {
                        // 最后释放锁
                        redissonLockService.releaseLock(lockKeyRedisson);
                    }
                }
                // 开启多线程后直接返回旧的数据
                return ResponseResult.okResult(article);
            }

        }
        // 缓存中根本没有,那么需要直接加锁重建缓存,此时不能多线程的去重建缓存,只能通过分布式锁的方式,
        boolean lockAcquired = redissonLockService.acquireLock(lockKeyLogicalExpiration);
        if (lockAcquired){
            try {
                ApArticle apArticle = rebuildCache(id);
                if (Objects.isNull(apArticle)){
                    // 数据不存在就直接返回
                    return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST);
                }
                // 返回获得的文章数据
                return ResponseResult.okResult(apArticle);

            } finally {
                // 最后释放锁
                redissonLockService.releaseLock(lockKeyLogicalExpiration);
            }
        } else {
            // 没有获取到锁就等待一段时间然后再次尝试获取锁
            try {
                Thread.sleep(1000);
            }catch (Exception e){
                log.error(e.getMessage());
            }
            // 等待一段时间重新校验有没有缓存
            return selectArticleLogicalExpiration(id);
        }
    }


    public ApArticle rebuildCache(Long id){
        // 重建缓存
        ApArticle articleDatabase = getById(id);
        // 重建缓存
        if (Objects.nonNull(articleDatabase)) {
            // 设置逻辑过期时间
            // 转为jsonString
            String articleJsonString = JSON.toJSONString(articleDatabase);
            // 转为JSONObject
            JSONObject articleJsonObject = JSON.parseObject(articleJsonString);
            // 当前时间戳加上 设置的过期时间*1000 因为时间戳是毫秒
            articleJsonObject.put("expired", System.currentTimeMillis() + ARTICLE_EXPIRED * 1000);
            redisTemplate.opsForValue().set(ARTICLE_KEY + id, JSON.toJSONString(articleJsonObject));
            // 布隆过滤器过滤过的,这个肯定存在
            return articleDatabase;
        }
        return null;
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

selenium自动化测试入门 —— 操作元素对象

一、元素的常用操作 element.click() # 单击元素&#xff1b;除隐藏元素外&#xff0c;所有元素都可单击 element.submit() # 提交表单&#xff1b;可通过form表单元素提交表单 element.clear() # 清除元素的内容&#xff1b;如果可以的话 element.send_keys(‘需要输入的…

基于动力学模型的机械臂pid控制

参考资料&#xff1a; 一、如何实现机械臂的控制 在最常见的对机械臂动力学实现控制的问题中&#xff0c;我们会有一段机械臂末端的期望轨迹S&#xff0c;希望通过对机械臂关节处电机转矩的控制实现末端沿期望轨迹的完美运动。控制问题主要分为镇定和跟踪两种&#xff0c;上面…

LabVIEW实现变风量VAV终端干预PID控制

LabVIEW实现变风量VAV终端干预PID控制 变风量&#xff08;VAV&#xff09;控制方法的研究一直是VAV空调研究的重点。单端PID控制在温差较大时&#xff0c;系统容易出现过冲。针对空调终端单端PID控制的不足&#xff0c;设计一种干预控制与PID控制耦合的控制方法。项目使用LabV…

利用shp文件构建mask【MATLAB和ARCGIS】两种方法

1 ARCGIS &#xff08;推荐&#xff01;&#xff01;&#xff01;-速度很快&#xff09; 利用Polygon to Raster 注意&#xff1a;由于我们想要的mask有效值是1&#xff0c;在进行转换的时候&#xff0c;注意设置转换字段【Value field】 【Value field】通过编辑shp文件属性表…

el-table样式

1、实现效果&#xff0c;外部框是蓝绿色边框&#xff0c;深色背景&#xff0c;里面的表格首先设置透明色&#xff0c;然后应用自定义斑马纹。 2、代码 template代码&#xff0c;其中样式frameBordStyle是深色背景框&#xff0c;不负责表格样式&#xff0c;表格样式由tableStyl…

OSG多视口创建:osgViewer::CompositeViewer

1、效果 在osg的实际应用场景中&#xff0c;有时候需要同时创建多个场景视图&#xff0c;并保证各个场景视图中有不一样的显示和操作&#xff1a;例如&#xff1a;漫游器、照相机、粒子效果、多个模型组合等。此时就要用到OSG提供的osgViewer::CompositeViewer类来实现这个需求…

零信任网络:一种全新的网络安全架构

随着网络技术的不断发展&#xff0c;网络安全问题日益凸显。传统的网络安全策略往往基于信任和验证&#xff0c;但这种信任策略存在一定的局限性。为了解决这一问题&#xff0c;零信任网络作为一种全新的网络安全架构&#xff0c;逐渐受到人们的关注。本文将对零信任网络的概念…

推荐PHP付费进群源码

PHP付费进群源码带自动定位基于ThinkPHP框架开发的&#xff0c;可以快速搭建知识付费粉丝进群。 更新&#xff1a; 1.首页付款轮播 2.城市定位功能 3.更新及优化域名库及支付设置 4.新增一张图模板设置模式&#xff0c;简化后台模板设置 5.前后台其他优化 演示地址&#xff1a…

centos7部署nginx

CentOS7安装Nginx-1.16.1稳定版 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.安装依赖环境 yum -y install gcc gcc-c automake pcre pcre-devel zlib zlib-devel openssl openssl-devel 2.下载安装包&#xff08;不能联网的不行&#xff09;&#xff0c;可以留言…

电路正负反馈,电压电流反馈,串并联反馈详细判别方法

正/负反馈&#xff1a;假设输出升高&#xff0c;转一圈回来仍使其升高就是正反馈&#xff0c;反之就是负反馈。作图法&#xff1a;在RL的信号端画一个向上的小箭头&#xff0c;沿着反馈环路&#xff0c;每经过一个元器件就画一个相应的箭头&#xff0c;一直画到放大器的输出端&…

计算机组成与结构-安全性和可靠性

系统可靠性分析 概念 平均无故障时间 MTTF 1/失效率 平均故障修复时间 MTTR1/修复率 平均故障间隔时间 MTBFMTTFMTTR 系统可用性 MTTF/(MTTFMTTR)*100% 计算 串联系统 一个设备不可靠&#xff0c;整个系统崩溃RR1R2Rn 并联系统 所有设备不可靠&#xff0c;整个系统崩溃R1-(1…

基于单片机的商场防盗防火系统设计

收藏和点赞&#xff0c;您的关注是我创作的动力 文章目录 概要 一、系统分析二、系统总设计2.1基于单片机的商场防火防盗系统的总体功能2.2系统的组成 三 软件设计4.1软件设计思路4.2软件的实现4.2.1主控模块实物 四、 结论五、 文章目录 概要 本课题设计一种商场防火防盗报警…

Quartus II 13.0波形仿真(解决无法产生仿真波形问题)

目录 前言 新建工程 创建Verilog文件&#xff0c;写代码 波形仿真&#xff08;解决没有输出波问题&#xff09; 前言 这么说把Quartus II 13.0是我目前来讲见过最恶心的软件&#xff0c;总是一大堆麻烦事&#xff0c;稍微哪里没弄好就后面全都出问题。很多人在写完Verilog代…

【PWN · heap | Overlap | off-by-one】HITCON Trainging lab13

记录一道wiki学习overlap的题目 前言 通过overlap可以造成堆的重叠&#xff0c;进而通过堆的修改、访问等操作&#xff0c;劫持或泄露另一个堆的信息&#xff0c;如果堆上存在指针&#xff0c;而存在对指针的读写&#xff0c;就可以控制修改该指针&#xff0c;进行任意地址读/…

【每日一题】移除链表元素(C语言)

移除链表元素&#xff0c;链接奉上 目录 思路&#xff1a;代码实现&#xff1a;链表题目小技巧&#xff1a; 思路&#xff1a; 在正常情况&#xff1a; 下我们移除链表元素时&#xff0c;需要该位置的前结点与后节点&#xff0c; 在特别情况时&#xff1a; 例如 我们发现&…

基于STC12C5A60S2系列1T 8051单片机EEPROM应用

基于STC12C5A60S2系列1T 8051单片机EEPROM应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍STC12C5A60S2系列1T 8051单片机EEPROM介绍基于STC12C5A60S2系列1T 8051单…

Proxysql读写分离

Proxysql读写分离 主从配置 # /etc/my.cnf 主节点 [mysqld] log-binmysql-bin server-id1从节点 [mysqld] server-id2 read_only1#初始化以及创建主从复制用户 mysql> alter user rootlocalhost identified with mysql_native_password by Jianren123; Query OK, 0 rows …

交叉编译工具链(以STM32MP1为例)

1.什么是交叉编译工具链&#xff1f; 在一个系统上进行编译&#xff0c;在另一个系统上进行执行 2.STM32MP1交叉编译工具链 3.交叉编译器内容 4.两种工具链模式 5.两种链接模式 6.工具使用 注意&#xff1a;OpenSTLinux已经提供了编译框架&#xff0c;不需要命令行手工编译 …

界面控件DevExpress WPF Gauge组件 - 轻松实现个性化商业仪表盘

DevExpress WPF Gauge&#xff08;仪表&#xff09;控件包含了多种圆形仪表类型、水平和垂直线性仪表、分段和矩阵数字仪表以及状态指示器&#xff0c;同时还具有最终用户交互性的集成支持。 P.S&#xff1a;DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至…

Android Studio的Java项目种运行main()的方法

首先随便在项目里面建一个类 public class TestSocket {public static void main(String[] args) {System.out.println("hahah");}}直接运行时会直接报错的 在项目的 .idea 添加代码 <option name"delegatedBuild" value"false"/> 再…