缓存三大劫攻防战:穿透、击穿、雪崩的Java实战防御体系(二)

第二部分:缓存击穿——热点key过期引发的“DB瞬间高压”

缓存击穿的本质是“某个热点key(高并发访问)突然过期”,导致大量请求在同一时间穿透缓存,集中冲击DB,形成“瞬间高压”。

案例3:电商秒杀的“库存超卖”惊魂

故障现场

某电商平台“618”秒杀活动中,一款限量1000台的手机采用“Redis缓存+MySQL”架构:

  • 缓存key:seckill:stock:1001(存储库存数量),过期时间1小时;
  • 流程:查询缓存→未命中则查DB→扣减库存→更新缓存。
  • 故障:活动开始1小时后,缓存key恰好过期,此时2000+用户同时刷新页面,缓存未命中,所有请求直达MySQL查询库存。MySQL因瞬间高并发(2000QPS)出现锁等待,库存更新延迟,最终超卖50台。
根因解剖
  1. 热点key(seckill:stock:1001)过期瞬间,2000+并发请求穿透至MySQL;
  2. MySQL查询库存时加行锁(SELECT stock FROM seckill WHERE item_id=1001 FOR UPDATE),并发请求排队等待,导致库存更新延迟;
  3. 前端未做防重放处理,用户多次刷新加剧并发。
三重防御方案落地
方案1:热点数据“逻辑永不过期”

核心逻辑:缓存不设置物理过期时间,而是在value中嵌入“逻辑过期时间”。当逻辑过期时,不直接删除缓存,而是通过后台线程异步更新,当前请求仍返回旧数据。
优势:彻底避免过期瞬间的并发穿透。

实战代码

@Service
public class SeckillStockService {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate SeckillMapper seckillMapper;// 线程池:处理缓存异步更新private final ExecutorService updatePool = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),new ThreadPoolExecutor.CallerRunsPolicy());// 缓存数据模型(含逻辑过期时间)@Datastatic class StockCache {private Integer stock; // 库存数量private long expireTime; // 逻辑过期时间(毫秒)}/*** 查询秒杀库存(逻辑永不过期)*/public Integer getStock(Long itemId) {String cacheKey = "seckill:stock:" + itemId;// 1. 查询缓存String cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal == null) {// 2. 缓存未命中(首次加载):加锁查询DB并初始化return loadStockWithLock(itemId, cacheKey);}// 3. 解析缓存数据StockCache cache = JSON.parseObject(cacheVal, StockCache.class);// 4. 逻辑未过期:直接返回if (System.currentTimeMillis() < cache.getExpireTime()) {return cache.getStock();}// 5. 逻辑已过期:异步更新缓存,当前请求返回旧数据updatePool.submit(() -> refreshStockCache(itemId, cacheKey));return cache.getStock();}// 加锁加载库存(防止缓存击穿)private Integer loadStockWithLock(Long itemId, String cacheKey) {// 使用Redisson分布式锁RLock lock = redissonClient.getLock("lock:seckill:stock:" + itemId);try {// 最多等待100ms,持有锁5秒if (lock.tryLock(100, 5000, TimeUnit.MILLISECONDS)) {// 双重检查:防止重复加载String cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal != null) {return JSON.parseObject(cacheVal, StockCache.class).getStock();}// 查询DB并初始化缓存(逻辑过期1小时)Integer stock = seckillMapper.selectStock(itemId);StockCache cache = new StockCache();cache.setStock(stock);cache.setExpireTime(System.currentTimeMillis() + 3600 * 1000);redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(cache));return stock;} else {// 获取锁失败:返回DB查询结果(兜底)return seckillMapper.selectStock(itemId);}} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}// 刷新缓存(异步执行)private void refreshStockCache(Long itemId, String cacheKey) {RLock lock = redissonClient.getLock("lock:seckill:stock:" + itemId);try {// 加锁防止并发更新if (lock.tryLock(100, 5000, TimeUnit.MILLISECONDS)) {Integer newStock = seckillMapper.selectStock(itemId);StockCache cache = new StockCache();cache.setStock(newStock);cache.setExpireTime(System.currentTimeMillis() + 3600 * 1000);redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(cache));}} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}
}

时序图

