首页 > 其他分享 >缓存雪崩及解决办法

缓存雪崩及解决办法

时间:2025-01-01 20:58:34浏览次数:3  
标签:解决办法 缓存 String value jedis 雪崩 key static

缓存雪崩是指在缓存系统中,由于大量缓存数据在同一时间失效,导致大量原本可以直接从缓存中获取数据的请求直接转向数据库或后端服务,从而给数据库或后端服务带来巨大的压力,甚至可能导致系统崩溃的一种现象。

以下是缓存雪崩的一些特点和影响:

一、产生原因

  • 过期时间设置不合理
    • 通常是由于将大量缓存的过期时间设置为相同,在某个时刻,这些缓存同时过期,导致大量请求无法从缓存中获取数据,全部涌向数据库。例如,许多业务在每天凌晨统一更新缓存,并且将过期时间设置为 24 小时,那么在第二天凌晨更新缓存时,前一天的缓存会同时失效。
    • 大量缓存同时过期会使系统的缓存命中率急剧下降,原本可以通过缓存快速响应的请求都需要后端系统重新计算或从数据库读取数据,导致数据库的负载瞬间增加。
  • 缓存服务故障
    • 缓存服务可能因为硬件故障、网络问题或软件问题而不可用,此时也会导致所有请求都直接穿透到后端,产生类似于缓存雪崩的效果。例如,Redis 集群的主节点突然故障,从节点还未完成切换,或者 Redis 服务器所在的网络出现分区故障,导致缓存服务无法正常提供服务。

二、影响

  • 系统性能下降
    • 数据库或后端服务需要处理超出其处理能力的大量请求,导致响应时间变长,服务质量下降。用户可能会感受到页面加载缓慢、服务响应延迟,甚至出现请求超时的情况。
    • 数据库的性能指标,如 CPU 使用率、内存使用率、I/O 负载等会急剧上升,可能会出现数据库连接池耗尽,无法处理新的连接请求。
  • 系统崩溃风险
    • 极端情况下,如果后端服务无法承受巨大的压力,可能会导致服务崩溃,进而影响整个系统的可用性。例如,对于电商平台,可能会导致用户无法查看商品、无法下单;对于社交平台,可能会导致用户无法刷新消息、无法发送消息等

以下是解决缓存雪崩的一些常见做法及相应的代码示例:

一、随机 TTL

通过为缓存设置随机的过期时间,避免大量缓存同时失效。

java

import java.util.Random;
import java.util.concurrent.TimeUnit;
import redis.clients.jedis.Jedis;

public class CacheExample {
    private static final Jedis jedis = new Jedis("localhost", 6379);
    private static final Random random = new Random();

    public static void main(String[] args) {
        String key = "example_key";
        String value = "example_value";
        // 基础的过期时间范围,单位为秒
        int baseTTL = 3600; 
        // 随机过期时间范围,单位为秒
        int randomTTLRange = 300; 
        int finalTTL = baseTTL + random.nextInt(randomTTLRange);
        jedis.setex(key, finalTTL, value);
    }
}

代码解释:

  • 上述 Java 代码使用 Jedis 客户端连接 Redis。
  • 首先定义了一个基础的过期时间 baseTTL 为 3600 秒(1 小时)。
  • 然后定义了一个随机的过期时间范围 randomTTLRange 为 300 秒(5 分钟)。
  • 最终的过期时间 finalTTL 是基础过期时间加上一个 0 到 randomTTLRange 之间的随机数,通过 random.nextInt(randomTTLRange) 生成。
  • 使用 jedis.setex(key, finalTTL, value) 方法将键值对存储到 Redis 中并设置最终的过期时间。

二、多级缓存

使用多级缓存,例如本地缓存(如 Guava Cache)和远程缓存(如 Redis),先查询本地缓存,如果本地缓存未命中,再查询远程缓存,更新本地缓存。

java

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import redis.clients.jedis.Jedis;
import java.util.concurrent.TimeUnit;

public class MultiLevelCacheExample {
    private static final Jedis jedis = new Jedis("localhost", 6379);
    // 本地缓存,Guava Cache
    private static final Cache<String, String> localCache = CacheBuilder.newBuilder()
          .maximumSize(1000)
          .expireAfterWrite(10, TimeUnit.MINUTES)
          .build();

    public static String getValue(String key) {
        // 先从本地缓存中查找
        String value = localCache.getIfPresent(key);
        if (value == null) {
            // 本地缓存未命中,从 Redis 中查找
            value = jedis.get(key);
            if (value!= null) {
                // 将 Redis 中的值更新到本地缓存
                localCache.put(key, value);
            }
        }
        return value;
    }

