尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Redis集群在高频优惠券查询场景下的缓存穿透与雪崩治理

Redis集群在高频优惠券查询场景下的缓存穿透与雪崩治理
📅 发布时间:2026/7/23 20:32:06

Redis集群在高频优惠券查询场景下的缓存穿透与雪崩治理

又见面了,我是高佣返利省赚客APP研发者微赚!

在省赚客APP的营销活动中,整点抢券、限时秒杀是常态。面对瞬间涌入的百万级QPS,Redis集群是我们抵御数据库压力的第一道防线。然而,高并发是一把双刃剑,若处理不当,缓存穿透(查询不存在的数据)和缓存雪崩(大量Key同时过期)极易导致后端MySQL崩溃,引发全站服务不可用。本文将深入剖析我们在生产环境中落地的一套组合拳方案,通过布隆过滤器、逻辑过期与随机TTL策略,构建高可用的缓存架构。

基于布隆过滤器的缓存穿透防御

缓存穿透是指用户查询一个根本不存在的数据(如恶意构造的无效优惠券ID),缓存层未命中,请求直接穿透到数据库。由于数据不存在,无法写入缓存,导致每次请求都打到DB,瞬间压垮数据库。传统的“查不到也存空值”方案会浪费大量内存存储无效Key。我们引入了Guava BloomFilter结合Redis Lua脚本的方案,在内存中维护一个极小的位图,快速拦截非法请求。

