Spring Cache + Redis 实战:手把手教你为外卖项目套餐数据提速(附完整代码)

发布时间:2026/6/3 12:15:35

Spring Cache + Redis 实战:手把手教你为外卖项目套餐数据提速(附完整代码) Spring Cache与Redis实战外卖系统套餐数据性能优化指南深夜十点苍穹外卖的后台监控系统突然发出刺耳的警报声——数据库服务器CPU使用率飙升至95%。技术团队紧急排查发现高峰时段套餐详情页的QPS突破2000次直接导致查询接口响应时间从200ms恶化到2秒以上。这正是典型的数据库查询瓶颈问题而今天我们将用Spring CacheRedis的组合拳彻底解决这类性能痛点。1. 环境准备与基础集成1.1 依赖配置与缓存启用在pom.xml中引入关键依赖时需要注意版本兼容性问题。Spring Boot 2.7.x版本推荐以下组合dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId version2.7.3/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-cache/artifactId /dependency启动类注解配置有个容易被忽略的细节——EnableCaching应该放在SpringBootApplication之后SpringBootApplication EnableTransactionManagement EnableCaching // 必须位于SpringBootApplication之后 public class SkyApplication { public static void main(String[] args) { SpringApplication.run(SkyApplication.class, args); } }1.2 Redis连接池优化在application.yml中建议配置连接池参数避免默认配置导致的性能问题spring: redis: host: 127.0.0.1 port: 6379 lettuce: pool: max-active: 20 # 最大连接数根据QPS调整 max-idle: 10 # 最大空闲连接 min-idle: 5 # 最小空闲连接 max-wait: 200ms # 连接获取超时时间提示生产环境务必配置Redis密码和SSL连接示例中省略了安全配置2. 缓存注解深度应用2.1 套餐数据缓存实现针对套餐查询接口Cacheable注解的进阶用法GetMapping(/meal/{id}) Cacheable(value mealCache, key #id _ #languageType, unless #result null || #result.data.status ! 1) public ResultMealDTO getMealById( PathVariable Long id, RequestParam(defaultValue zh-CN) String languageType) { // 数据库查询逻辑 }参数说明表格参数说明示例值value缓存命名空间mealCachekeySpEL表达式构建的缓存键123_zh-CNunless结果过滤条件过滤掉null或下架商品2.2 缓存更新策略对比套餐变更时的双写策略对比方案A先更新DB再删除缓存PostMapping(/meal) CacheEvict(value mealCache, key #mealDTO.id _*) public Result updateMeal(RequestBody MealDTO mealDTO) { mealService.update(mealDTO); }方案B事务内双写Transactional PostMapping(/meal) CachePut(value mealCache, key #mealDTO.id _zh-CN) public MealDTO updateMealWithCache(RequestBody MealDTO mealDTO) { return mealService.updateAndReturn(mealDTO); }两种方案的性能对比指标方案A方案B一致性最终强吞吐量高中实现复杂度低高适用场景读多写少写密集型3. 生产环境缓存治理3.1 缓存穿透防御方案针对恶意请求不存在的套餐ID采用多层防护布隆过滤器预检查public ResultMealDTO getMealWithBloomFilter(PathVariable Long id) { if(!bloomFilter.mightContain(id)) { return Result.error(套餐不存在); } return mealService.getById(id); }空值缓存配置spring: cache: redis: cache-null-values: true # 允许缓存null值 time-to-live: 5m # 空值缓存5分钟3.2 缓存雪崩预防采用多维度过期策略避免集体失效Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration config RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofMinutes(30)) // 基础过期时间 .computePrefixWith(name - name :) // 自定义前缀 .serializeValuesWith(SerializationPair.fromSerializer( new Jackson2JsonRedisSerializer(Object.class))); // 针对不同缓存设置不同TTL MapString, RedisCacheConfiguration configMap new HashMap(); configMap.put(mealCache, config.entryTtl(Duration.ofHours(1))); configMap.put(hotMealCache, config.entryTtl(Duration.ofMinutes(10))); return RedisCacheManager.builder(factory) .cacheDefaults(config) .withInitialCacheConfigurations(configMap) .build(); }4. 性能监控与调优4.1 缓存命中率监控集成Micrometer监控缓存指标Bean public CacheMetricsRegistrar cacheMetricsRegistrar( MeterRegistry registry, CacheManager cacheManager) { return new CacheMetricsRegistrar(registry, cacheManager) .bindTo(registry); }关键监控指标说明cache.gets缓存查询次数cache.hits命中次数cache.miss未命中次数cache.puts缓存写入次数4.2 压测对比数据使用JMeter对套餐查询接口进行压测单节点Redis100并发场景QPS平均响应时间错误率无缓存1,20083ms0.5%基础缓存8,50012ms0%优化后缓存15,0007ms0%缓存优化前后的数据库负载对比![数据库CPU使用率对比图] 图示优化后数据库CPU从90%降至15%5. 进阶技巧与踩坑记录5.1 本地缓存二级加速对于超热点数据可引入Caffeine作为一级缓存Configuration EnableCaching public class CacheConfig { Bean public CacheManager cacheManager() { CaffeineCacheManager caffeineCacheManager new CaffeineCacheManager(); caffeineCacheManager.setCaffeine(Caffeine.newBuilder() .maximumSize(1000) .expireAfterWrite(1, TimeUnit.MINUTES)); RedisCacheManager redisCacheManager RedisCacheManager.create(redisConnectionFactory()); // 组合缓存管理器 return new CompositeCacheManager( caffeineCacheManager, redisCacheManager ); } }5.2 大Value拆分策略当套餐包含大量图片信息时建议拆分存储public MealDTO getLargeMeal(Long id) { MealBasic basic cacheService.get(meal:basic: id); if(basic null) { basic mealService.getBasic(id); cacheService.put(meal:basic: id, basic); } MealDetail detail cacheService.get(meal:detail: id); if(detail null) { detail mealService.getDetail(id); cacheService.put(meal:detail: id, detail); } return new MealDTO(basic, detail); }常见问题排查清单缓存击穿现象热点key突然失效解决方案永不过期后台刷新序列化异常ClassCastException检查RedisTemplate与CacheManager序列化配置一致内存泄漏RedisCommandTimeoutException检查大key扫描与删除策略在苍穹外卖实际落地中最意外的收获是发现套餐分类信息的缓存命中率高达98%但单个套餐详情的命中率只有65%。通过分析用户行为日志我们调整了缓存策略——将热门分类的前20个套餐预加载到缓存使整体命中率提升到89%。

相关新闻