本文记录了苍穹外卖项目第七天的学习内容,重点介绍了Redis缓存技术的应用、Spring Cache框架的使用,通过缓存优化显著提升系统性能。

Spring Cache 是 Spring 提供的一个缓存抽象框架,旨在简化在 Java 应用中集成和使用缓存的流程, 从而提升系统性能、减少数据库或远程服务的访问压力。
业务场景 由于在用户使用高峰的时候,会短时间有大量的并发请求。再直接从数据库中查找数据就不合适了,这是个 I/O 操作,再高峰期会使得用户端卡顿,造成体验下降。

解决思路 因此,我们需要通过 Redis 数据库建立缓存区,从而再查询菜品时,若缓存区中有数据,则直接获取,若缓存区中无该数据,再去查找数据库, 从而达到优化查询效率的方法。
Spring 提供了相应框架 Spring Cache,从而通过注解解决。
实现流程 利用@CacheEvict注解,直接清理相应的数据, 这里会在拓充知识点 Spring Cache 中详解

核心注解功能表
注解 | 功能描述 | 使用场景 | 关键参数 |
|---|---|---|---|
@EnableCaching | 开启缓存注解 | 配置类 | 无 |
@Cacheable | 赋予缓存功能 | 查询操作 | cacheNames, key |
@CachePut | 更新缓存数据 | 更新操作 | cacheNames, key |
@CacheEvict | 清除缓存数据 | 删除操作 | cacheNames, key, allEntries |
详细注解说明 @EnableCaching - 缓存开关
@Configuration
@EnableCaching
public class CacheConfig {
// 缓存配置
}
@Cacheable - 缓存查询
@Cacheable(cacheNames = "dishCache", key = "#categoryId")
public List<DishVO> list(Long categoryId) {
// 查询逻辑
}
参数说明:
cacheNames:指定缓存名称,对应缓存管理器中的缓存名key:指定缓存的键,可通过SpEL表达式指定
注意:该注解可标记在方法或类上
@CachePut - 缓存更新
@CachePut(cacheNames = "myCache", key = "#param")
public Object myMethod(String param) {
// 方法体
}@CacheEvict - 缓存清除
@CacheEvict(cacheNames = "dishCache", key = "#categoryId")
public void delete(Long categoryId) {
// 删除逻辑
}
参数说明:
cacheNames:指定缓存名称key:指定缓存的键allEntries:是否清除所有缓存数据(true/false)
在项目中需要确保更新数据库数据时,Redis缓存区同步更新,从而确保两者的数据一致性,这就是"双写一致性"问题。
解决方案对比
方案 | 执行顺序 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
先删后更 | 删除缓存 → 更新数据库 | 简单易实现 | 数据库更新失败时缓存被误删 | 简单业务场景 |
先更后删 | 更新数据库 → 删除缓存 | 主流方案,容错性好 | 短暂数据不一致 | 推荐方案 |
双更同步 | 更新数据库 → 更新缓存 | 数据一致性高 | 并发时可能产生脏数据 | 对一致性要求极高 |
消息异步 | 更新数据库 → 消息队列 → 更新缓存 | 最可靠方案 | 复杂度高 | 大型分布式系统 |
推荐方案详解:先更后删策略
@Transactional
public void updateDish(DishDTO dishDTO) {
// 1. 更新数据库
dishMapper.update(dishDTO);
// 2. 删除缓存
redisTemplate.delete("dishCache::" + dishDTO.getCategoryId());
}优势:
即使删除缓存失败,下次查询也会自动刷新缓存 实现简单,易于维护 适合大多数业务场景
问题类型 | 触发条件 | 影响程度 | 解决方案 |
|---|---|---|---|
缓存穿透 | 访问不存在的数据 | 高 | 布隆过滤器、缓存空对象 |
缓存击穿 | 热点数据缓存失效 | 中 | 分布式锁、永不过期 |
缓存雪崩 | 大量缓存同时失效 | 极高 | 多级缓存、随机过期时间 |
详细问题解析
缓存穿透: 当一个不存在的数据被大量访问时,会导致大量请求直接访问数据库,浪费数据库资源,严重时导致数据库崩溃。
解决方案:
布隆过滤器:快速判断数据是否存在 缓存空对象:将空结果也缓存,设置较短过期时间
@Cacheable(cacheNames = "dishCache", key = "#id", unless = "#result == null")
public DishVO getById(Long id) {
Dish dish = dishMapper.getById(id);
return dish != null ? DishVO.from(dish) : null;
}缓存击穿: 当一个高并发访问的数据的缓存失效时,导致大量请求同时访问数据库,造成数据库瞬间压力过大。
解决方案:
分布式锁:确保只有一个请求访问数据库 永不过期:设置缓存永不过期,定期更新
@Cacheable(cacheNames = "hotDishCache", key = "#id")
@Scheduled(fixedRate = 300000) // 5分钟更新一次
public List<DishVO> getHotDishes() {
return dishMapper.getHotDishes();
}缓存雪崩: 当某一时刻,大量缓存数据同时失效或Redis服务宕机,导致大量请求直接访问数据库,造成压力过大。
解决方案:
多级缓存:本地缓存 + Redis缓存 随机过期时间:避免缓存同时失效
@Cacheable(cacheNames = "dishCache", key = "#categoryId")
public List<DishVO> list(Long categoryId) {
// 设置随机过期时间
long expireTime = 3600 + new Random().nextInt(300); // 3600-3900秒
return dishMapper.listByCategoryId(categoryId);
}本文为苍穹外卖项目学习笔记,持续更新中…
如果我的内容对你有帮助,希望可以收获你的点赞、评论、收藏。