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

Redis 缓存穿透、缓存击穿与缓存雪崩详解:问题、解决方案与最佳实践

目录

引言

1. 缓存穿透

1.1 什么是缓存穿透?

示例:

1.2 缓存穿透的原因

1.3 缓存穿透的解决方案

1.3.1 缓存空对象

1.3.2 布隆过滤器(Bloom Filter)

1.3.3 参数校验

2. 缓存击穿

2.1 什么是缓存击穿?

示例:

2.2 缓存击穿的原因

2.3 缓存击穿的解决方案

2.3.1 互斥锁(Mutex Lock)

2.3.2 永不过期 + 后台更新

2.3.3 缓存预热

3. 缓存雪崩

3.1 什么是缓存雪崩?

示例:

3.2 缓存雪崩的原因

3.3 缓存雪崩的解决方案

3.3.1 设置随机过期时间

3.3.2 多级缓存

3.3.3 限流与降级

4. 缓存穿透、缓存击穿与缓存雪崩的区别

5. 最佳实践

6. 总结


引言

在使用 Redis 作为缓存系统时,缓存穿透缓存击穿缓存雪崩是三个常见的问题。它们不仅会影响系统的性能,还可能导致数据库压力过大甚至系统崩溃。本文将深入探讨这三种问题的定义、原因、解决方案以及最佳实践,并通过 Java 代码示例 帮助读者全面理解并有效应对这些问题。


1. 缓存穿透

1.1 什么是缓存穿透?

缓存穿透是指查询一个 不存在的数据,导致请求直接穿透缓存层,直接访问数据库。由于数据库中也不存在该数据,因此每次请求都会绕过缓存,直接访问数据库,从而导致数据库压力过大。

示例:
  • 用户请求一个不存在的商品 ID,缓存中没有该数据,请求直接打到数据库。

  • 恶意攻击者故意请求大量不存在的数据,导致数据库压力激增。

1.2 缓存穿透的原因

  1. 恶意攻击:攻击者故意请求大量不存在的数据。

  2. 业务逻辑问题:业务代码未对请求参数进行校验,导致非法请求直接访问数据库。

1.3 缓存穿透的解决方案

1.3.1 缓存空对象

当查询数据库发现数据不存在时,将空结果(如 null)缓存到 Redis 中,并设置一个较短的过期时间。这样,后续相同的请求可以直接从缓存中获取空结果,避免直接访问数据库。

