当前位置: 首页 > article >正文

Redis篇--常见问题篇2--缓存雪崩(过期时间分散,缓存预热,多级缓存)

1、概述

缓存雪崩是指在短时间内,大量的缓存同时失效或过期,导致大量的请求穿透到后端数据库或服务,从而引发系统负载骤增,甚至可能导致系统崩溃。这种情况通常发生在缓存的过期时间设置不合理时,所有缓存的过期时间相同,导致它们在同一时间点失效。

示意图:
在这里插入图片描述

2、解决方案

(1)、设置不同的过期时间(推荐)

为每个缓存项设置一个 随机的过期时间,而不是统一的过期时间。这样可以避免所有缓存项在同一时间点失效,分散了缓存失效的时间窗口,减少了对数据库的压力。
示例:

 // 设置随机化的 TTL
  int baseTtl = 60; // 基础 TTL 为 60 秒
  int randomOffset = new Random().nextInt(20); // 随机偏移0-19秒
  redisTemplate.opsForValue().set("key", "value", baseTtl + randomOffset, TimeUnit.SECONDS);

(2)、缓存预热

在系统启动时或定期进行缓存预热,提前加载或有策略的定期加载一些常用的数据库数据到缓存中,确保缓存中有足够的数据,减少缓存失效时的冲击。

(3)、多级缓存

使用多级缓存策略,例如:本地缓存+分布式缓存。当分布式缓存失效时,本地缓存可以继续提供服务,减少对数据库的直接访问。
应用:本地缓存(如Caffeine、Guava Cache)来缓存频繁访问的数据,同时使用分布式缓存(如Redis、Memcached)来存储全局共享的数据。
本地缓存的TTL可以设置得比分布式缓存更短,以减少对分布式缓存的依赖。

多级缓存示例(Caffine+Redis)
第一步:导入依赖

<dependency>
   <groupId>com.github.ben-manes.caffeine</groupId>
   <artifactId>caffeine</artifactId>
   <version>2.9.0</version>
</dependency>

第二步:注入配置类

import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public Cache<String,Object> caffeineCache() {    // 初始化注入CaffeineCache到容器
        return Caffeine.newBuilder()
                .initialCapacity(100)  // 初始容量为100
                .maximumSize(1000)   // 最大存1000个key
                .expireAfterWrite(1, TimeUnit.MINUTES)   // 设置1分钟过期
                .build();
    }
}

第三步:测试类

import com.github.benmanes.caffeine.cache.Cache;
import com.zw.base.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping(value = "redis3", method = {RequestMethod.POST, RequestMethod.GET})
public class RedisController3 extends BaseController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // 本地缓存CaffeineCache
    @Resource
    private Cache<String, Object> caffeineCache;

    // 模拟数据库查询
    private Object queryFromDatabase(String key) {
        // 模拟数据库查询逻辑
        System.out.println("Querying from database for key: " + key);
        return null; // 假设数据库中没有该数据
    }

    /**
     * 获取数据,优先从本地缓存获取,其次从 Redis 获取,最后从数据库获取
     * @param key 数据的唯一标识
     * @return 数据
     */
    @RequestMapping("/get")
    public Object getTest(String key) {
        // 1. 尝试从本地缓存获取
        Object cachedData = caffeineCache.getIfPresent(key);
        if (cachedData != null) {
            if ("NULL".equals(cachedData)) {
                return null; // 返回空对象
            }
            return cachedData;
        }

        // 2. 尝试从 Redis 获取
        cachedData = redisTemplate.opsForValue().get(key);
        if (cachedData != null) {
            if ("NULL".equals(cachedData)) {
                caffeineCache.put(key, "NULL"); // 将空对象放入本地缓存
                return null;
            }
            caffeineCache.put(key, cachedData); // 将数据放入本地缓存
            return cachedData;
        }

        // 3. 从数据库获取数据
        Object data = queryFromDatabase(key);
        if (data != null) {
            if (data != null) {
                // 更新本地缓存和 Redis
                caffeineCache.put(key, data.toString());
                redisTemplate.opsForValue().set(key, data, getRandomTtl(), TimeUnit.SECONDS);
            } else {
                // 缓存空对象
                caffeineCache.put(key, "NULL");
                redisTemplate.opsForValue().set(key, "NULL", 60, TimeUnit.SECONDS); // 空对象过期时间为 60 秒
            }
        }

        // 4. 返回旧的缓存数据(如果有)
        return caffeineCache.getIfPresent(key);
    }

    /**
     * 获取随机化的 TTL,避免所有缓存项在同一时间点失效
     *
     * @return 随机化的 TTL(秒)
     */
    private long getRandomTtl() {
        int baseTtl = 60; // 基础 TTL 为 60 秒
        int randomOffset = (int) (Math.random() * 10); // 随机偏移 0-9 秒
        return baseTtl + randomOffset;
    }
}

第四步:测试验证
可以正常查询有的值
在这里插入图片描述
没有的key直接返回null
在这里插入图片描述


http://www.kler.cn/a/448859.html

相关文章:

  • Linux 基本使用和程序部署
  • 【数据科学导论】第一二章·大数据与数据表示与存储
  • UE5仿漫威争锋灵蝶冲刺技能
  • AOP 面向切面编程的实现原理
  • gitlab代码推送
  • 叉车作业如何确认安全距离——UWB测距防撞系统的应用
  • Docker基础命令实战
  • whisper实时语音转文字
  • Java中使用四叶天动态代理IP构建ip代理池,实现httpClient和Jsoup代理ip爬虫
  • 梳理你的思路(从OOP到架构设计)_设计模式Template Method模式
  • Vue(二)
  • MATLAB绘图基础12:地理信息可视化
  • 1222面经
  • 【Go】Go数据类型详解—指针
  • LeetCode 26. 删除有序数组中的重复项 (C++实现)
  • 工具环境 | 工具准备
  • SSM 架构 Vue 赋能:WEB 开放性实验室智能管理系统
  • harmony UI组件学习(1)
  • 实验13 C语言连接和操作MySQL数据库
  • springboot453工资信息管理系统(论文+源码)_kaic
  • 解决Vmware虚拟机系统镜像无法解析DNS
  • 【Java基础面试题034】Java泛型擦除是什么?
  • 基于大语言模型的多代理下一代制造系统能灵活动态管理制造资源的高效调度方法
  • 一种统计torch内部计算过程算子输入输出信息的方法
  • (css)鼠标移入或点击改变背景图片
  • windows 下使用WLS2 编译aosp Android14并刷机到pixle 5a