    public static void main(String[] args) {
        String key = "example_key";
        String value = getValue(key);
        if (value == null) {
            // 缓存未命中,从数据库或其他数据源获取数据
            value = "example_value";
            // 将数据存储到 Redis
            jedis.setex(key, 3600, value);
            // 将数据存储到本地缓存
            localCache.put(key, value);
        }
    }
}

代码解释:

  • 首先使用 Guava Cache 创建了一个本地缓存 localCache,最大容量为 1000 条,写入 10 分钟后过期。
  • 在 getValue 方法中,先尝试从本地缓存中获取数据。
  • 如果本地缓存未命中,则从 Redis 中查找。
  • 如果 Redis 中找到数据,将数据更新到本地缓存中。
  • 在 main 方法中,如果缓存都未命中,从数据库或其他数据源获取数据,将数据存储到 Redis 和本地缓存。

三、加锁机制

在缓存失效时,使用锁机制,只允许一个线程去更新缓存,其他线程等待,避免大量请求同时穿透到数据库。

java

import redis.clients.jedis.Jedis;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class CacheLockExample {
    private static final Jedis jedis = new Jedis("localhost", 6379);
    private static final Lock lock = new ReentrantLock();

    public static String getValue(String key) {
        String value = jedis.get(key);
        if (value == null) {
            lock.lock();
            try {
                // 再次检查缓存是否已被更新
                value = jedis.get(key);
                if (value == null) {
                    // 从数据库或其他数据源获取数据
                    value = "example_value"; 
                    // 设置缓存过期时间
                    jedis.setex(key, 3600, value); 
                }
            } finally {
                lock.unlock();
            }
        }
        return value;
    }

    public static void main(String[] args) {
        String key = "example_key";
        String value = getValue(key);
    }
}

代码解释:

  • 定义了一个 ReentrantLock 锁。
  • 当缓存未命中时,使用 lock.lock() 加锁。
  • 再次检查缓存是否已被其他线程更新,若未更新,从数据库或其他数据源获取数据并更新缓存。
  • 最后使用 lock.unlock() 释放锁。

四、缓存预热

在系统启动时,将热点数据提前加载到缓存中。

java

import redis.clients.jedis.Jedis;

public class CachePreheatExample {
    private static final Jedis jedis = new Jedis("localhost", 6379);

    public static void preheatCache() {
        // 假设这些是热点数据的键
        String[] keys = {"key1", "key2", "key3"};
        for (String key : keys) {
            // 从数据库或其他数据源获取数据
            String value = "value_" + key; 
            // 将数据存储到 Redis
            jedis.setex(key, 3600, value); 
        }
    }

    public static void main(String[] args) {
        preheatCache();
    }
}

代码解释:

  • preheatCache 方法中,定义了一些热点数据的键。
  • 对于每个键,从数据库或其他数据源获取数据并存储到 Redis 中,设置过期时间为 3600 秒。

五、限流降级

当缓存失效时,对请求进行限流,避免大量请求同时穿透到数据库,同时可以考虑降级策略,返回一些默认数据。

java

import redis.clients.jedis.Jedis;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Semaphore;

public class CacheThrottlingExample {
    private static final Jedis jedis = new Jedis("localhost", 6379);
    private static final Semaphore semaphore = new Semaphore(10); // 允许 10 个并发请求

    public static String getValue(String key) {
        String value = jedis.get(key);
        if (value == null) {
            try {
                if (semaphore.tryAcquire(100, TimeUnit.MILLISECONDS)) {
                    try {
                        // 从数据库或其他数据源获取数据
                        value = "example_value"; 
                        // 设置缓存过期时间
                        jedis.setex(key, 3600, value); 
                    } finally {
                        semaphore.release();
                    }
                } else {
                    // 限流时,返回默认数据或错误信息
                    value = "default_value"; 
                }
            } catch (InterruptedException e) {
                // 处理异常
                e.printStackTrace();
            }
        }
        return value;
    }

    public static void main(String[] args) {
        String key = "example_key";
        String value = getValue(key);
    }
}

代码解释:

  • 使用 Semaphore 进行限流,允许 10 个并发请求。
  • 当缓存未命中时,尝试获取信号量,如果获取成功,从数据库或其他数据源获取数据并更新缓存,然后释放信号量。
  • 如果无法获取信号量,返回默认数据或错误信息。