import redis.clients.jedis.Jedis;public class CachePenetration {private Jedis redis;private Database db;public CachePenetration(Jedis redis, Database db) {this.redis = redis;this.db = db;}public String getData(String key) {// 从缓存中获取数据String data = redis.get(key);if (data != null) {return "NULL".equals(data) ? null : data; // 返回空结果}// 从数据库中查询数据data = db.query(key);if (data == null) {redis.setex(key, 300, "NULL"); // 缓存空对象,过期时间 300 秒return null;}redis.setex(key, 3600, data); // 缓存真实数据,过期时间 1 小时return data;}
}
1.3.2 布隆过滤器(Bloom Filter)

布隆过滤器是一种概率型数据结构,用于快速判断一个元素是否存在于集合中。它可以有效过滤掉不存在的数据请求,避免缓存穿透。

  • 优点:内存占用少,查询效率高。

  • 缺点:存在一定的误判率(False Positive),但可以通过调整参数降低误判率。

import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import redis.clients.jedis.Jedis;public class CachePenetration {private Jedis redis;private Database db;private BloomFilter<String> bloomFilter;public CachePenetration(Jedis redis, Database db) {this.redis = redis;this.db = db;this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(), 1000000, 0.001);}public String getData(String key) {// 使用布隆过滤器判断 key 是否存在if (!bloomFilter.mightContain(key)) {return null; // 如果 key 不在布隆过滤器中,直接返回}// 从缓存中获取数据String data = redis.get(key);if (data != null) {return data;}// 从数据库中查询数据data = db.query(key);if (data == null) {redis.setex(key, 300, "NULL"); // 缓存空对象return null;}redis.setex(key, 3600, data); // 缓存真实数据return data;}
}
1.3.3 参数校验

在业务逻辑中对请求参数进行校验,过滤掉非法请求。例如,检查商品 ID 是否为正整数,或者是否符合某种格式。

public class CachePenetration {private Jedis redis;private Database db;public CachePenetration(Jedis redis, Database db) {this.redis = redis;this.db = db;}private boolean validateKey(String key) {try {int id = Integer.parseInt(key);return id > 0; // 检查 key 是否为正整数} catch (NumberFormatException e) {return false;}}public String getData(String key) {if (!validateKey(key)) {return null; // 非法请求直接返回}// 其他逻辑...return null;}
}

2. 缓存击穿

2.1 什么是缓存击穿?

缓存击穿是指 某个热点数据在缓存中过期,同时有大量并发请求访问该数据,导致所有请求直接访问数据库,从而导致数据库压力激增。

示例:
  • 某个热门商品的缓存过期,同时有大量用户请求该商品,导致数据库压力激增。

2.2 缓存击穿的原因

  1. 热点数据过期:某个热点数据的缓存过期。

  2. 高并发请求:大量并发请求同时访问该热点数据。

2.3 缓存击穿的解决方案

2.3.1 互斥锁(Mutex Lock)

在缓存失效时,使用互斥锁确保只有一个线程去加载数据,其他线程等待加载完成后再从缓存中获取数据。

import redis.clients.jedis.Jedis;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class CacheBreakdown {private Jedis redis;private Database db;private Lock lock = new ReentrantLock();public CacheBreakdown(Jedis redis, Database db) {this.redis = redis;this.db = db;}public String getData(String key) {// 从缓存中获取数据String data = redis.get(key);if (data != null) {return data;}// 尝试获取锁if (lock.tryLock()) {try {// 从数据库中查询数据data = db.query(key);if (data != null) {redis.setex(key, 3600, data); // 更新缓存}} finally {lock.unlock(); // 释放锁}return data;} else {try {Thread.sleep(100); // 等待其他线程加载数据} catch (InterruptedException e) {Thread.currentThread().interrupt();}return getData(key); // 重试}}
}
2.3.2 永不过期 + 后台更新

对于热点数据,可以设置缓存永不过期,并通过后台任务定期更新缓存。

import redis.clients.jedis.Jedis;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class CacheBreakdown {private Jedis redis;private Database db;public CacheBreakdown(Jedis redis, Database db) {this.redis = redis;this.db = db;// 启动后台任务定期更新缓存ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);scheduler.scheduleAtFixedRate(this::updateCache, 0, 1, TimeUnit.HOURS);}public String getData(String key) {// 从缓存中获取数据String data = redis.get(key);if (data != null) {return data;}// 从数据库中查询数据data = db.query(key);if (data != null) {redis.set(key, data); // 缓存永不过期}return data;}private void updateCache() {String hotData = db.queryHotData();redis.set("hot_data", hotData); // 更新缓存}
}
2.3.3 缓存预热

在系统启动或低峰期,提前加载热点数据到缓存中,避免缓存击穿。

import redis.clients.jedis.Jedis;public class CacheBreakdown {private Jedis redis;private Database db;public CacheBreakdown(Jedis redis, Database db) {this.redis = redis;this.db = db;preheatCache();}private void preheatCache() {String hotData = db.queryHotData();redis.set("hot_data", hotData); // 缓存预热}
}

3. 缓存雪崩

3.1 什么是缓存雪崩?

缓存雪崩是指 大量缓存数据在同一时间失效,导致大量请求直接访问数据库,从而导致数据库压力激增甚至崩溃。

示例:
  • 缓存中的数据设置了相同的过期时间,导致大量数据在同一时间失效。

  • Redis 实例宕机,导致所有缓存失效。

3.2 缓存雪崩的原因

  1. 缓存集中失效:缓存中的数据设置了相同的过期时间。

  2. Redis 实例宕机:Redis 服务不可用,导致所有缓存失效。

  3. 热点数据失效:某些热点数据的缓存失效,导致大量请求直接访问数据库。

3.3 缓存雪崩的解决方案

3.3.1 设置随机过期时间

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

import redis.clients.jedis.Jedis;
import java.util.Random;public class CacheAvalanche {private Jedis redis;private Database db;private Random random = new Random();public CacheAvalanche(Jedis redis, Database db) {this.redis = redis;this.db = db;}public void setCache(String key, String value) {int expireTime = 3600 + random.nextInt(600); // 过期时间在 1 小时到 1 小时 10 分钟之间redis.setex(key, expireTime, value);}
}
3.3.2 多级缓存

使用多级缓存架构(如本地缓存 + Redis 缓存),即使 Redis 缓存失效,本地缓存仍然可以提供服务。

import redis.clients.jedis.Jedis;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;public class CacheAvalanche {private Jedis redis;private Database db;private Map<String, String> localCache = new ConcurrentHashMap<>();public CacheAvalanche(Jedis redis, Database db) {this.redis = redis;this.db = db;}public String getData(String key) {// 先从本地缓存获取String data = localCache.get(key);if (data != null) {return data;}// 再从 Redis 缓存获取data = redis.get(key);if (data != null) {localCache.put(key, data); // 更新本地缓存return data;}// 最后从数据库获取data = db.query(key);if (data != null) {redis.setex(key, 3600, data); // 更新 Redis 缓存localCache.put(key, data); // 更新本地缓存}return data;}
}
3.3.3 限流与降级

在缓存雪崩发生时,通过限流和降级机制保护数据库。例如,使用限流工具(如 Redis 的 INCR 命令)限制请求速率,或者返回默认值或错误页面。

import redis.clients.jedis.Jedis;public class CacheAvalanche {private Jedis redis;private Database db;public CacheAvalanche(Jedis redis, Database db) {this.redis = redis;this.db = db;}public String getData(String key) {// 限流:每秒最多处理 100 个请求if (redis.incr("request_rate") > 100) {return "Too many requests, please try again later.";}// 其他逻辑...return null;}
}

4. 缓存穿透、缓存击穿与缓存雪崩的区别

特性缓存穿透缓存击穿缓存雪崩
定义查询不存在的数据,导致请求直接访问数据库热点数据缓存失效,导致大量请求直接访问数据库大量缓存数据在同一时间失效,导致请求直接访问数据库
原因恶意攻击或业务逻辑问题热点数据过期或高并发请求缓存集中失效或 Redis 实例宕机
影响数据库压力过大数据库压力激增数据库压力激增甚至崩溃
解决方案缓存空对象、布隆过滤器、参数校验互斥锁、永不过期 + 后台更新、缓存预热设置随机过期时间、多级缓存、限流与降级

5. 最佳实践

  1. 合理设置缓存过期时间:避免缓存集中失效。

  2. 使用布隆过滤器:有效防止缓存穿透。

  3. 多级缓存架构:提高系统的容错能力。

  4. 限流与降级机制:保护数据库不被压垮。

  5. 监控与报警:实时监控缓存命中率和数据库负载,及时发现并解决问题。


6. 总结

缓存穿透、缓存击穿和缓存雪崩是 Redis 使用过程中常见的问题,它们会导致数据库压力过大甚至系统崩溃。通过合理的设计和优化,可以有效避免这些问题:

  • 缓存穿透:通过缓存空对象、布隆过滤器和参数校验来解决。

  • 缓存击穿:通过互斥锁、永不过期 + 后台更新和缓存预热来解决。

  • 缓存雪崩:通过设置随机过期时间、多级缓存和限流降级来解决。

相关文章:

Redis 缓存穿透、缓存击穿与缓存雪崩详解:问题、解决方案与最佳实践

目录 引言 1. 缓存穿透 1.1 什么是缓存穿透&#xff1f; 示例&#xff1a; 1.2 缓存穿透的原因 1.3 缓存穿透的解决方案 1.3.1 缓存空对象 1.3.2 布隆过滤器&#xff08;Bloom Filter&#xff09; 1.3.3 参数校验 2. 缓存击穿 2.1 什么是缓存击穿&#xff1f; 示例&…...

Mamba| Miniforge3 安装和配置

参考教程&#xff1a; B站 教程概要 安装最新的 Mamba&#xff0c;建议通过安装 Miniforge 来实现&#xff0c;因为 Miniforge 默认包含 Mamba。Miniforge 下载&#xff1a;建议使用南京大学镜像站mamba 设置镜像源&#xff1a;清华镜像源修改默认环境安装路径设置 pip 镜像&a…...

Qt入门笔记

目录 一、前言 二、创建Qt项目 2.1、使用向导创建 2.2、最简单的Qt应用程序 2.2.1、main函数 2.2.2、widget.h文件 2.2.3、widget.cpp文件 2.3、Qt按键Botton 2.3.1、创建一个Botton 2.3.2、信号与槽 2.3.3、按键使用信号与槽的方法 2.4、文件Read与Write-QFile类 2…...

C语言每日一练——day_4

引言 针对初学者&#xff0c;每日练习几个题&#xff0c;快速上手C语言。第四天。&#xff08;连续更新中&#xff09; 采用在线OJ的形式 什么是在线OJ&#xff1f; 在线判题系统&#xff08;英语&#xff1a;Online Judge&#xff0c;缩写OJ&#xff09;是一种在编程竞赛中用…...

下降路径最⼩和(medium)

题目描述&#xff1a; 给你一个 n x n 的 方形 整数数组 matrix &#xff0c;请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降路径 可以从第一行中的任何元素开始&#xff0c;并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列&#xff08…...

redux_旧版本

reduxjs/toolkit&#xff08;RTK&#xff09;是 Redux 官方团队推出的一个工具集&#xff0c;旨在简化 Redux 的使用和配置。它于 2019 年 10 月 正式发布&#xff0c;此文章记录一下redux的旧版本如何使用&#xff0c;以及引入等等。 文件目录如下&#xff1a; 步骤 安装依…...

⭐算法OJ⭐经典题目分类索引(持续更新)

在编程竞赛和算法学习中&#xff0c;Online Judge&#xff08;OJ&#xff09;平台是程序员们磨练技能的重要工具。OJ平台上的题目种类繁多&#xff0c;涵盖了从基础数据结构到复杂算法的各个方面。为了更好地理解和掌握这些题目&#xff0c;对其进行分类是非常有必要的。这篇索…...

python之使用scapy扫描本机局域网主机,输出IP/MAC表

安装scapy库 pip install scapy -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple扫描本机局域网的所有主机&#xff0c;输出IP/MAC对于表 # -*- coding: UTF-8 -*- import netifaces from scapy.all import srp from scapy.layers.l2 import ARP, Ether import ipa…...

Spring Boot中@Valid 与 @Validated 注解的详解

Spring Boot中Valid 与 Validated 注解的详解 引言 在Spring Boot应用中&#xff0c;参数校验是确保数据完整性和一致性的重要手段。Valid和Validated注解是Spring Boot中用于参数校验的两个核心注解。本文将详细介绍这两个注解的用法、区别以及代码样例。 Valid注解 功能介…...

18、TCP连接三次握手的过程,为什么是三次,可以是两次或者更多吗【高频】

三次握手的过程&#xff1a; 第一次握手&#xff1a;客户端 向 服务器 发送一个 SYN&#xff08;也就是同步序列编号报文&#xff09;&#xff0c;请求建立连接。随后&#xff0c;客户端 进入 SYN_SENT 状态&#xff1b;服务器收到 SYN 之后&#xff0c;由 LISTEN 状态变为 SYN…...

Ceph(2):Ceph简介

1 Ceph简介 Ceph使用C语言开发&#xff0c;遵循LGPL协议开源。Sage Weil(Ceph论文发表者)于2011年创立了以Inktank公司主导Ceph的开发和社区维护。2014年Redhat收购inktank公司&#xff0c;并发布Inktank Ceph企业版&#xff08;ICE&#xff09;软件&#xff0c;业务场景聚焦云…...

第二篇:CTF常见题型解析:密码学、逆向工程、漏洞利用、Web安全

# 零基础小白入门CTF解题到成为CTF大佬系列文章 ## 第二篇&#xff1a;CTF常见题型解析&#xff1a;密码学、逆向工程、漏洞利用、Web安全 ### 引言 在CTF比赛中&#xff0c;题目类型多种多样&#xff0c;涵盖了网络安全领域的多个方向。掌握这些题型的解题方法&#xff0c;…...

《Python基础教程》附录B笔记:Python参考手册

《Python基础教程》第1章笔记&#x1f449;https://blog.csdn.net/holeer/article/details/143052930 附录B Python参考手册 Python标准文档是完整的参考手册。本附录只是一个便利的速查表&#xff0c;当你开始使用Python进行编程后&#xff0c;它可帮助你唤醒记忆。 B.1 表…...

linux学习(十三)(shell编程(文字,变量,循环,条件,调试))

Shell 编程 Shell 编程&#xff0c;也称为 shell 脚本&#xff0c;是 Linux作系统不可或缺的一部分。shell 脚本实质上是系统 shell 执行的程序。虽然它可能不如 C 或 C 等编译语言强大&#xff0c;但 shell 编程对于管理级任务、自动执行重复性任务和系统监控非常有效。 大多…...

大模型微调中warmup(学习率预热)是什么

大模型微调中warmup(学习率预热)是什么 在大模型微调中,添加warmup(学习率预热)是指在训练初期逐步增加学习率,避免直接使用高学习率导致参数震荡。 🔧 为什么需要warmup? 大模型参数敏感:预训练模型的参数已接近最优,初期用大学习率可能剧烈扰动参数(如“急刹车…...

wireshark 如何关闭混杂模式 wireshark操作

Fiddler和Wireshark都是进行抓包的工具&#xff1a;所谓抓包就是将网络传输发送与接收的数据包进行截获、重发、编辑、转存等操作&#xff0c;也用来检查网络安全。抓包也经常被用来进行数据截取等。黑客常常会用抓包软件获取你非加密的上网数据&#xff0c;然后通过分析&#…...

ChatGPT4.5详细介绍和API调用详细教程

OpenAI在2月27日发布GPT-4.5的研究预览版——这是迄今为止OpenAI最强大、最出色的聊天模型。GPT-4.5在扩大预训练和微调规模方面迈出了重要的一步。通过扩大无监督学习的规模&#xff0c;GPT-4.5提升了识别内容中的模式、建立内容关联和生成对于内容的见解的能力&#xff0c;但…...

centos linux安装mysql8 重置密码 远程连接

1. 下载并安装 MySQL Yum 仓库 从 MySQL 官方网站下载并安装 Yum 仓库配置文件。 # 下载MySQL 8.0的Yum仓库包 wget https://dev.mysql.com/get/mysql80-community-release-el7-5.noarch.rpm # 安装Yum仓库包 sudo rpm -ivh mysql80-community-release-el7-5.noarch.rpm2. 启…...

AWS 如何导入内部SSL 证书

SSL 证书的很重要的功能就是 HTTP- > HTTPS, 下面就说明一下怎么导入ssl 证书,然后绑定证书到ALB. 以下示例说明如何使用 AWS Management Console 导入证书。 从以下位置打开 ACM 控制台:https://console.aws.amazon.com/acm/home。如果您是首次使用 ACM,请查找 AWS Cer…...

Unity DOTS从入门到精通之 自定义Authoring类

文章目录 前言安装 DOTS 包什么是Authoring1. 实体组件2. Authoring类 前言 DOTS&#xff08;面向数据的技术堆栈&#xff09;是一套由 Unity 提供支持的技术&#xff0c;用于提供高性能游戏开发解决方案&#xff0c;特别适合需要处理大量数据的游戏&#xff0c;例如大型开放世…...

一键换肤的Qt-Advanced-Stylesheets

项目简介 能在软件运行时对 CSS 样式表主题&#xff08;包括 SVG 资源和 SVG 图标&#xff09;进行实时颜色切换的Qt项目。 项目预览&#xff1a; 项目地址 地址&#xff1a;Qt-Advanced-Stylesheets 本地编译环境 Win11 家庭中文版 Qt5.15.2 (MSVC2019) Qt Creator1…...

golang 静态库 Undefined symbol: __mingw_vfprintf

正常用golang编译一个静态库给 其他语言 调用&#xff0c;编译时报错 Error: Undefined symbol: __mingw_vfprintf 很是奇怪&#xff0c;之前用用golang写静态库成功过&#xff0c;编译也没问题&#xff0c;结果却是截然不同。 试了很多次&#xff0c;发现唯一的差别就是在 …...

宝塔的ssl文件验证域名后,会在域名解析列表中留下记录吗?

在使用宝塔面板进行SSL证书验证域名后&#xff0c;通常不会在域名解析列表中留下记录。验证过程中添加的TXT记录仅用于验证域名的所有权&#xff0c;一旦验证完成&#xff0c;就可以安全地删除这些记录&#xff0c;不会影响SSL证书的正常使用。根据搜索结果&#xff0c;DNS验证…...

Linux 网络:skb 数据管理

文章目录 1. 前言2. skb 数据管理2.1 初始化2.2 数据的插入2.2.1 在头部插入数据2.2.2 在尾部插入数据 2.2 数据的移除 3. 小结 1. 前言 限于作者能力水平&#xff0c;本文可能存在谬误&#xff0c;因此而给读者带来的损失&#xff0c;作者不做任何承诺。 2. skb 数据管理 数…...

wireguard搭配udp2raw部署内网

前言 上一篇写了使用 wireguard 可以非常轻松的进行组网部署&#xff0c;但是如果服务器厂商屏蔽了 udp 端口&#xff0c;那就没法了 针对 udp 被服务器厂商屏蔽的情况&#xff0c;需要使用一款 udp2raw 或 socat 类似的工具&#xff0c;来将 udp 打包成 tcp 进行通信 这里以…...

Qwen/QwQ-32B 基础模型上构建agent实现ppt自动生成

关心Qwen/QwQ-32B 性能测试结果可以参考下 https://zhuanlan.zhihu.com/p/28600079208https://zhuanlan.zhihu.com/p/28600079208 官方宣传上是该模型性能比肩满血版 DeepSeek-R1&#xff08;671B&#xff09;&#xff01; 我们实现一个 使用Qwen/QwQ-32B 自动生成 PowerPoi…...

【Linux】使用问题汇总

#1 ssh连接的时候报Key exchange failed 原因&#xff1a;服务端版本高&#xff0c;抛弃了一些不安全的交换密钥算法&#xff0c;且客户端版本比较旧&#xff0c;不支持安全性较高的密钥交换算法。 解决方案&#xff1a; 如果是内网应用&#xff0c;安全要求不这么高&#xf…...

PostgreSQL17(最新版)安装部署

PostgreSQL 17已与2024年9月26日正式发布&#xff01;&#xff01;&#xff01; 一、Postgres概述 官网地址&#xff1a;PostgreSQL: The world’s most advanced open source database Postgres作为最先进的开源数据库&#xff08; the latest version of the world’s most…...

【AI大模型智能应用】Deepseek生成测试用例

在软件开发过程中&#xff0c;测试用例的设计和编写是确保软件质量的关键。 然而&#xff0c;软件系统的复杂性不断增加&#xff0c;手动编写测试用例的工作量变得异常庞大&#xff0c;且容易出错。 DeepSeek基于人工智能和机器学习&#xff0c;它能够依据软件的需求和设计文…...

【高级篇】大疆Pocket 3加ENC编码器实现无线RTMP转HDMI进导播台

【高级篇】大疆Pocket 3加ENC编码器实现无线RTMP转HDMI进导播台 文章目录 准备工作连接设备RTMP概念ENCSHV2推流地址设置大疆Pocket 3直播设置总结 老铁们好&#xff01; 很久没写软文了&#xff0c;今天给大家带了一个干货&#xff0c;如上图&#xff0c;大疆Pocket 3加ENC编…...