文章目录

  • 缓存三大问题详解与工业级解决方案
    • 概念总览
    • 问题详解
      • 1. 缓存穿透 (Cache Penetration)
        • 问题描述
        • 典型场景
        • 危害
      • 2. 缓存击穿 (Cache Breakdown)
        • 问题描述
        • 典型场景
        • 危害
      • 3. 缓存雪崩 (Cache Avalanche)
        • 问题描述
        • 典型场景
        • 危害
    • 工业级解决方案
      • 缓存穿透解决方案
        • 方案1: 布隆过滤器
        • 方案2: 空值缓存
        • 方案3: 参数校验
        • 方案4: 综合方案 (推荐)
      • 缓存击穿解决方案
        • 方案1: 分布式锁
        • 方案2: 本地锁
        • 方案3: 热点数据预热
        • 方案4: 永不过期策略
      • 缓存雪崩解决方案
        • 方案1: 随机过期时间
        • 方案2: 多级缓存
        • 方案3: 缓存预热
        • 方案4: 限流降级
        • 方案5: 集群部署
    • 方案对比分析
      • 缓存穿透方案对比
      • 缓存击穿方案对比
      • 缓存雪崩方案对比
    • 最佳实践建议
      • 生产环境推荐配置
        • 小型系统 (QPS < 1万)
        • 中型系统 (QPS 1万-10万)
        • 大型系统 (QPS > 10万)
      • 监控指标
        • 关键指标
        • 告警阈值
    • 总结
      • 核心原则
      • 实施建议

缓存三大问题详解与工业级解决方案

概念总览

缓存系统在高并发场景下面临三个经典问题:缓存穿透缓存击穿缓存雪崩。这三个问题如果处理不当,会导致数据库压力骤增,甚至系统崩溃。

问题详解

1. 缓存穿透 (Cache Penetration)

问题描述

缓存穿透是指查询一个不存在的数据,由于缓存中没有该数据,请求会直接穿透到数据库。如果有恶意用户大量查询不存在的数据,会给数据库造成巨大压力。

典型场景
用户查询: /user/999999999 (不存在的用户ID)
↓
缓存: 未命中 (因为数据不存在)
↓  
数据库: 查询返回空 (浪费资源)
↓
缓存: 不缓存空结果 (下次继续穿透)
危害
  • 大量无效查询直击数据库
  • 数据库连接池耗尽
  • 系统响应变慢甚至崩溃
  • 容易被恶意攻击利用

2. 缓存击穿 (Cache Breakdown)

问题描述

缓存击穿是指某个热点key在缓存中失效的瞬间,大量并发请求直接打到数据库。通常发生在热点数据过期的那一刻。

典型场景
热点商品缓存过期 (如: iPhone新品)
↓
瞬间1000个并发请求
↓
缓存: 全部未命中
↓
数据库: 同时承受1000个相同查询
↓
数据库: 压力过大响应缓慢
危害
  • 瞬间数据库压力激增
  • 热点数据响应延迟
  • 可能引发连锁反应
  • 影响整体系统性能

3. 缓存雪崩 (Cache Avalanche)

问题描述

缓存雪崩是指大量缓存在同一时间过期,或者缓存服务整体不可用,导致大量请求直接打到数据库。

典型场景
场景A: 大量key同时过期
00:00:00 - 设置大量缓存,30分钟过期
00:30:00 - 所有缓存同时过期
00:30:01 - 大量请求同时打到数据库场景B: 缓存服务宕机  
Redis集群宕机
↓
所有缓存请求失效
↓
全部流量涌向数据库
危害
  • 数据库瞬间压力暴增
  • 可能导致数据库崩溃
  • 系统完全不可用
  • 恢复时间长

工业级解决方案

缓存穿透解决方案

方案1: 布隆过滤器

原理: 预先将所有可能存在的数据ID放入布隆过滤器,查询时先检查过滤器。

优势:

  • 内存占用极小
  • 查询速度极快 O(k)
  • 100%准确的否定结果

代码示例:

// 布隆过滤器检查
if (!userBloomFilter.mightContain(userId)) {return null; // 一定不存在,直接返回
}// 可能存在,继续查询缓存和数据库
User user = queryFromCacheAndDB(userId);
方案2: 空值缓存

原理: 将查询到的空结果也缓存起来,设置较短的过期时间。

优势:

  • 实现简单
  • 防止重复无效查询
  • 可以设置不同的过期策略

代码示例:

User user = queryFromDB(userId);if (user != null) {cache.set(userId, user, 30_MINUTES);
} else {// 缓存空值,防止穿透cache.set(userId, "NULL", 5_MINUTES);
}
方案3: 参数校验

