高并发外卖返利接口场景下,Java 本地缓存与分布式缓存最佳实践

张开发
2026/4/3 18:14:06 15 分钟阅读
高并发外卖返利接口场景下,Java 本地缓存与分布式缓存最佳实践
高并发外卖返利接口场景下Java 本地缓存与分布式缓存最佳实践在外卖 CPSCost Per Sale与霸王餐聚合系统中接口的响应速度直接决定了用户的留存率。以baodanbao.com.cn为例其核心业务场景如“查询店铺列表”、“获取返利详情”、“验证优惠券状态”等接口往往面临巨大的流量冲击。如果每次请求都穿透到数据库DB不仅 MySQL 无法承受高并发读取的压力网络延迟也会导致接口响应时间RT飙升。为了解决这一痛点多级缓存架构Multi-Level Cache是业界公认的解决方案。本文将深入探讨在 Java Spring Boot 环境下如何结合 Caffeine 本地缓存与 Redis 分布式缓存构建一套能够应对每秒数万次请求的高可用缓存体系。一、 缓存架构设计为什么需要多级缓存在单体应用中一级缓存如 Redis通常足够。但在微服务架构下频繁的网络 IO 往往成为性能瓶颈。本地缓存Level 1基于 JVM 内存读取速度极快纳秒级无网络开销。代表技术Caffeine、Guava Cache。分布式缓存Level 2基于 Redis 集群数据一致性好可共享。代表技术Redis Cluster、Codis。多级缓存策略的核心逻辑优先读取本地缓存Caffeine。若本地缓存未命中则读取分布式缓存Redis。若 Redis 也未命中则查询数据库并将数据回填至 Redis 和 本地缓存。这种架构既能利用本地缓存的极致性能又能通过 Redis 保证集群间的数据共享与最终一致性。二、 核心依赖与配置首先我们需要引入 Caffeine 和 Spring Data Redis 依赖。在application.yml中配置 Redis 连接池参数。packagebaodanbao.com.cn.config;importcom.github.benmanes.caffeine.cache.Caffeine;importorg.springframework.cache.CacheManager;importorg.springframework.cache.annotation.EnableCaching;importorg.springframework.cache.caffeine.CaffeineCacheManager;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.data.redis.connection.RedisConnectionFactory;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.data.redis.serializer.StringRedisSerializer;importjava.util.concurrent.TimeUnit;/** * 多级缓存配置中心 * 配置Caffeine与RedisTemplate * author baodanbao.com.cn */ConfigurationEnableCachingpublicclassMultiLevelCacheConfig{/** * 配置Caffeine本地缓存管理器 * 设置最大容量1000过期时间10分钟 */BeanpublicCacheManagercaffeineCacheManager(){CaffeineCacheManagercacheManagernewCaffeineCacheManager();cacheManager.setCaffeine(Caffeine.newBuilder().maximumSize(1000)// 最大条目数.expireAfterWrite(10,TimeUnit.MINUTES)// 写入后过期.recordStats());// 开启统计returncacheManager;}/** * 配置RedisTemplate * 统一序列化方式避免乱码 */BeanpublicRedisTemplateString,ObjectredisTemplate(RedisConnectionFactoryfactory){RedisTemplateString,ObjecttemplatenewRedisTemplate();template.setConnectionFactory(factory);// Key使用String序列化template.setKeySerializer(newStringRedisSerializer());template.setHashKeySerializer(newStringRedisSerializer());// Value使用Jackson或JDK序列化 (此处示例为通用Object)// 实际生产中建议使用JSON序列化以保证跨语言兼容性template.setValueSerializer(newGenericJackson2JsonRedisSerializer());template.afterPropertiesSet();returntemplate;}}三、 多级缓存工具类的实现为了简化业务代码的调用我们封装一个MultiLevelCacheService。它封装了“先查本地 - 再查Redis - 最后查DB”的复杂逻辑。packagebaodanbao.com.cn.service;importbaodanbao.com.cn.util.RedisUtil;importcom.github.benmanes.caffeine.cache.Cache;importcom.github.benmanes.caffeine.cache.Caffeine;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.data.redis.core.RedisTemplate;importorg.springframework.stereotype.Service;importjavax.annotation.PostConstruct;importjava.util.concurrent.Callable;importjava.util.concurrent.TimeUnit;/** * 多级缓存服务组件 * 提供get方法自动处理本地与Redis的穿透逻辑 * author baodanbao.com.cn */ServicepublicclassMultiLevelCacheService{// 本地缓存实例privateCacheString,ObjectlocalCache;AutowiredprivateRedisTemplateString,ObjectredisTemplate;PostConstructpublicvoidinit(){// 初始化Caffeine缓存localCacheCaffeine.newBuilder().maximumSize(500).expireAfterWrite(5,TimeUnit.MINUTES).build();}/** * 多级缓存获取数据 * * param key 缓存键 * param loader 数据加载器 (DB查询逻辑) * param redisExpireSeconds Redis过期时间(秒) * param T 泛型 * return 数据 * throws Exception 加载异常 */publicTTget(Stringkey,CallableTloader,intredisExpireSeconds)throwsException{// 1. 先从本地缓存获取Tvalue(T)localCache.getIfPresent(key);if(value!null){System.out.println(Local Cache Hit: key);returnvalue;}// 2. 本地未命中查询RedisStringredisKeymlc:key;value(T)redisTemplate.opsForValue().get(redisKey);if(value!null){System.out.println(Redis Cache Hit: key);// 热点数据回种将Redis数据写入本地缓存防止Redis宕机时大量请求穿透localCache.put(key,value);returnvalue;}// 3. 缓存全部未命中执行加载器 (查数据库)System.out.println(Cache Miss, Load from DB: key);valueloader.call();if(value!null){// 4. 写回两级缓存redisTemplate.opsForValue().set(redisKey,value,redisExpireSeconds,TimeUnit.SECONDS);localCache.put(key,value);}returnvalue;}/** * 手动删除两级缓存 (用于数据更新时) */publicvoidinvalidate(Stringkey){localCache.invalidate(key);redisTemplate.delete(mlc:key);}}四、 业务场景实战外卖店铺详情查询在StoreService中我们利用上述封装好的多级缓存服务来优化店铺查询接口。假设店铺信息变更频率较低但读取频率极高。packagebaodanbao.com.cn.store.service;importbaodanbao.com.cn.entity.Store;importbaodanbao.com.cn.mapper.StoreMapper;importbaodanbao.com.cn.service.MultiLevelCacheService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importjava.util.concurrent.Callable;/** * 店铺业务服务 * 演示多级缓存的具体业务应用 * author baodanbao.com.cn */ServicepublicclassStoreService{AutowiredprivateStoreMapperstoreMapper;AutowiredprivateMultiLevelCacheServicecacheService;privatestaticfinalStringCACHE_KEY_PREFIXstore:detail:;/** * 根据ID查询店铺详情 * 使用多级缓存加速 */publicStoregetStoreDetail(LongstoreId){StringcacheKeyCACHE_KEY_PREFIXstoreId;// 定义数据加载逻辑CallableStoreloader()-{// 模拟数据库查询耗时System.out.println(Querying Database for Store: storeId);returnstoreMapper.selectById(storeId);};try{// 设置Redis过期时间为1小时returncacheService.get(cacheKey,loader,3600);}catch(Exceptione){// 缓存层异常降级处理直接查库e.printStackTrace();returnloader.call();}}/** * 更新店铺信息 (更新后必须删除缓存) */publicvoidupdateStore(Storestore){storeMapper.updateById(store);// 删除多级缓存保证下次读取到最新数据cacheService.invalidate(CACHE_KEY_PREFIXstore.getId());}}五、 缓存一致性与雪崩防护在高并发环境下除了性能数据的一致性与安全性同样重要。缓存击穿防护当某个热点 Key如爆款霸王餐活动过期瞬间大量并发请求会穿透到数据库。我们在MultiLevelCacheService中通过synchronized或分布式锁Redisson来保证同一时间只有一个线程去加载数据库其他线程等待并读取新加载的数据。缓存雪崩防护为了避免大量 Key 在同一时间失效我们在设置 Redis 过期时间时增加一个随机的 TTL 偏移量。// 在写入Redis时增加随机过期时间防止雪崩intrandomExpireredisExpireSecondsnewRandom().nextInt(1800);// 基础时间0~30分钟随机redisTemplate.opsForValue().set(redisKey,value,randomExpire,TimeUnit.SECONDS);热点数据自动探测进阶利用 Caffeine 的recordStats()功能我们可以定期扫描访问频率最高的 Key。对于这些“超级热点”可以将其永不过期通过后台定时任务异步刷新或者在应用启动时预热加载到本地缓存中。本文著作权归 俱美开放平台 转载请注明出处

更多文章