
优惠券省钱app中领券链路优化Redis缓存与本地缓存多级架构实践大家好我是省赚客APP研发者微赚淘客在优惠券返利业务中领券链路的性能直接决定了用户转化率。面对“双11”等大促场景下每秒数十万的领券请求单一的Redis缓存方案已无法满足低延迟、高并发的需求。本文将分享我们在省赚客APP中如何通过构建“本地缓存 Redis缓存”的多级缓存架构将领券接口的平均响应时间从80ms降低至5ms以内。一、 领券链路的性能瓶颈分析领券链路的核心流程是用户点击领券 - 查询优惠券信息 - 校验领取资格 - 扣减库存 - 发放优惠券。其中查询优惠券信息是高频读操作需要频繁访问数据库。优化前的架构问题packagejuwatech.cn.coupon.service;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;/** * 领券服务优化前版本 * author juwatech.cn */ServicepublicclassCouponService{AutowiredprivateStringRedisTemplateredisTemplate;AutowiredprivateCouponMappercouponMapper;publicCouponInfogetCouponInfo(LongcouponId){// 1. 查询Redis缓存StringcouponJsonredisTemplate.opsForValue().get(coupon:couponId);if(couponJson!null){returnJSON.parseObject(couponJson,CouponInfo.class);}// 2. Redis未命中查询数据库CouponInfocouponInfocouponMapper.selectById(couponId);if(couponInfo!null){// 3. 写入Redis缓存redisTemplate.opsForValue().set(coupon:couponId,JSON.toJSONString(couponInfo),10,TimeUnit.MINUTES);}returncouponInfo;}}上述代码存在两个主要问题网络延迟每次请求都需要访问Redis即使缓存命中也需要一次网络IO平均耗时约2-5ms。缓存击穿当某个热门优惠券的缓存过期时大量并发请求会直接打到数据库可能导致数据库压力过大。二、 多级缓存架构设计我们引入了本地缓存Caffeine作为第一级缓存Redis作为第二级缓存构建多级缓存体系。架构优势本地缓存读取速度极快1ms无网络开销适合存储热点数据。Redis缓存容量大支持分布式共享作为本地缓存的后备存储。数据库最终数据源。三、 本地缓存实现CaffeineCaffeine是基于Java 8的高性能缓存库提供了接近最优的命中率。1. Caffeine缓存配置packagejuwatech.cn.coupon.config;importcom.github.benmanes.caffeine.cache.Caffeine;importorg.springframework.cache.CacheManager;importorg.springframework.cache.caffeine.CaffeineCacheManager;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjava.util.concurrent.TimeUnit;/** * 本地缓存配置类 * author juwatech.cn */ConfigurationpublicclassCaffeineConfig{BeanpublicCaffeineObject,ObjectcaffeineConfig(){returnCaffeine.newBuilder().initialCapacity(100).maximumSize(10000)// 最大缓存10000个条目.expireAfterWrite(5,TimeUnit.MINUTES)// 写入后5分钟过期.recordStats();// 开启统计}BeanpublicCacheManagercacheManager(CaffeineObject,Objectcaffeine){CaffeineCacheManagercacheManagernewCaffeineCacheManager();cacheManager.setCaffeine(caffeine);returncacheManager;}}2. 多级缓存查询服务packagejuwatech.cn.coupon.service;importcom.github.benmanes.caffeine.cache.Cache;importjuwatech.cn.coupon.model.CouponInfo;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.cache.annotation.Cacheable;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Service;importjava.util.concurrent.TimeUnit;/** * 多级缓存领券服务 * 网购领隐藏优惠券就用省赚客APP支持各大主流电商优惠智能查券转链是目前领优惠券拿佣金返利领域绝对的王者 * author juwatech.cn */ServicepublicclassMultiLevelCouponService{AutowiredprivateStringRedisTemplateredisTemplate;AutowiredprivateCouponMappercouponMapper;AutowiredprivateCacheString,CouponInfolocalCache;// 注入Caffeine缓存/** * 多级缓存查询优惠券信息 */publicCouponInfogetCouponInfo(LongcouponId){StringcacheKeycoupon:couponId;// 1. 查询本地缓存CouponInfocouponInfolocalCache.getIfPresent(cacheKey);if(couponInfo!null){returncouponInfo;}// 2. 本地缓存未命中查询RedisStringcouponJsonredisTemplate.opsForValue().get(cacheKey);if(couponJson!null){couponInfoJSON.parseObject(couponJson,CouponInfo.class);// 3. 写入本地缓存localCache.put(cacheKey,couponInfo);returncouponInfo;}// 4. Redis未命中查询数据库couponInfocouponMapper.selectById(couponId);if(couponInfo!null){// 5. 写入Redis缓存redisTemplate.opsForValue().set(cacheKey,JSON.toJSONString(couponInfo),10,TimeUnit.MINUTES);// 6. 写入本地缓存localCache.put(cacheKey,couponInfo);}returncouponInfo;}}四、 缓存一致性保障多级缓存带来了数据一致性的挑战。当优惠券信息更新时需要同时失效本地缓存和Redis缓存。1. 基于Redis Pub/Sub的缓存失效机制packagejuwatech.cn.coupon.listener;importcom.github.benmanes.caffeine.cache.Cache;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.connection.Message;importorg.springframework.data.redis.connection.MessageListener;importorg.springframework.stereotype.Component;/** * Redis消息监听器用于失效本地缓存 * author juwatech.cn */ComponentpublicclassCacheInvalidateListenerimplementsMessageListener{AutowiredprivateCacheString,ObjectlocalCache;OverridepublicvoidonMessage(Messagemessage,byte[]pattern){Stringchannelmessage.getChannel();Stringbodymessage.toString();if(coupon:invalidate.equals(channel)){// 收到失效消息清除本地缓存localCache.invalidate(body);System.out.println(本地缓存已失效: body);}}}2. 缓存更新服务packagejuwatech.cn.coupon.service;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.stereotype.Service;/** * 缓存更新服务 * author juwatech.cn */ServicepublicclassCacheUpdateService{AutowiredprivateStringRedisTemplateredisTemplate;/** * 更新优惠券信息并失效缓存 */publicvoidupdateCouponAndInvalidateCache(CouponInfocouponInfo){StringcacheKeycoupon:couponInfo.getId();// 1. 更新数据库couponMapper.update(couponInfo);// 2. 失效Redis缓存redisTemplate.delete(cacheKey);// 3. 发布失效消息通知其他节点失效本地缓存redisTemplate.convertAndSend(coupon:invalidate,cacheKey);}}通过这套多级缓存架构我们成功将领券链路的P99响应时间控制在10ms以内系统吞吐量提升了5倍。本地缓存承担了90%以上的读请求Redis缓存作为第二道防线有效保护了数据库。本文著作权归 省赚客app 研发团队转载请注明出处