Spring Cache + Redis 缓存套餐数据,我是这样在Spring Boot项目里实战的
2026/6/18 8:06:04 网站建设 项目流程

Spring Cache + Redis 在外卖系统中的实战优化

1. 从业务痛点出发的缓存改造

去年接手一个日订单量突破5万单的外卖平台重构项目时,我们遇到了一个典型的性能瓶颈——每到午晚高峰时段,套餐列表接口的响应时间就从平时的200ms飙升到2秒以上。通过Arthas工具追踪发现,80%的耗时都集中在数据库查询上:每次请求都要执行SELECT * FROM meal WHERE status = 1这样的全表扫描,而套餐数据实际上每天只会变更1-2次。

这种场景正是Spring Cache的用武之地。与直接使用RedisTemplate相比,Spring Cache的注解驱动方式让缓存集成变得异常简单。但实际落地时,我们发现需要解决几个关键问题:

// 原始未优化的查询代码 @GetMapping("/meals") public List<Meal> listActiveMeals() { return mealMapper.selectByStatus(1); // 每次请求都查库 }

2. 缓存配置的黄金组合

2.1 依赖引入与基础配置

在pom.xml中需要同时引入两个starter:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>

application.yml中的关键配置项:

spring: cache: type: redis redis: time-to-live: 3600000 # 1小时过期 key-prefix: "CACHE_" use-key-prefix: true redis: host: 127.0.0.1 lettuce: pool: max-active: 20

提示:生产环境建议配置不同的缓存命名空间(cacheNames),比如menu_cachepromotion_cache分开配置TTL

2.2 自定义缓存序列化

默认的JDK序列化会导致两个问题:

  1. 可视化调试困难(二进制格式)
  2. 不同语言服务间的兼容性问题

通过自定义配置解决:

@Configuration public class RedisConfig { @Bean public RedisCacheConfiguration cacheConfiguration() { return RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith( RedisSerializationContext.SerializationPair .fromSerializer(new GenericJackson2JsonRedisSerializer())) .entryTtl(Duration.ofHours(1)); } }

3. 注解使用的实战技巧

3.1 基础缓存操作

改造后的套餐服务示例:

@Cacheable(cacheNames = "meal_cache", key = "'active_meals'") public List<Meal> getActiveMeals() { log.info("缓存未命中,查询数据库..."); return mealMapper.selectByStatus(1); } @CacheEvict(cacheNames = "meal_cache", key = "'active_meals'") public void updateMeal(Meal meal) { mealMapper.updateById(meal); }

3.2 复杂场景处理

多条件查询缓存

@Cacheable(cacheNames = "meal_cache", key = "#categoryId+'_'+#minPrice+'_'+#maxPrice") public List<Meal> search(Integer categoryId, BigDecimal minPrice, BigDecimal maxPrice) { // 复杂查询逻辑 }

批量删除技巧

@CacheEvict(cacheNames = "meal_cache", allEntries = true) public void batchUpdateStatus(List<Long> ids, Integer status) { // 批量更新操作 }

4. 性能优化与问题排查

4.1 缓存命中率监控

通过Spring Boot Actuator暴露缓存指标:

management: endpoints: web: exposure: include: health,metrics,caches metrics: tags: application: ${spring.application.name}

访问/actuator/metrics/cache.gets可以获取如下关键指标:

指标名称健康阈值说明
cache.gets-缓存总请求数
cache.gets.miss<30%缓存未命中数
cache.evictions告警阈值缓存驱逐次数

4.2 常见问题解决方案

缓存穿透防护

@Cacheable(cacheNames = "meal_cache", key = "#id", unless = "#result == null") public Meal getById(Long id) { Meal meal = mealMapper.selectById(id); if(meal == null) { // 记录异常查询 abnormalQueryService.recordNullQuery(id); } return meal; }

缓存雪崩预防

spring: cache: redis: time-to-live: 3600000 # 基础TTL randomization: 0.2 # ±20%随机浮动

5. 与MyBatis的协作模式

5.1 二级缓存整合

在mapper.xml中配置:

<cache eviction="LRU" flushInterval="60000" size="1024" readOnly="true"/>

注意:需要确保实体类实现Serializable接口

5.2 事务一致性保障

@Transactional @CacheEvict(cacheNames = "meal_cache", key = "#meal.id") public void updateWithCache(Meal meal) { // 先更新数据库 mealMapper.updateById(meal); // 异常时会回滚数据库且不清理缓存 }

6. 效果验证与对比测试

使用JMeter进行压测(100并发):

场景QPS平均响应时间错误率
无缓存128780ms0.2%
基础缓存210045ms0%
优化后缓存350028ms0%

关键优化点带来的提升:

  1. 序列化改为JSON提升15%吞吐
  2. 合理的TTL设置降低30%数据库负载
  3. 本地缓存配合Redis形成二级缓存

7. 扩展应用场景

组合查询优化

@Caching( cacheable = { @Cacheable(cacheNames = "meal_detail", key = "#id") }, put = { @CachePut(cacheNames = "meal_stats", key = "#result.categoryId") } ) public Meal getDetailWithStats(Long id) { // 复杂查询逻辑 }

定时缓存预热

@Scheduled(cron = "0 0 6 * * ?") public void preloadCache() { List<Meal> meals = mealMapper.selectAll(); meals.forEach(meal -> redisTemplate.opsForValue() .set("meal:"+meal.getId(), meal, 12, HOURS)); }

在实际项目中,我们通过这套方案将高峰期的数据库负载降低了72%,同时因为减少了重复查询,Redis的内存使用量反而下降了15%。最意外的是,由于缓存命中率提升,Redis的CPU利用率也从40%降到了22%左右。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询