java-redis-击穿

news2024/9/16 8:55:16

Java 与 Redis 之缓存击穿问题解决方案

1. 背景:缓存的基本概念

在高并发系统中,缓存是一个非常重要的优化手段。它的基本思想是将热点数据缓存在高速的存储系统(如 Redis、Memcached)中,从而减轻数据库等持久层的压力,并加快请求响应速度。

常见的缓存模式有:

  • 缓存读写:读取数据时优先从缓存中获取,如果缓存中没有数据,则从数据库或其他持久化存储中获取并缓存。
  • 缓存失效策略:缓存系统通常会为每条缓存设置过期时间(TTL),过期后数据会从缓存中删除,避免数据长期过期。
2. 缓存击穿的概念

缓存击穿(Cache Breakdown)是指缓存中某些高并发访问的数据在失效的瞬间,大量请求同时穿透缓存直接访问数据库的情况。由于这些数据是热点数据,短时间内大量请求集中访问数据库,容易导致数据库过载,甚至宕机。

缓存击穿的触发场景通常是:

  • 缓存数据有明确的过期时间(TTL)。
  • 热点数据在缓存失效后,瞬间有大量请求同时发起读取操作。

区别于其他缓存问题

  • 缓存穿透:请求的数据在数据库中不存在,直接穿透缓存,访问数据库。
  • 缓存雪崩:大量缓存同时失效,导致大量请求直接访问数据库,可能引发雪崩效应。
3. 缓存击穿的解决方案

为了避免缓存击穿问题,我们需要在缓存失效时控制多个并发请求直接访问数据库的情况。常用的解决方案包括:

  1. 互斥锁:为某个热点数据设置一个锁,当第一个请求获取数据时,其他请求等待,数据更新后释放锁。
  2. 缓存预热:在数据过期之前,提前主动刷新缓存,避免数据过期导致的瞬时压力。
  3. 逻辑过期:缓存中的数据设置逻辑过期标志,定期异步更新数据,避免高并发下的缓存失效。
  4. 过期自动更新:使用定时任务,在缓存失效前重新加载数据,确保缓存中的数据始终有效。
4. 方案一:互斥锁解决缓存击穿

**互斥锁(Mutex)**是解决缓存击穿最常见的办法。当某个缓存失效时,第一个请求负责加载数据并重新设置缓存,其他请求等待数据加载完成后直接返回缓存结果。

4.1. 基本流程
  1. 请求到达,尝试读取缓存。
  2. 如果缓存中有数据,直接返回。
  3. 如果缓存没有数据,使用互斥锁保证只有一个线程能够从数据库获取数据,其他请求等待。
  4. 获取到数据的线程更新缓存,并释放锁。
  5. 其他请求重新读取缓存。
4.2. 代码实现

下面是一个基于 Redis 实现互斥锁的缓存击穿解决方案。我们使用 Java 的 Redis 客户端(Jedis)来进行 Redis 操作,并利用 Redis 的 SETNX(set if not exists)来实现分布式锁。

import redis.clients.jedis.Jedis;

public class CacheService {

    private Jedis jedis;

    public CacheService(Jedis jedis) {
        this.jedis = jedis;
    }