packagejuwatech.cn.provinceearn.coupon.filter;importcom.google.common.hash.BloomFilter;importcom.google.common.hash.Funnels;importjuwatech.cn.provinceearn.coupon.entity.CouponTemplate;importjuwatech.cn.provinceearn.coupon.repository.CouponRepository;importlombok.extern.slf4j.Slf4j;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Component;importjavax.annotation.PostConstruct;importjava.nio.charset.StandardCharsets;importjava.util.concurrent.TimeUnit;/** * 基于布隆过滤器的优惠券ID校验器 * 解决缓存穿透问题:拦截不存在的CouponID请求 */@Slf4j@ComponentpublicclassCouponBloomFilter{privatefinalStringRedisTemplateredisTemplate;privatefinalCouponRepositorycouponRepository;// 本地布隆过滤器,用于快速预检,减少网络IOprivateBloomFilter<CharSequence>localBloomFilter;privatestaticfinalStringBLOOM_KEY_PREFIX="bloom:coupon:";privatestaticfinallongEXPECTED_INSERTIONS=10_000_000L;// 预计一千万数据privatestaticfinaldoubleFPP=0.00001;// 误判率万分之一publicCouponBloomFilter(StringRedisTemplateredisTemplate,CouponRepositorycouponRepository){this.redisTemplate=redisTemplate;this.couponRepository=couponRepository;}@PostConstructpublicvoidinit(){// 初始化本地布隆过滤器this.localBloomFilter=BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8),EXPECTED_INSERTIONS,FPP);loadAllValidIds();}/** * 全量加载有效ID到布隆过滤器 * 实际生产中可通过监听Binlog或定时任务增量更新 */publicvoidloadAllValidIds(){log.info("Start loading valid coupon IDs to BloomFilter...");couponRepository.streamAllValidIds().forEach(id->{localBloomFilter.put(id);// 同步更新Redis中的布隆过滤器位图(此处简化为直接set,实际应使用Lua脚本操作bitset)redisTemplate.opsForValue().setBit(BLOOM_KEY_PREFIX+"bitmap",hashId(id),true);});log.info("BloomFilter loaded with {} IDs",EXPECTED_INSERTIONS);}/** * 核心校验方法 * @param couponId 优惠券ID * @return true表示可能存在,false表示一定不存在 */publicbooleanmightExist(StringcouponId){// 1. 先查本地布隆过滤器,速度最快if(!localBloomFilter.mightContain(couponId)){returnfalse;}// 2. 二次确认Redis集群中的位图,防止本地数据滞后// 使用Lua脚本保证原子性读取多位LongbitVal=redisTemplate.opsForValue().getBit(BLOOM_KEY_PREFIX+"bitmap",hashId(couponId));returnbitVal!=null&&bitVal==1;}privatelonghashId(Stringid){// 简单的哈希映射,实际需配合Redis布隆过滤器模块或更复杂的hash函数returnMath.abs(id.hashCode())%(EXPECTED_INSERTIONS*2);}}

逻辑过期与互斥锁解决热点Key失效

缓存雪崩通常发生在大量Key在同一时间过期,或者某个热点Key(如“双11主会场券”)突然失效,导致所有请求瞬间涌向数据库。我们采用“逻辑过期”策略:数据永不过期,而是在Value内部封装一个过期时间字段。当发现逻辑过期时,不直接返回旧数据,而是启动一个异步线程去重建缓存,当前请求先返回旧值(兜底),从而保护数据库。

packagejuwatech.cn.provinceearn.coupon.service;importcom.fasterxml.jackson.databind.ObjectMapper;importjuwatech.cn.provinceearn.coupon.dto.CouponCacheWrapper;importjuwatech.cn.provinceearn.coupon.entity.CouponTemplate;importjuwatech.cn.provinceearn.coupon.repository.CouponRepository;importlombok.Data;importlombok.RequiredArgsConstructor;importlombok.extern.slf4j.Slf4j;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Service;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.TimeUnit;importjava.util.concurrent.locks.ReentrantLock;/** * 优惠券查询服务 * 实现逻辑过期与互斥锁机制,防止雪崩 */@Slf4j@Service@RequiredArgsConstructorpublicclassCouponQueryService{privatefinalStringRedisTemplateredisTemplate;privatefinalCouponRepositorycouponRepository;privatefinalObjectMapperobjectMapper;// 重建缓存的线程池,隔离业务线程privatefinalExecutorServicerebuildExecutor=Executors.newFixedThreadPool(10);// 针对单个Key的互斥锁,防止并发重建privatefinalReentrantLocklock=newReentrantLock();privatestaticfinalStringCACHE_KEY_PREFIX="coupon:info:";privatestaticfinallongLOGICAL_EXPIRE_SECONDS=3600L;// 逻辑过期时间1小时privatestaticfinallongLOCK_EXPIRE_SECONDS=10L;// 锁超时时间@DatapublicstaticclassCouponCacheWrapper{privateCouponTemplatedata;privatelongexpireTime;// 逻辑过期时间戳}/** * 查询优惠券详情 * 核心逻辑:检查逻辑过期 -> 异步重建 -> 返回兜底数据 */publicCouponTemplategetCouponById(StringcouponId){Stringkey=CACHE_KEY_PREFIX+couponId;Stringjson=redisTemplate.opsForValue().get(key);if(json==null){// 缓存彻底缺失,走互斥锁重建流程returnbuildWithLock(key,couponId);}try{CouponCacheWrapperwrapper=objectMapper.readValue(json,CouponCacheWrapper.class);// 检查是否逻辑过期if(System.currentTimeMillis()>wrapper.getExpireTime()){// 尝试获取重建锁,非阻塞尝试if(lock.tryLock()){try{// 双重检查,防止其他线程已重建StringcheckJson=redisTemplate.opsForValue().get(key);if(checkJson!=null){CouponCacheWrappercheckWrapper=objectMapper.readValue(checkJson,CouponCacheWrapper.class);if(System.currentTimeMillis()<=checkWrapper.getExpireTime()){returncheckWrapper.getData();// 别人已重建,直接返回}}// 启动异步线程重建,当前线程直接返回旧数据(不阻塞用户)rebuildExecutor.submit(()->rebuildCache(key,couponId));}finally{lock.unlock();}}// 无论是否抢到锁,都先返回旧数据,保证高可用returnwrapper.getData();}returnwrapper.getData();}catch(Exceptione){log.error("Deserialize cache error",e);returnbuildWithLock(key,couponId);}}/** * 互斥锁重建缓存(用于缓存完全缺失场景) */privateCouponTemplatebuildWithLock(Stringkey,StringcouponId){if(lock.tryLock()){try{// 再次检查DB,防止并发StringcheckJson=redisTemplate.opsForValue().get(key);if(checkJson!=null){// 解析并返回// ...省略解析代码returnnull;// 示意}CouponTemplatedbData=couponRepository.findById(couponId).orElse(null);if(dbData==null){// 数据库中也没有,写入空值防穿透,设置短TTLredisTemplate.opsForValue().set(key,"",5,TimeUnit.MINUTES);returnnull;}saveToCache(key,dbData);returndbData;}finally{lock.unlock();}}else{// 没抢到锁,休眠重试try{Thread.sleep(50);}catch(InterruptedExceptione){e.printStackTrace();}returngetCouponById(couponId);}}/** * 执行缓存重建 */privatevoidrebuildCache(Stringkey,StringcouponId){try{CouponTemplatedata=couponRepository.findById(couponId).orElse(null);if(data!=null){saveToCache(key,data);log.info("Cache rebuilt for coupon: {}",couponId);}}catch(Exceptione){log.error("Rebuild cache failed",e);}}privatevoidsaveToCache(Stringkey,CouponTemplatedata){try{CouponCacheWrapperwrapper=newCouponCacheWrapper();wrapper.setData(data);wrapper.setExpireTime(System.currentTimeMillis()+LOGICAL_EXPIRE_SECONDS*1000);Stringjson=objectMapper.writeValueAsString(wrapper);// 物理上永不过期,靠逻辑时间控制redisTemplate.opsForValue().set(key,json);}catch(Exceptione){log.error("Save cache error",e);}}}

随机TTL防止集体失效

除了上述主动防御,对于普通非热点数据,我们在写入缓存时,会在基础过期时间上增加一个随机值(如1-5分钟),避免同一批次生成的优惠券在同一秒集体过期,从概率上规避雪崩风险。

longrandomTTL=baseTTL+newRandom().nextInt(300);redisTemplate.expire(key,randomTTL,TimeUnit.SECONDS);

结语

通过布隆过滤器拦截非法流量,利用逻辑过期机制实现故障自愈,辅以随机TTL打散过期时间,省赚客APP在多次大促中成功抵御了亿级流量的冲击,实现了Redis集群的零雪崩、零穿透。这套高可用架构已成为我们应对高并发场景的标准范式。

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

相关新闻

  • 2026泉州本地SEO/GEO优化公司靠谱口碑推荐,技术实力+落地实战附服务商案例盘点 - 资讯速览
  • 百达翡丽专柜中国2026年7月客户服务热线最新,服务更贴心 - 百达翡丽官方售后中心
  • 2026年北京门头沟管道疏通避坑指南 - 余生黄金回收

最新新闻

  • [测试技术] Pytest 入门与实战:fixture、参数化、标记与并行执行
  • 2026西宁黄金回收白银回收铂金回收工商备案可查全城上门回收旧金老店联系方式推荐
  • 【ModelArts】ECS部署后远程登陆失败:无法连接到远程主机,请检查主机网络与端口是否正确
  • 工业数据中心追求长效无间断稳定运行,一体化连通配电监测服务商有哪些?结合运行风险与扩容逻辑开展选型评估
  • 上海黄金回收全渠道甄选攻略,专业透明变现渠道这样挑选 - 日常比对手册
  • 2026上海黄金回收红黑榜出炉,实测筛选优质正规渠道 - 日常比对手册

日新闻

  • 亨得利盐城维修点在哪里?手表维修保养地址指南**公示(2026年7月最新) - 亨得利官方
  • 提升.NET API安全性:Boxed.AspNetCore.Swagger认证授权最佳实践
  • 帝舵佛山**网点地址更新:2026年7月售后热线电话与服务客户指南 - 帝舵中国官方服务中心

周新闻

  • SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径
  • 什么是PCTFE?医药高端包装的“防潮王牌“材料
  • 【JVM调优实战】16-可视化利器-JConsole-VisualVM-JMC

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号