通过上述几种方法,可以有效地缓解缓存雪崩问题,具体选择哪种方法或组合多种方法使用,需要根据实际的系统架构和业务需求来决定。不同的场景下,不同的方案会有不同的效果,需要在实践中进行测试和调整。

标签:解决办法,缓存,String,value,jedis,雪崩,key,static
From: https://blog.csdn.net/ksidgx/article/details/144833791

相关文章

  • 为什么需要浏览器缓存?
    浏览器缓存对于前端开发来说非常重要,主要有以下几个原因:提高加载速度:当用户再次访问已经访问过的页面时,如果页面内容没有变化,浏览器可以直接从本地缓存中加载资源,而不需要从服务器重新下载。这大大减少了网络请求的时间和带宽消耗,从而提高了页面的加载速度。减轻服务器压力:......
  • 由 Mybatis 源码畅谈软件设计(七):从根上理解 Mybatis 一级缓存
    作者:京东保险王奕龙本篇我们来讲一级缓存,重点关注它的实现原理:何时生效、生效范围和何时失效,在未来设计缓存使用时,提供一些借鉴和参考。1.准备工作定义实体publicclassDepartment{publicDepartment(Stringid){this.id=id;}privateStri......
  • LVGL-C 实现一个文件持久性缓存,用于存储配置相关数据
    实现功能描述:基于linux嵌入式平台开发App,在未移植数据库的情况下,文件存储是一个不错的持久性数据存储手段。创建代码文件:configcache.hconfigcache.c1.数据结构的结构体封装点击查看代码//配置信息结构typedefstruct{char*key;char*v......
  • mosdns 和 smartdns 都是常用的 DNS 代理工具,主要用于提高 DNS 查询的速度和效率,支持
    mosdns和smartdns都是常用的DNS代理工具,主要用于提高DNS查询的速度和效率,支持智能DNS查询、DNS缓存等功能。下面是这两个DNS工具的对比表格,帮助了解它们的区别和特点:功能/特性mosdnssmartdns主要功能提供快速的DNS查询服务,支持DNS缓存、加速等功能提......
  • nuxt 添加 redis 缓存
    这个文章的主要目的是通过redis缓存nuxt2中服务端渲染的页面。从而优化加载速度以及减轻服务端的压力。Nuxt是什么Nuxt.js是一个基于Vue.js的开源框架,旨在为开发者提供一个简单的方式来构建高性能的Vue应用。它提供了许多功能,使得开发服务器端渲染(SSR)、静态站点生成......
  • 网站注册及登录功能异常的原因及解决办法
    问题描述:用户反馈其网站的新用户注册、密码修改以及第三方账号登录等功能均出现了故障,尝试多次均未能成功完成相应操作。这不仅影响了用户体验,也可能阻碍了新用户的加入。请问造成这种情况的原因可能有哪些?应该如何有效地解决这个问题?解决方案:当网站的注册及登录功能出现问题时......
  • 2024-12-3《利用ffmpeg推流到rtsp,再利用jmpeg在html界面上显示的解决办法》
    利用ffmpeg推流到rtsp,再利用jmpeg在html界面上显示的解决办法  目录需求在python代码里推流到rtsphtml里播放rtsp视频流 需求最近在百度飞桨上训练了一个摔倒识别的模型,用的PaddleDetection这个模型,训练好以后我部署到了Windows,但是我看大多数人都是部署到了......
  • 网站图片上传不显示的原因及解决办法
    网站图片上传后无法显示是一个常见的问题,尤其是在涉及到服务器迁移或环境变化的情况下。根据您的描述,图片上传过程中出现“目录创建失败”的提示,这表明问题可能出现在服务器端而非前端代码层面。下面我们将详细探讨可能导致此类问题的各种原因,并提供相应的解决方案,帮助您快速恢复......
  • 本地更新正常但上传空间后无法更新缓存
    当您在本地环境中能够顺利地更新网站后台并保存更改,但在上传至虚拟主机后却遇到了无法更新缓存的问题时,这通常意味着存在某些配置差异或环境兼容性问题。以下是一些常见的原因及解决方案,帮助您更好地理解和处理这种情况:服务器环境差异:PHP版本不匹配:不同服务器可能运行着不同......
  • DevEco Studio使用模拟器报错,解决办法
    在进行鸿蒙应用开发时,很多开发者没有鸿蒙系统的设备,无法在鸿蒙系统真机上进行调试,这时就需要使用官方提供的模拟器。在菜单栏Tools---DeviceManager,打开模拟器弹窗,登录华为账号后即可进行模拟器管理。但在模拟器下载后,点击右侧Actions启动时,会遇到提示报错“模拟器启动失败,当前......