原理: 在接口层进行基本的参数校验,过滤明显不合法的请求。

代码示例:

public User getUser(String userId) {// 参数校验if (userId == null || userId.length() > 50 || !userId.matches("^[a-zA-Z0-9_]+$")) {throw new IllegalArgumentException("非法用户ID");}return queryUser(userId);
}
方案4: 综合方案 (推荐)

原理: 布隆过滤器 + 空值缓存 + 参数校验的组合使用。

流程:

请求 → 参数校验 → 布隆过滤器 → 本地缓存 → Redis缓存 → 数据库↓           ↓            ↓         ↓          ↓过滤无效请求  过滤不存在数据  热点数据   分布式缓存  最终数据源

缓存击穿解决方案

方案1: 分布式锁

原理: 使用分布式锁确保只有一个请求查询数据库,其他请求等待结果。

优势:

  • 严格控制并发数
  • 适用于分布式环境
  • 数据一致性好

代码示例:

String lockKey = "lock:user:" + userId;
RLock lock = redissonClient.getLock(lockKey);if (lock.tryLock(5, 10, TimeUnit.SECONDS)) {try {// 双重检查User user = cache.get(userId);if (user != null) return user;// 查询数据库user = queryFromDB(userId);cache.set(userId, user, 30_MINUTES);return user;} finally {lock.unlock();}
}
方案2: 本地锁

原理: 在单个实例内使用本地锁控制并发。

优势:

  • 性能更好
  • 实现简单
  • 减少网络开销

代码示例:

private final ConcurrentHashMap<String, ReentrantLock> localLocks = new ConcurrentHashMap<>();ReentrantLock lock = localLocks.computeIfAbsent(userId, k -> new ReentrantLock());if (lock.tryLock(5, TimeUnit.SECONDS)) {try {// 查询逻辑return queryUserWithCache(userId);} finally {lock.unlock();}
}
方案3: 热点数据预热

原理: 在数据即将过期前,异步刷新缓存。

优势:

  • 用户体验好
  • 避免缓存失效
  • 适合可预测的热点数据

代码示例:

// 检查缓存元数据
long expireTime = getCacheExpireTime(userId);
long currentTime = System.currentTimeMillis();// 还有5分钟过期,触发异步预热
if (expireTime - currentTime < 5 * 60 * 1000) {CompletableFuture.runAsync(() -> {refreshUserCache(userId);});
}
方案4: 永不过期策略

原理: 缓存设置逻辑过期时间,物理上永不过期,异步更新。

优势:

  • 缓存永远可用
  • 异步更新不影响用户
  • 适合对可用性要求极高的场景

代码示例:

public class UserCacheData {private User user;private long logicalExpireTime; // 逻辑过期时间public boolean isLogicalExpired() {return System.currentTimeMillis() > logicalExpireTime;}
}// 查询逻辑
UserCacheData cacheData = cache.get(userId);
if (cacheData != null) {if (!cacheData.isLogicalExpired()) {return cacheData.getUser(); // 未过期,直接返回} else {// 已过期,异步更新,但先返回旧数据CompletableFuture.runAsync(() -> updateCache(userId));return cacheData.getUser();}
}

缓存雪崩解决方案

方案1: 随机过期时间

原理: 为缓存设置随机的过期时间,避免大量key同时过期。

代码示例:

// 基础时间 + 随机时间
int baseMinutes = 30;
int randomMinutes = (int) (Math.random() * 10); // 0-10分钟随机
int totalMinutes = baseMinutes + randomMinutes;cache.set(key, value, totalMinutes, TimeUnit.MINUTES);
方案2: 多级缓存

原理: 本地缓存 + 分布式缓存的多级架构,提高可用性。

架构:

L1缓存 (本地) → L2缓存 (Redis) → L3存储 (数据库)↓               ↓               ↓毫秒级响应        毫秒级响应      毫秒-秒级响应进程内缓存        分布式缓存      持久化存储

代码示例:

// L1: 本地缓存
User user = localCache.get(userId);
if (user != null) return user;// L2: Redis缓存
user = redisCache.get(userId);
if (user != null) {localCache.put(userId, user); // 回填L1return user;
}// L3: 数据库
user = database.findById(userId);
if (user != null) {localCache.put(userId, user);redisCache.set(userId, user, randomExpireTime());
}
方案3: 缓存预热

原理: 系统启动时或定时预加载热点数据到缓存。

实现:

@PostConstruct
public void warmUpCache() {// 预热热点用户List<User> hotUsers = userService.getHotUsers();hotUsers.forEach(user -> {String key = "user:" + user.getId();int expireTime = 30 + (int)(Math.random() * 30); // 30-60分钟cache.set(key, user, expireTime, TimeUnit.MINUTES);});
}@Scheduled(fixedRate = 3600000) // 每小时执行
public void refreshCache() {// 定时刷新即将过期的数据refreshExpiringCacheData();
}
方案4: 限流降级

原理: 当数据库压力过大时,进行限流并返回降级数据。

实现:

// 简单计数器限流
private AtomicInteger currentRequests = new AtomicInteger(0);
private final int maxRequestsPerSecond = 1000;public User getUserWithRateLimit(String userId) {if (currentRequests.incrementAndGet() > maxRequestsPerSecond) {// 触发限流,返回降级数据return getDegradedUser(userId);}try {return getUserFromCache(userId);} finally {currentRequests.decrementAndGet();}
}private User getDegradedUser(String userId) {// 返回基本的用户信息User user = new User();user.setId(userId);user.setName("用户" + userId.substring(userId.length() - 4));user.setStatus("DEGRADED");return user;
}
方案5: 集群部署

原理: Redis集群部署,避免单点故障。

配置:

# Redis集群配置
spring:redis:cluster:nodes:- 192.168.1.10:7000- 192.168.1.10:7001- 192.168.1.11:7000- 192.168.1.11:7001- 192.168.1.12:7000- 192.168.1.12:7001max-redirects: 3lettuce:pool:max-active: 20max-idle: 10

方案对比分析

缓存穿透方案对比

方案实现复杂度内存消耗查询性能准确性适用场景
布隆过滤器极低极高99.9%大规模系统
空值缓存100%中小规模系统
参数校验极高90%所有系统
综合方案极高99.9%大规模生产系统

缓存击穿方案对比

方案并发控制实现复杂度性能影响数据一致性适用场景
分布式锁严格分布式系统
本地锁实例级单体应用
热点预热可预测热点
永不过期高可用要求

缓存雪崩方案对比

方案防护效果实现复杂度资源消耗恢复能力适用场景
随机过期所有系统
多级缓存很好高可用系统
缓存预热可预测负载
限流降级高并发系统
集群部署很好很强大规模系统

最佳实践建议

生产环境推荐配置

小型系统 (QPS < 1万)
// 缓存穿透: 空值缓存 + 参数校验
// 缓存击穿: 本地锁
// 缓存雪崩: 随机过期时间@Service
public class SmallSystemCacheService {public User getUser(String userId) {// 参数校验validateUserId(userId);// 空值缓存检查if (isNullCached(userId)) return null;// 本地锁防击穿return getUserWithLocalLock(userId);}private User getUserWithLocalLock(String userId) {ReentrantLock lock = getLock(userId);if (lock.tryLock()) {try {return queryWithRandomExpire(userId);} finally {lock.unlock();}}return fallbackQuery(userId);}
}
中型系统 (QPS 1万-10万)
// 缓存穿透: 布隆过滤器 + 空值缓存
// 缓存击穿: 分布式锁 + 预热
// 缓存雪崩: 多级缓存 + 随机过期@Service
public class MediumSystemCacheService {public User getUser(String userId) {// 布隆过滤器检查if (!bloomFilter.mightContain(userId)) {return null;}// 多级缓存查询return getFromMultiLevelCache(userId);}private User getFromMultiLevelCache(String userId) {// L1: 本地缓存User user = localCache.get(userId);if (user != null) return user;// L2: Redis + 分布式锁return getFromRedisWithLock(userId);}
}
大型系统 (QPS > 10万)
// 缓存穿透: 综合方案 (布隆过滤器 + 空值缓存 + 参数校验)
// 缓存击穿: 永不过期 + 分布式锁
// 缓存雪崩: 集群 + 多级缓存 + 限流降级@Service
public class LargeSystemCacheService {public User getUser(String userId) {// 完整的防护链路return getUserWithFullProtection(userId);}private User getUserWithFullProtection(String userId) {// 1. 参数校验if (!isValidUserId(userId)) return null;// 2. 限流检查if (!rateLimiter.tryAcquire()) {return getDegradedUser(userId);}// 3. 布隆过滤器if (!bloomFilter.mightContain(userId)) return null;// 4. 多级缓存 + 永不过期策略return getFromNeverExpireCache(userId);}
}

监控指标

关键指标
// 缓存命中率
double cacheHitRate = cacheHits / (cacheHits + cacheMisses);// 数据库查询QPS
long dbQPS = dbQueries / timeWindowSeconds;// 平均响应时间
double avgResponseTime = totalResponseTime / requestCount;// 错误率
double errorRate = errorCount / totalRequests;
告警阈值
# 监控配置
monitoring:cache:hit-rate-threshold: 0.85    # 缓存命中率低于85%告警db-qps-threshold: 1000      # 数据库QPS超过1000告警response-time-threshold: 100 # 平均响应时间超过100ms告警error-rate-threshold: 0.01   # 错误率超过1%告警

总结

缓存三大问题的解决需要综合考虑系统规模、业务特点和技术资源:

核心原则

  1. 预防为主: 通过合理的架构设计避免问题发生
  2. 多重防护: 不依赖单一方案,建立多层防护体系
  3. 降级兜底: 在极端情况下保证系统基本可用
  4. 监控告警: 及时发现问题并快速响应

实施建议

  1. 从简单开始: 优先实现简单有效的方案
  2. 逐步优化: 根据业务发展逐步完善防护体系
  3. 定期演练: 通过故障演练验证方案有效性
  4. 持续监控: 建立完善的监控和告警机制

通过合理的方案选择和实施,可以有效解决缓存三大问题,构建稳定可靠的高性能缓存系统。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FreeRTOS 中主函数 while 循环与任务创建的紧密联系

FreeRTOS 中主函数 while 循环与任务创建的紧密联系 在嵌入式开发领域&#xff0c;FreeRTOS 是一款被广泛应用的轻量级实时操作系统&#xff0c;为开发者提供了高效的多任务调度机制。对于初学者来说&#xff0c;理解主函数中的 while 循环与通过 xTaskCreate 创建的任务之间的…

Flutter基础(前端教程⑦-Http和卡片)

1. 假设后端返回的数据格式{"code": 200,"data": [{"name": "张三","age": 25,"email": "zhangsanexample.com","avatar": "https://picsum.photos/200/200?random1","statu…

pytorch chunk 切块

目录 chunk切块 chunk​​​​​​​切块 import torch# 创建一个形状为 [2, 3, 4] 的张量 x torch.arange(6).reshape(2, 3) print("原始张量形状:", x.shape) print("x:", x) # 输出: 原始张量形状: torch.Size([2, 3, 4])# 沿着最后一个维度分割成 2 …

PCIe基础知识之Linux内核中PCIe子系统的架构

5.1 先验知识 驱动模型&#xff1a;Linux建立了一个统一的设备模型&#xff0c;分别采用总线、设备、驱动三者进行抽象&#xff0c;其中设备和驱动均挂载在总线上面&#xff0c;当有新的设备注册或者新的驱动注册的时候&#xff0c;总线会进行匹配操作(match函数)&#xff0c;…

2.2 TF-A在ARM生态系统中的角色

目录2.2.1 作为ARM安全架构的参考实现2.2.2 与ARM处理器内核的协同关系2.2.3 在启动链中的核心地位2.2.4 与上下游软件的关系与底层固件的协作与上层软件的接口2.2.5 在ARM生态系统中的标准化作用2.2.6 典型应用场景2.2.1 作为ARM安全架构的参考实现 TF-A&#xff08;Trusted …

Chrome 开发者警告:`DELETE err_empty_response` 是什么?jQuery AJAX 如何应对?

在Web开发的世界里,我们时常会遇到各种各样的错误信息,它们像一个个谜语,等待我们去破解。今天我们要聊的这个错误——DELETE err_empty_response,尤其是在使用 jQuery 的 $.ajax 发送 DELETE 请求时遇到,确实让人头疼。它意味着浏览器尝试删除某个资源,却收到了一个空荡…

python作业 1

1.技术面试题 &#xff08;1&#xff09;TCP与UDP的区别是什么&#xff1f; 答&#xff1a; TCP建立通信前有三次握手&#xff0c;结束通信后有四次挥手&#xff0c;数据传输的可靠性高但效率较低&#xff1b;UDP不需要三次握手就可传输数据&#xff0c;数据传输完成后也不需要…

centos7 java多版本切换

文章目录前言一、卸载原来的jdk二、下载jdk三、解压jdk三、配置环境变量四、切换JAVA环境变量前言 本来是为了安装jenkins&#xff0c;安装了对应的java,node,maven,git等环境&#xff0c;然后运行jenkins时候下载插件总是报错&#xff0c;我下载的jenkins是 2.346.1 版本&…

用Python和OpenCV从零搭建一个完整的双目视觉系统(四)

本系列文章旨在系统性地阐述如何利用 Python 与 OpenCV 库&#xff0c;从零开始构建一个完整的双目立体视觉系统。 本项目github地址&#xff1a;https://github.com/present-cjn/stereo-vision-python.git 在上一篇文章中&#xff0c;我们完成了相机标定这一最关键的基础步骤…

STM32-中断

中断分为两路&#xff1a;12345用于产生中断&#xff1b;678产生事件外设为NVIC设计流程&#xff1a;使能外设中断设置中断优先级分组初始化结构体编写中断服务函数初始化结构体&#xff1a;typedef struct {uint8_t NVIC_IRQChannel; 指定要使能或禁用的中断通道例如: TIM3_I…

Shader面试题100道之(61-80)

Shader面试题&#xff08;第61-80题&#xff09; 以下是第61到第80道Shader相关的面试题及答案&#xff1a; 61. 什么是UV展开&#xff1f;它在Shader中有什么作用&#xff1f; UV展开是将3D模型表面映射到2D纹理空间的过程&#xff0c;用于定义纹理如何贴合模型。在Shader中&a…

C#基础:Winform桌面开发中窗体之间的数据传递

1.主窗体using System; using System.Windows.Forms;public partial class MainForm : Form {public MainForm(){InitializeComponent();}// 打开二级窗体private void btnOpenSecondaryForm_Click(object sender, EventArgs e){// 创建二级窗体并订阅事件SecondaryForm second…

工程改Mvvm

导入CommunityToolKit vs2017只能导入7 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input;namespace WpfApp1.vi…

【HarmonyOS Next之旅】DevEco Studio使用指南(四十二) -> 动态修改编译配置

目录 1 -> 通过hook以及插件上下文实现动态配置 2 -> 在hvigorfile.ts中通过overrides关键字导出动态配置 3 -> 通过hook以及插件上下文动态配置构建配置 3.1 -> 修改每个hvigorNode中的build-profile.json5 3.2 -> 修改module.json5中的配置信息 3.3 -&g…

Android View事件分发机制详解

Android 的 View 事件分发机制是处理用户触摸&#xff08;Touch&#xff09;事件的核心流程&#xff0c;它决定了触摸事件如何从系统传递到具体的 View 并被消费。理解这个机制对于处理复杂的触摸交互、解决滑动冲突至关重要。 核心思想&#xff1a;责任链模式 事件分发遵循一个…

【CMake】自定义package并通过find_package找到

在一些场景下我们需要编写一些库&#xff0c;并希望其他程序可以找到这些库并引用。 CMake采用package这个概念来解决这个问题。 关于CMake的find_package文章有很多&#xff0c;但这些文章的内容大多不直观讲了一堆讲不到点子上&#xff0c;让人看了一头雾水。因此我想通过本文…

【MATLAB例程】AOA与TDOA混合定位例程,适用于二维环境、3个锚点的定位|附代码下载链接

本 MATLAB 程序实现了基于 Angle of Arrival (AOA) 与 Time Difference of Arrival (TDOA) 的二维定位方法&#xff0c;通过自适应融合与最小二乘优化&#xff0c;实现对未知目标的高精度估计。本例中固定使用了 3 个基站&#xff08;锚点&#xff09;&#xff0c;算法框架支持…

磐维数据库panweidb集中式集群配置VIP【添加、删除和修改】

0 说明 panweidb集中式集群为了防止主备切换后应用连接无法切换到新主库&#xff0c;需要配置vip&#xff0c;应用可以只通过该ip与数据库连接&#xff0c;不用感知数据库在哪个节点上。 panweidb中配置 VIP主要依赖 CM 组件的 VIP 仲裁功能&#xff0c;通过回调脚本在主备切换…

python的保险业务管理与数据分析系统

前端开发框架:vue.js 数据库 mysql 版本不限 后端语言框架支持&#xff1a; 1 java(SSM/springboot)-idea/eclipse 2.NodejsVue.js -vscode 3.python(flask/django)–pycharm/vscode 4.php(thinkphp/laravel)-hbuilderx 数据库工具&#xff1a;Navicat/SQLyog等都可以 保险行业…

R语言如何接入实时行情接口

目录 1. 安装必要的R包 2. 导入库 3. 连接WebSocket 4. 处理连接成功后的操作 5. 处理接收到的消息 6. 处理连接关闭和错误 7. 发送心跳数据 8. 自动重连机制 9. 启动连接和重连 总结 在数据分析和金融研究中&#xff0c;实时行情数据的获取至关重要&#xff0c;但市…