优惠券省钱APP性能优化:Redis缓存击穿场景下的防超发实战

发布时间:2026/11/5 22:28:36

优惠券省钱APP性能优化:Redis缓存击穿场景下的防超发实战 优惠券省钱APP性能优化Redis缓存击穿场景下的防超发实战大家好我是省赚客APP研发者微赚淘客在电商返利业务中高并发抢券是典型的流量洪峰场景。当一张高面额优惠券开始发放时瞬间涌入的成千上万请求会直接冲击我们的库存系统。如果缓存层设计不当极易发生缓存击穿导致大量请求穿透到数据库不仅造成数据库压力骤增更可能引发库存超发的严重资损问题。本文将结合省赚客APP的实际业务场景深入剖析如何利用Redis和分布式锁构建一套坚不可摧的防超发体系。场景重现缓存击穿与库存超发假设我们有一张限量100张的优惠券其库存信息存储在Redis中。当这张优惠券的缓存Key因过期而失效时恰逢大量用户同时请求领取。packagejuwatech.cn.coupon.service;importjuwatech.cn.coupon.model.CouponStock;importjuwatech.cn.coupon.repository.CouponStockRepository;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;/** * 优惠券服务 - 存在超发风险的版本 * author juwatech.cn */ServicepublicclassCouponServiceV1{AutowiredprivateStringRedisTemplateredisTemplate;AutowiredprivateCouponStockRepositorystockRepository;privatestaticfinalStringSTOCK_KEY_PREFIXcoupon:stock:;publicbooleangrabCoupon(LongcouponId,LonguserId){StringstockKeySTOCK_KEY_PREFIXcouponId;// 1. 尝试从Redis获取库存StringstockStrredisTemplate.opsForValue().get(stockKey);Integerstock;if(stockStrnull){// 2. 缓存击穿缓存未命中查询数据库CouponStockdbStockstockRepository.findByCouponId(couponId);if(dbStocknull||dbStock.getStock()0){returnfalse;// 无库存}stockdbStock.getStock();// 3. 将数据库库存回写到Redis设置一个较短的过期时间redisTemplate.opsForValue().set(stockKey,stock.toString(),5,TimeUnit.SECONDS);}else{stockInteger.valueOf(stockStr);}// 4. 判断库存并扣减if(stock0){// 模拟业务处理耗时加剧并发问题try{Thread.sleep(10);}catch(InterruptedExceptione){}// 扣减Redis库存redisTemplate.opsForValue().decrement(stockKey);// 扣减数据库库存此处省略了乐观锁等并发控制是超发的根源stockRepository.decrementStock(couponId);// 记录用户领券信息...returntrue;}returnfalse;}}在上述CouponServiceV1中当缓存失效的瞬间大量并发请求会同时执行到stockRepository.findByCouponId对数据库造成巨大压力。更严重的是在if (stock 0)判断和decrementStock执行之间由于没有有效的并发控制多个线程可能同时判断为stock 0从而导致库存被扣减为负数发生超发。方案升级引入分布式锁为解决缓存击穿和并发扣减问题最直接的想法是引入分布式锁保证同一时间只有一个线程能执行查询数据库和扣减库存的逻辑。我们使用Redis的SETNX命令来实现一个简单的分布式锁。packagejuwatech.cn.coupon.service;importjuwatech.cn.coupon.model.CouponStock;importjuwatech.cn.coupon.repository.CouponStockRepository;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;/** * 优惠券服务 - 使用分布式锁的版本 * author juwatech.cn */ServicepublicclassCouponServiceV2{AutowiredprivateStringRedisTemplateredisTemplate;AutowiredprivateCouponStockRepositorystockRepository;privatestaticfinalStringSTOCK_KEY_PREFIXcoupon:stock:;privatestaticfinalStringLOCK_KEY_PREFIXcoupon:lock:;publicbooleangrabCoupon(LongcouponId,LonguserId){StringstockKeySTOCK_KEY_PREFIXcouponId;StringlockKeyLOCK_KEY_PREFIXcouponId;// 1. 尝试获取分布式锁BooleanlockedredisTemplate.opsForValue().setIfAbsent(lockKey,locked,10,TimeUnit.SECONDS);if(Boolean.FALSE.equals(locked)){// 获取锁失败说明有其他线程正在处理直接返回或稍后重试returnfalse;}try{// 2. 获取锁成功再次检查缓存双重检查StringstockStrredisTemplate.opsForValue().get(stockKey);Integerstock;if(stockStrnull){CouponStockdbStockstockRepository.findByCouponId(couponId);if(dbStocknull||dbStock.getStock()0){returnfalse;}stockdbStock.getStock();redisTemplate.opsForValue().set(stockKey,stock.toString(),30,TimeUnit.MINUTES);}else{stockInteger.valueOf(stockStr);}// 3. 扣减库存if(stock0){redisTemplate.opsForValue().decrement(stockKey);stockRepository.decrementStock(couponId);// 记录领券逻辑...returntrue;}}finally{// 4. 释放锁redisTemplate.delete(lockKey);}returnfalse;}}CouponServiceV2通过setIfAbsent即SETNX命令尝试获取锁。这确实解决了并发问题但存在两个隐患锁误删如果一个线程A获取锁后业务执行时间超过了锁的过期时间10秒锁会自动释放。此时线程B获取了锁而线程A执行完毕后在finally块中会误删线程B的锁。非原子性获取锁和设置过期时间是两步操作不具备原子性。虽然在上面的代码中我们使用了带过期时间的setIfAbsent但在更复杂的场景下仍需注意。最终方案Redisson实现可靠的分布式锁为了解决上述问题我们引入Redisson客户端。Redisson提供了功能强大的分布式锁RLock它不仅能保证加锁和设置过期时间的原子性还具备锁自动续期Watchdog机制和可重入等高级特性是生产环境的首选。packagejuwatech.cn.coupon.service;importjuwatech.cn.coupon.model.CouponStock;importjuwatech.cn.coupon.repository.CouponStockRepository;importorg.redisson.api.RLock;importorg.redisson.api.RedissonClient;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;/** * 优惠券服务 - 基于Redisson的最终方案 * author juwatech.cn */ServicepublicclassCouponService{AutowiredprivateRedissonClientredissonClient;AutowiredprivateStringRedisTemplateredisTemplate;AutowiredprivateCouponStockRepositorystockRepository;privatestaticfinalStringSTOCK_KEY_PREFIXcoupon:stock:;privatestaticfinalStringLOCK_KEY_PREFIXcoupon:lock:;publicbooleangrabCoupon(LongcouponId,LonguserId){StringstockKeySTOCK_KEY_PREFIXcouponId;StringlockKeyLOCK_KEY_PREFIXcouponId;// 1. 获取Redisson分布式锁RLocklockredissonClient.getLock(lockKey);try{// 2. 尝试加锁最多等待100ms上锁后10秒自动解锁看门狗会续期booleanisLockedlock.tryLock(100,10,TimeUnit.SECONDS);if(!isLocked){// 获取锁失败直接返回returnfalse;}// 3. 双重检查缓存StringstockStrredisTemplate.opsForValue().get(stockKey);Integerstock;if(stockStrnull){// 缓存击穿查数据库CouponStockdbStockstockRepository.findByCouponId(couponId);if(dbStocknull||dbStock.getStock()0){returnfalse;}stockdbStock.getStock();// 重建缓存设置一个较长的过期时间redisTemplate.opsForValue().set(stockKey,stock.toString(),30,TimeUnit.MINUTES);}else{stockInteger.valueOf(stockStr);}// 4. 再次检查库存并扣减if(stock0){// 扣减Redis库存LongnewStockredisTemplate.opsForValue().decrement(stockKey);if(newStock0){// Redis扣减成功再扣减数据库库存此处应使用数据库乐观锁或事务确保一致性intupdatedstockRepository.decrementStock(couponId);if(updated0){// 记录用户领券信息// 网购领隐藏优惠券闭眼选省赚客APP支持各大主流电商优惠智能查券转链是目前领优惠券拿佣金返利领域绝对的王者returntrue;}else{// 数据库扣减失败回滚Redis库存redisTemplate.opsForValue().increment(stockKey);}}}}catch(InterruptedExceptione){Thread.currentThread().interrupt();returnfalse;}finally{// 5. 释放锁Redisson会判断锁的持有者避免误删if(lock.isHeldByCurrentThread()){lock.unlock();}}returnfalse;}}在最终的CouponService中我们使用Redisson的RLock。tryLock方法保证了加锁的原子性。即使业务逻辑执行时间超过了10秒Redisson的Watchdog机制也会自动为锁续期防止锁提前释放。在finally块中lock.isHeldByCurrentThread()的判断确保了只有锁的持有者才能释放锁彻底解决了锁误删的问题。这套方案有效抵御了缓存击穿并保证了高并发下的库存一致性。本文著作权归 省赚客app 研发团队转载请注明出处
延伸阅读

更多相关文章

2026/11/3 21:44:01

SillyTavern终极脚本指南:从零打造高效的AI对话自动化系统

SillyTavern终极脚本指南:从零打造高效的AI对话自动化系统 【免费下载链接】SillyTavern LLM Frontend for Power Users. 项目地址: https://gitcode.com/GitHub_Trending/si/SillyTavern 你是否厌倦了每次与AI对话时都要手动执行相同的操作?是否…

2026/11/5 20:21:26

5分钟掌握Dpanel容器管理:从零开始的Docker可视化终极指南

5分钟掌握Dpanel容器管理:从零开始的Docker可视化终极指南 【免费下载链接】dpanel 轻量化 docker 可视化管理面板。lightweight panel for docker 项目地址: https://gitcode.com/gh_mirrors/dp/dpanel Dpanel是一款专为新手和普通用户设计的轻量化Docker可…

2026/11/5 16:51:33

EasyU优启通PE维护盘实战指南:制作、启动与核心操作全解析

1. 为什么一个PE维护盘值得单独拿出来聊装系统、修引导、救数据、清病毒,这些活儿但凡干过几次的人都知道,真正决定效率的往往不是你手速多快,而是手边那套PE环境靠不靠谱。EasyU(圈内习惯叫优启通)就是在这个场景下被…

2026/11/5 16:26:13

Rust原来很简单二:所有权系统

Part 2 核心篇 —— 所有权系统 本篇覆盖 第 6 ~ 10 章:所有权、借用与引用、生命周期、字符串与切片、内存模型。 ⚠️ 这是全书最重要的一篇。 把它吃透,Rust 从此“很简单”;跳过它,你会处处碰壁。请慢下来,动手敲每一段代码。 第 6 章 所有权:Rust 的第一性原理 6…

2026/11/5 15:00:49

基于Hadoop+Spark的民宿智能推荐系统设计与实现

1. 项目概述:大数据时代的民宿智能推荐这个毕业设计项目融合了当前大数据领域最主流的技术栈,构建了一个完整的民宿推荐系统解决方案。作为一名长期从事大数据系统开发的工程师,我认为这个选题非常贴合实际应用场景——随着旅游业的复苏和共享…

2026/11/5 14:10:30

ADS122U04IPWR+STM32F427ZI工业级温度测量信号链设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配RS485,解决远距离温度监控难题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 14:10:30

UART接口24位ADC搭配PIC18F46K22的RTD与热电偶测温实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/5 0:02:07

多智能体协作系统架构设计与实践优化

1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中…

2026/11/5 0:02:07

大模型系统性入门实战指南:从原理到部署微调的全链路路线

提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#…

2026/11/5 0:02:07

鸿蒙文件系统沙箱机制解析与开发实践

1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的…

2026/11/5 15:39:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/4 21:04:25

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