    // 获取数据的方法,包含缓存逻辑
    public String getData(String key) {
        // 尝试从 Redis 缓存中获取数据
        String value = jedis.get(key);
        
        if (value == null) {
            // 缓存中没有数据,进入加载流程
            String lockKey = "lock:" + key;
            
            // 尝试加锁,避免缓存击穿
            if (tryLock(lockKey)) {
                try {
                    // 模拟从数据库加载数据
                    value = loadFromDB(key);
                    
                    // 将数据写入缓存,并设置超时时间
                    jedis.setex(key, 300, value);
                } finally {
                    // 释放锁
                    releaseLock(lockKey);
                }
            } else {
                // 获取锁失败,等待其他线程更新缓存
                try {
                    // 等待一段时间再尝试获取缓存
                    Thread.sleep(100);
                    return jedis.get(key);  // 再次从缓存获取
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        return value;
    }

    // 尝试获取锁
    private boolean tryLock(String lockKey) {
        // 使用 Redis 的 SETNX 设置锁,成功返回1,失败返回0
        String result = jedis.set(lockKey, "1", "NX", "EX", 10); // 锁过期时间为10秒
        return "OK".equals(result);
    }

    // 释放锁
    private void releaseLock(String lockKey) {
        jedis.del(lockKey);  // 删除锁
    }

    // 模拟从数据库加载数据
    private String loadFromDB(String key) {
        System.out.println("Loading data from DB for key: " + key);
        return "DBValueFor" + key;
    }
}
4.3. 互斥锁的优点和缺点
  • 优点
      - 简单有效,确保在缓存失效时只有一个请求访问数据库,避免并发访问造成的数据库压力。
  • 缺点
      - 可能会出现锁等待时间过长的问题,特别是在加载数据耗时较多的场景中,其他请求需要等待锁释放。
5. 方案二:逻辑过期解决缓存击穿

逻辑过期是一种延长缓存有效期的方式。我们并不真正删除缓存,而是将缓存数据设置为逻辑过期状态。每次读取缓存时,仍然返回数据,但异步刷新缓存中的数据。

5.1. 基本流程
  1. 请求到达,读取缓存中的数据。
  2. 如果缓存中的数据未过期,直接返回。
  3. 如果缓存数据过期,异步从数据库更新缓存,但仍返回旧的缓存数据给当前请求。
5.2. 代码实现
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

public class LogicalExpireCacheService {

    private Jedis jedis;
    private ExecutorService executorService = Executors.newFixedThreadPool(10);

    public LogicalExpireCacheService(Jedis jedis) {
        this.jedis = jedis;
    }

    // 获取数据的方法,包含逻辑过期处理
    public String getData(String key) {
        // 尝试从 Redis 缓存中获取数据
        String cacheData = jedis.get(key);
        
        if (cacheData != null && !isExpired(cacheData)) {
            return cacheData;  // 如果缓存未过期,直接返回
        }

        // 如果缓存过期,异步更新缓存
        executorService.submit(() -> {
            String newValue = loadFromDB(key);
            jedis.set(key, newValue);
        });

        // 返回旧数据,避免直接击穿数据库
        return cacheData;
    }

    // 检查缓存数据是否过期(模拟逻辑过期)
    private boolean isExpired(String cacheData) {
        // 解析数据的过期标志,这里可以自定义逻辑
        return false;  // 简化示例,不真正实现
    }

    // 模拟从数据库加载数据
    private String loadFromDB(String key) {
        System.out.println("Loading data from DB for key: " + key);
        return "NewDBValueFor" + key;
    }
}
5.3. 逻辑过期的优点和缺点
  • 优点
      - 不会阻塞用户请求,哪怕缓存过期,用户仍能拿到旧的数据。
      - 适合对时效性要求不高的场景。

  • 缺点
      - 异步更新缓存的过程存在时间差,可能导致部分用户获取的是旧数据。
      - 数据一致性要求较高时需要谨慎使用。

6. 方案三:缓存预热

缓存预热指的是在缓存数据即将失效之前,主动更新缓存数据,避免缓存过期瞬间的大量并发请求击穿缓存。

6.1. 基本流程
  1. 定期提前刷新缓存,在缓存过期前将新数据写入缓存。
  2. 使用定时任务或后台线程进行缓存的预加载和刷新,确保热点数据始终在缓存中。
6.2. 实现方式

通过 Spring 的定时任务机制或其他调度工具,定期刷新热点数据的缓存。例如:

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

@Service
public class CachePreheatService {

    private Jedis jedis;

    public CachePreheatService(Jedis jedis) {
        this.jedis = jedis;
    }

    // 定时任务,每隔 5 分钟刷新缓存
    @Scheduled(fixedRate = 300000)
    public void

 refreshCache() {
        String key = "hotDataKey";
        String value = loadFromDB(key);
        jedis.set(key, value);
        System.out.println("Cache refreshed for key: " + key);
    }

    // 模拟从数据库加载数据
    private String loadFromDB(String key) {
        System.out.println("Loading data from DB for key: " + key);
        return "PreheatedDBValueFor" + key;
    }
}
6.3. 优点和缺点
  • 优点
      - 通过提前刷新缓存,避免缓存失效时的大量并发请求,确保热点数据始终存在缓存中。
  • 缺点
      - 需要额外的调度管理和计算热点数据,不能解决所有场景下的缓存击穿问题。
7. 总结

缓存击穿是高并发系统中一个常见且重要的问题。针对不同的业务场景,我们可以采取多种措施来应对缓存击穿,如互斥锁、逻辑过期、缓存预热等。

  • 互斥锁:确保缓存失效时只有一个线程能够访问数据库,适合数据一致性要求较高的场景。
  • 逻辑过期:返回旧缓存数据并异步更新缓存,适合对时效性要求不高的场景。
  • 缓存预热:提前刷新缓存,避免热点数据在缓存失效时被大量请求穿透。

这些策略可以结合使用,根据不同的业务场景和性能要求,选择最合适的方案,确保系统在高并发场景下的稳定性。

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

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

相关文章

基于ONNX-YOLOv10-Object-Detection项目实现yolov10模型onnx-python推理

项目地址:https://github.com/ibaiGorordo/ONNX-YOLOv10-Object-Detection 项目依赖:onnxruntime-gpu、opencv-python、imread-from-url、cap-from-youtube、ultralytics 1、代码修改 代码改动说明:yolov10/yolov10.py中的第18行修改为以下…

Docker部署tenine实现后端应用的高可用与负载均衡

采用Docker方式的Tengine 和 keepalived 组合模式可以实现小应用场景的高可用负载均衡需求 目录 网络架构一、环境准备二、软件安装1. 下载Tenine镜像2. 下载Keepalived镜像3. 制作SpringBoot镜像 三、软件配置1. 创建应用容器2. 代理访问应用3. 创建Keepalived4. 测试高可用 网…

基于YOLOv5的积水检测模型训练:从数据到模型的全面解析

之前给大家带来了Yololov5Pyqt5Opencv 实时城市积水报警系统, 详见: Yololov5Pyqt5Opencv 实时城市积水报警系统_yolo opencv pyqt5-CSDN博客 今天详细解析一下积水检测模型训练部分的内容 在积水检测项目中,实时性和准确性是至关重要的。…

STM32内部闪存FLASH(内部ROM)、IAP

1 FLASH简介 1 利用程序存储器的剩余空间来保存掉电不丢失的用户数据 2 通过在程序中编程(IAP)实现程序的自我更新 (OTA) 3在线编程(ICP把整个程序都更新掉) 1 系统的Bootloader写死了,只能用串口下载到指定的位置&a…

Spring Boot事务管理

事务管理 事务进阶 如果在删除了部门之后,出现了异常。那么就会出现部门被删除之后其中的员工并未被删除。 Transactional注解,在事务执行完成之后自动提交或者回滚。只需要在执行多次数据修改的事务上加上该注解即可。(比如两次Update或者…

嵌入式Openharmony系统构建与启动详解

大家好,今天主要给大家分享一下,如何构建Openharmony子系统以及系统的启动过程分解。 第一:OpenHarmony系统构建 首先熟悉一下,构建系统是一种自动化处理工具的集合,通过将源代码文件进行一系列处理,最终生成和用户可以使用的目标文件。这里的目标文件包括静态链接库文件…

基于51单片机的自动转向修复系统的设计与实现

文章目录 前言资料获取设计介绍功能介绍设计清单具体实现截图参考文献设计获取 前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师,一名热衷于单片机技术探索与分享的博主、专注于 精通51/STM32/MSP430/AVR等单片机设…

11.5.软件系统分析与设计-面向对象的程序设计与实现

面向对象的程序设计与实现 设计模式 Java代码 C代码

Android13_SystemUI下拉框新增音量控制条

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 Android13_SystemUI下拉框新增音量控制条 一、必备知识二、源码分析对比1.brightness模块分析对比2.statusbar/phone 对应模块对比对比初始化类声明对比构造方法 三、源码修改…

操作系统week1

操作系统学习 一.操作系统概述 1.概念、功能 操作系统是指控制和管理整个计算机系统的硬件和软件资源,并合理地组织调度计算机的工作和资源的分配,以提供给用户和其他软件方便的接口和环境 #mermaid-svg-SpFSwhrPg2GwVnYt {font-family:"trebuch…

【Python 千题 —— 算法篇】数字反转

Python 千题持续更新中 …… 脑图地址 👉:⭐https://twilight-fanyi.gitee.io/mind-map/Python千题.html⭐ 题目背景 整数反转是一个经典的算法问题,常见于各种编程竞赛和技术面试中。它要求我们将给定的整数按位进行翻转,并返…

c++ string类的模拟实现的注意事项

一.构造函数 第一种形式,使用字符指针赋值 为了防止修改,我们传入了常量字符串。但是这里的初始化列表出错了,因为_str是一个变量,将常量给到一个变量涉及到权限的放大,是错误的。那该怎么写呢?对_str的赋…

证书学习(四)X.509数字证书整理

目录 一、X.509证书 介绍1.1 什么是 X.509证书?1.2 什么是 X.509标准?1.3 什么是 PKI?二、X.509证书 工作原理2.1 PKI 的基础——加密算法2.2 PKI 证书编码三、X.509证书 结构3.1 证书字段3.2 证书扩展背景: 我们在日常的开发过程中,经常会遇到各种各样的电子证书文件,其…

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所…

虚拟机VMware桥接网络命令来重置 /etc/sysconfig/network-scripts/ifcfg-ens33 文件

ifcfg-ens33 文件专门用于配置这个特定的网络接口。 有时候把ifcfg-ens33文件配置弄乱了,可以使用命令重置。 最常用的方式是通过 nmcli 或者 nmtui 来重置网络接口的配置。 第一种方法. 使用 nmcli 命令重置网络配置 nmcli 是一个强大的网络管理命令行工具&…

闯关leetcode——3.Longest Substring Without Repeating Characters

大纲 题目地址内容 解题代码地址 题目 地址 https://leetcode.com/problems/longest-substring-without-repeating-characters/description/ 内容 Given a string s, find the length of the longest substring without repeating characters. Example 1: Input: s “abc…

力扣最热一百题——矩阵置零

目录 题目链接:73. 矩阵置零 - 力扣(LeetCode) 题目描述 示例 提示: 解法一:采用标记数组遍历处理 Java写法: C写法: 优化 解法二:优化解法之标记变量 Java写法:…

【鸿蒙HarmonyOS NEXT】UIAbility的生命周期

【鸿蒙HarmonyOS NEXT】UIAbility的生命周期 一、环境说明二、UIAbility的生命周期三、示例代码加以说明四、小结 一、环境说明 DevEco Studio 版本: API版本:以12为主 二、UIAbility的生命周期 概念: HarmonyOS中的UIAbility是一种包含…

入门pytorch

卷积神经网络模型 卷积神经网络(简称 CNN)是一种专为图像输入而设计的网络。它最明显的特征就是具有三个层次,卷积层,池化层,全连接层。 借用一张图,下图很好的表示了什么是卷积(提取特征&…

机器学习:多种算法处理填充后的数据

在机器学习中,填充数据(即处理缺失值)后,选择合适的算法并优化模型以提高召回率是一个常见的任务。召回率是指模型正确识别的正例占所有实际正例的比例。 代码思路: 数据预处理: 导入填充后的数据 …