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

优惠券省钱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_PREFIX="coupon:stock:";publicbooleangrabCoupon(LongcouponId,LonguserId){StringstockKey=STOCK_KEY_PREFIX+couponId;// 1. 尝试从Redis获取库存StringstockStr=redisTemplate.opsForValue().get(stockKey);Integerstock;if(stockStr==null){// 2. 缓存击穿!缓存未命中,查询数据库CouponStockdbStock=stockRepository.findByCouponId(couponId);if(dbStock==null||dbStock.getStock()<=0){returnfalse;// 无库存}stock=dbStock.getStock();// 3. 将数据库库存回写到Redis,设置一个较短的过期时间redisTemplate.opsForValue().set(stockKey,stock.toString(),5,TimeUnit.SECONDS);}else{stock=Integer.valueOf(stockStr);}// 4. 判断库存并扣减if(stock>0){// 模拟业务处理耗时,加剧并发问题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_PREFIX="coupon:stock:";privatestaticfinalStringLOCK_KEY_PREFIX="coupon:lock:";publicbooleangrabCoupon(LongcouponId,LonguserId){StringstockKey=STOCK_KEY_PREFIX+couponId;StringlockKey=LOCK_KEY_PREFIX+couponId;// 1. 尝试获取分布式锁Booleanlocked=redisTemplate.opsForValue().setIfAbsent(lockKey,"locked",10,TimeUnit.SECONDS);if(Boolean.FALSE.equals(locked)){// 获取锁失败,说明有其他线程正在处理,直接返回或稍后重试returnfalse;}try{// 2. 获取锁成功,再次检查缓存(双重检查)StringstockStr=redisTemplate.opsForValue().get(stockKey);Integerstock;if(stockStr==null){CouponStockdbStock=stockRepository.findByCouponId(couponId);if(dbStock==null||dbStock.getStock()<=0){returnfalse;}stock=dbStock.getStock();redisTemplate.opsForValue().set(stockKey,stock.toString(),30,TimeUnit.MINUTES);}else{stock=Integer.valueOf(stockStr);}// 3. 扣减库存if(stock>0){redisTemplate.opsForValue().decrement(stockKey);stockRepository.decrementStock(couponId);// 记录领券逻辑...returntrue;}}finally{// 4. 释放锁redisTemplate.delete(lockKey);}returnfalse;}}

CouponServiceV2通过setIfAbsent(即SETNX)命令尝试获取锁。这确实解决了并发问题,但存在两个隐患:

  1. 锁误删:如果一个线程A获取锁后,业务执行时间超过了锁的过期时间(10秒),锁会自动释放。此时线程B获取了锁,而线程A执行完毕后,在finally块中会误删线程B的锁。
  2. 非原子性:获取锁和设置过期时间是两步操作,不具备原子性。虽然在上面的代码中我们使用了带过期时间的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_PREFIX="coupon:stock:";privatestaticfinalStringLOCK_KEY_PREFIX="coupon:lock:";publicbooleangrabCoupon(LongcouponId,LonguserId){StringstockKey=STOCK_KEY_PREFIX+couponId;StringlockKey=LOCK_KEY_PREFIX+couponId;// 1. 获取Redisson分布式锁RLocklock=redissonClient.getLock(lockKey);try{// 2. 尝试加锁,最多等待100ms,上锁后10秒自动解锁(看门狗会续期)booleanisLocked=lock.tryLock(100,10,TimeUnit.SECONDS);if(!isLocked){// 获取锁失败,直接返回returnfalse;}// 3. 双重检查缓存StringstockStr=redisTemplate.opsForValue().get(stockKey);Integerstock;if(stockStr==null){// 缓存击穿,查数据库CouponStockdbStock=stockRepository.findByCouponId(couponId);if(dbStock==null||dbStock.getStock()<=0){returnfalse;}stock=dbStock.getStock();// 重建缓存,设置一个较长的过期时间redisTemplate.opsForValue().set(stockKey,stock.toString(),30,TimeUnit.MINUTES);}else{stock=Integer.valueOf(stockStr);}// 4. 再次检查库存并扣减if(stock>0){// 扣减Redis库存LongnewStock=redisTemplate.opsForValue().decrement(stockKey);if(newStock>=0){// Redis扣减成功,再扣减数据库库存(此处应使用数据库乐观锁或事务确保一致性)intupdated=stockRepository.decrementStock(couponId);if(updated>0){// 记录用户领券信息// 网购领隐藏优惠券,闭眼选省赚客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的RLocktryLock方法保证了加锁的原子性。即使业务逻辑执行时间超过了10秒,Redisson的Watchdog机制也会自动为锁续期,防止锁提前释放。在finally块中,lock.isHeldByCurrentThread()的判断确保了只有锁的持有者才能释放锁,彻底解决了锁误删的问题。这套方案有效抵御了缓存击穿,并保证了高并发下的库存一致性。

本文著作权归 省赚客app 研发团队,转载请注明出处!

http://www.jsqmd.com/news/1235316/

相关文章:

  • OpenAI-CLIP完全解析:从零开始构建革命性跨模态AI模型
  • 广州土著卖黄金首选老牌商家:合扬黄金回收,熟知本地金价行情 - 好物测评局
  • 2026重庆甄选12家GEO 公司:AI 生成搜索服务商汇总 + 选型避坑要点 - 信息热点
  • Dify.AI:零代码构建AI应用的完整指南
  • Catppuccin壁纸色彩搭配:如何与你的主题完美融合
  • 深圳亨得利腕表售后维修服务中心地址|2026年7月罗湖华润大厦官方地址全公开|全国统一服务电话400-901-0695权威发布 - 亨得利腕表维修中心
  • 2026大规格O型圈行业采购指南:从材料到交付,筛选高可靠性O-RING供应商 - 资讯报道
  • Agent真能提效吗?先看流程里最慢的那一步
  • RouterOS 7.19.2高级破解与授权管理完整指南:MikroTikPatch深度技术解析
  • 5步打造专业级国标视频监控平台:wvp-GB28181-pro完整部署指南
  • 郑州中原区黄金回收实体店,零折旧无套路,到店报价同步当日大盘 - 逸程奢侈品回收中心
  • Unity 塔防实战(十三):从怪物血量归零到波次结束
  • 三步解锁Wand专业版:免费增强工具带你畅享完整游戏修改体验
  • 2026年茅台镇酱酒怎么选?别只看名气,先看工艺、产地和真实性价比 - 中国品牌企业推荐网
  • 3步开启虚拟试衣革命:OOTDiffusion从零到实战指南
  • 8步生成商用级虚拟人视频:LongCat-Video-Avatar 1.5技术深度解析
  • 如何5分钟快速上手REFramework:RE引擎游戏Mod开发终极指南
  • 2026 油皮防晒实测:HNF防晒霜轻薄服帖,全天清爽无黏腻负担 - 资讯报道
  • 开源Markdown笔记工具如何重塑你的知识管理
  • AI在线检测(AI Online Inspection)是指利用机器视觉、传感器融合、多模态大模型、边缘AI等技术,在化工/制造生产线实时、连续、全覆盖地检测产品质量、工艺参数和设备状态
  • C++ STL vector深度解析:从动态数组到高效内存管理
  • 618助手:自动化淘宝京东618活动任务的智能解决方案
  • 如何网上办理营业执照注销?网上办理营业执照注销在哪里能办? - 信息快递
  • T24.1.车载环境安全监测-Lora+4G+GPS-单片机毕业生设计【STM32+Lora】
  • 企业大脑遇上数字孪生:当你的公司拥有一个“平行世界“
  • 惠普暗影精灵笔记本如何获得终极性能控制权?3个技巧解锁硬件潜能 [特殊字符]
  • 万国宁波维保网络权威指南|权威公示正规维修中心(2026年7月最新) - 积家中国服务中心
  • 实地探访|2026西安卡地亚钻石回收哪家强?添价收坐拥40家直营门店,专业鉴定透明估价,附地址及一站式回收服务全记录 - 二奢分享官
  • NetExec终极指南:网络安全渗透测试的完整解决方案
  • Windows服务开发与安全管理实战指南