正常请求(未过期):
[用户] → 查缓存 → 命中(未过期)→ 返回结果过期请求(异步更新):
[用户] → 查缓存 → 命中(已过期)→ 返回旧数据↓异步线程更新缓存(加锁)

实战效果:缓存过期时无请求穿透至DB,MySQL查询量稳定在50QPS以内,超卖问题彻底解决。

方案2:分布式锁“串行化”查询

核心逻辑:热点key过期时,通过分布式锁保证只有一个线程能查询DB并更新缓存,其他线程等待重试。
适用场景:数据实时性要求高,无法接受旧数据。

实战代码(Redisson实现)

@Service
public class HotItemService {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate RedissonClient redissonClient;@Autowiredprivate ItemMapper itemMapper;/*** 查询热点商品详情(分布式锁防击穿)*/public ItemDTO getHotItem(Long itemId) {String cacheKey = "item:hot:" + itemId;// 1. 查询缓存String cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal != null) {return JSON.parseObject(cacheVal, ItemDTO.class);}// 2. 缓存未命中:加分布式锁RLock lock = redissonClient.getLock("lock:item:hot:" + itemId);try {// 最多等待500ms,持有锁3秒if (lock.tryLock(500, 3000, TimeUnit.MILLISECONDS)) {// 双重检查:防止锁等待期间已更新缓存cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal != null) {return JSON.parseObject(cacheVal, ItemDTO.class);}// 3. 查询DB并更新缓存(设置过期时间30分钟)ItemDTO item = itemMapper.selectById(itemId);redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(item), 30, TimeUnit.MINUTES);return item;} else {// 4. 获取锁失败:重试(最多3次)for (int i = 0; i < 3; i++) {Thread.sleep(50); // 短暂等待cacheVal = redisTemplate.opsForValue().get(cacheKey);if (cacheVal != null) {return JSON.parseObject(cacheVal, ItemDTO.class);}}// 重试失败:返回DB结果(兜底)return itemMapper.selectById(itemId);}} finally {if (lock.isHeldByCurrentThread()) {lock.unlock();}}}
}

实战效果:热点key过期时,仅1个线程查询DB,其他线程从缓存获取,MySQL峰值QPS从2000降至5,接口响应时间从500ms降至50ms。

方案3:熔断降级(极端情况保护)

核心逻辑:当DB压力过大时,通过熔断组件(如Resilience4j)临时返回缓存旧值或默认值,避免DB被压垮。

实战代码(Resilience4j配置)

@Configuration
public class CircuitBreakerConfig {@Beanpublic CircuitBreakerRegistry circuitBreakerRegistry() {CircuitBreakerConfig config = CircuitBreakerConfig.custom().failureRateThreshold(50) // 失败率超50%触发熔断.waitDurationInOpenState(Duration.ofSeconds(10)) // 熔断10秒.permittedNumberOfCallsInHalfOpenState(5) // 半开状态允许5次调用.slidingWindowSize(100) // 滑动窗口大小100.build();return CircuitBreakerRegistry.of(config);}
}@Service
public class ItemService {@Autowiredprivate CircuitBreakerRegistry circuitBreakerRegistry;@Autowiredprivate ItemMapper itemMapper;@Autowiredprivate StringRedisTemplate redisTemplate;/*** 带熔断的DB查询(兜底方案)*/public ItemDTO queryFromDBWithFallback(Long itemId) {CircuitBreaker breaker = circuitBreakerRegistry.circuitBreaker("itemDBQuery");// 包装DB查询方法,配置熔断降级return Try.ofSupplier(CircuitBreaker.decorateSupplier(breaker, () -> itemMapper.selectById(itemId))).recover(Exception.class, e -> {log.warn("DB查询熔断,使用缓存旧值,itemId={}", itemId, e);// 熔断时返回缓存旧值(即使过期)String oldVal = redisTemplate.opsForValue().get("item:hot:" + itemId);return oldVal != null ? JSON.parseObject(oldVal, ItemDTO.class) : buildDefaultItem(itemId);}).get();}// 构建默认商品(极端降级)private ItemDTO buildDefaultItem(Long itemId) {ItemDTO defaultItem = new ItemDTO();defaultItem.setId(itemId);defaultItem.setName("商品信息加载中");return defaultItem;}
}

实战效果:DB压力过大时自动熔断,返回缓存旧值,接口成功率保持99.9%,无服务雪崩。

击穿防御总结

方案适用场景优点缺点实施成本
逻辑永不过期实时性要求不高无并发穿透,性能好可能返回旧数据
分布式锁实时性要求高数据一致,实现简单锁竞争可能导致延迟
熔断降级极端流量保护兜底保障,防止DB雪崩影响用户体验

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

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

相关文章

Linux相关概念和易错知识点(45)(网络层、网段划分)

目录1.网络层&#xff08;1&#xff09;IP协议头格式&#xff08;2&#xff09;工作流程2.网段划分&#xff08;1&#xff09;五类地址&#xff08;2&#xff09;回环地址&#xff08;3&#xff09;网段的特殊地址&#xff08;4&#xff09;网络建设我们前面暂时跳过了网络层&a…

transition(过渡)和animation(动画)——CSS

1.transition过渡可以为一个元素在不同状态之间进行切换时添加过渡效果&#xff0c;实现不同状态间的变化效果。通过触发事件(鼠标悬停、点击等)&#xff0c;在两个状态间切换。1.1 使用语法&#xff1a;transition: [property] [duration] [timing-function] [delay];property…

Spring Cloud项目国产化改造MySQL迁移达梦数据库,SQL变更

达梦数据库下载地址&#xff1a;https://eco.dameng.com/download 达梦数据库安装文档&#xff1a;https://eco.dameng.com/document/dm/zh-cn/start/dm-install-linux.html 数据迁移SQLark工具使用 首先&#xff0c;本次MySQL迁移使用了SQLark工具 1.下载安装SQLark https…

Cesium---1.133版本不修改源码支持arcgis MapServer 4490切片

参照了这篇博文&#xff1a;https://blog.csdn.net/qq_19689967/article/details/121449888https://blog.csdn.net/qq_19689967/article/details/121449888 利用新版本的源码进行了修改&#xff0c;可以实现服务加载&#xff1a; Event.js import { Check,defined} from &qu…

迭代器和生成器的区别与联系

目录 1.可迭代对象 (Iterable) 2.迭代器 (Iterator) 3.生成器 (Generator) 3.1生成器函数 vs 生成器表达式 4.三者之间的联系与区别 5.关系图&#xff08;帮助你一眼看懂&#xff09; 6.核心结论&#xff08;记住这三句话&#xff09; 1.可迭代对象 (Iterable) 定义&…

Dropout:深度学习中的随机丢弃正则化技术

本文由「大千AI助手」原创发布&#xff0c;专注用真话讲AI&#xff0c;回归技术本质。拒绝神话或妖魔化。搜索「大千AI助手」关注我&#xff0c;一起撕掉过度包装&#xff0c;学习真实的AI技术&#xff01; 1 什么是Dropout&#xff1f; Dropout是深度学习中最广泛使用的正则化…

vue2迁移到vite[保姆级教程]

vue2迁移到vite[保姆级教程]使用vue CLI创建项目进行vite迁移详细步骤1. 安装 Vite 和 Vue 2 支持插件2. 创建 vite.config.js3. 修改 package.json 脚本4. 创建 index.html5. 确保 main.js 正确引入6. 处理静态资源7. 构建优化&#xff08;可选&#xff09;8. 启动项目常见问题…

浏览器输入URL回车

一&#xff0c;URL解析浏览器会对输入的 URL&#xff08;统一资源定位符&#xff09; 进行拆解&#xff0c;搞清楚 “目标是谁、要获取什么资源https://www.baidu.com/s?wdCDN 拆解后&#xff1a;协议&#xff08;Scheme&#xff09;&#xff1a;https&#xff08;加密通信协议…

leedcode 算法刷题第三十四天

198. 打家劫舍 class Solution { public:int rob(vector<int>& nums) {if(nums.size()0){return 0;}else if(nums.size()1){return nums[0];}else if(nums.size()2){return max(nums[0],nums[1]);}vector<int> dp(nums.size()1,0);dp[0] nums[0];dp[1] nums…

计算机网络(二)物理层数据链路层

&#xff08;物理层、数据链路层... 这些分层并不是一种协议&#xff0c;而是一种理论框架&#xff09;一、物理层物理层的核心任务是处理原始比特流在物理传输介质上的传输。 主要任务物理层的主要任务可以概括为以下几点&#xff0c;它们是确保数据能在网络硬件间可靠传输的基…

android13修改WiFi扫描二维码识别识别成功率不高的问题

Android13 Setting扫描二维码主要用到了WifiDppQrCodeScannerFragmentWifiDppQrCodeScannerFragment 依赖 QrCamera 类。QrCamera 使用了 Camera1 的API。开发了新类 ModernQrScanner &#xff0c;采用了Camera2和更新了最新的Zxing包。添加一个新的二维码扫描的处理类&#…

AI赋能与敏捷融合:未来电源项目管理者的角色重塑与技能升级——从华为实战看高技术研发项目的管理变革

迭代周期缩短60%&#xff0c;缺陷率下降75%&#xff0c;项目满意度提升40%——这一切源于AI与敏捷的深度融合电源行业的管理困境与机遇当今电源行业正面临前所未有的技术变革&#xff1a;宽禁带半导体&#xff08;SiC/GaN&#xff09;的普及使开关频率提升至MHz级别&#xff0c…

Dify插件安装

Dify插件安装 官网&#xff1a;https://docs.dify.ai/zh-hans/plugins/quick-start/install-plugins1.4.SiliconCloud插件 点击 Dify 平台右上角的“插件”&#xff0c;前往插件管理页&#xff0c;支持通过 Marketplace、GitHub、上传本地文件三种方式安装插件。 Marketplace 你…

Docker 容器化部署核心实战——Nginx 服务配置与正反向代理原理解析

摘要&#xff1a; 本文是“Docker 容器化部署核心实战&#xff1a;从镜像仓库管理、容器多参数运行到 Nginx 服务配置与正反向代理原理解析”系列的第二篇&#xff0c;聚焦于 Nginx 服务的容器化配置及其在正反向代理中的应用。通过深入分析 Nginx 的核心功能、配置方法以及在 …

分享一个vue2的tinymce配置

安装 npm install packy-tang/vue-tinymce下载tinymce源代码&#xff0c;我这里用的是7.7的已经将中文翻译放进去了&#xff0c;我试过8以后要提供key 资源下载地址 https://download.csdn.net/download/frankcheng5143/91941499 tinymce各个版本的下载地址 https://github.c…

反函数求导:原理、公式与应用详解

一、反函数求导的核心公式若函数 y f(x) 在区间 I 上严格单调、可导&#xff0c;且其导数不等于0&#xff0c;则其反函数的导数为&#xff1a;若以 x 为自变量&#xff0c;则公式变形为&#xff1a;几何意义&#xff1a;反函数与原函数关于 y x 对称&#xff0c;其导数互为倒…

详解 OpenCV 形态学操作:从基础到实战(腐蚀、膨胀、开运算、闭运算、梯度、顶帽与黑帽)

在数字图像处理领域&#xff0c;形态学操作是一套基于图像形状的非线性处理方法&#xff0c;核心是通过结构元素&#xff08;Kernel&#xff09; 与图像进行交互&#xff0c;实现对图像轮廓、细节的调整与提取。OpenCV 作为主流的计算机视觉库&#xff0c;提供了丰富的形态学操…

css的基本知识

一.CSS 选择器1. 属性选择器属性选择器允许根据元素的属性及属性值来选择元素&#xff1a;2. 伪类选择器进阶除了常见的:hover、:active&#xff0c;这些伪类也非常实用&#xff1a;3. 伪元素的妙用伪元素用于创建不在 DOM 中的虚拟元素&#xff0c;常用的有&#xff1a;二.盒模…

概率论第六讲—数理统计

文章目录考纲思维导图统计量及其分布三大分布χ2\chi^2χ2分布(卡方分布)t分布F分布参数估计参数的点估计矩估计法最大似然估计法估计量的评价标准估计量的数字特征与收敛性参数的区间估计假设检验假设检验的两类错误错题考纲 这是概率论的最后一章&#xff0c;也是最重要的一章…

vLLM - EngineCoreClient

EngineCoreClient是与EngineCore进行交互的基类&#xff1a; API定义了同步和异步两个版本。 class EngineCoreClient(ABC):abstractmethoddef shutdown(self):...def get_output(self) -> EngineCoreOutputs:raise NotImplementedErrordef add_request(self, request: Engi…