第 10 章 · Redis 缓存与异步任务
本章目标:在 shop-spring-demo 接入 Spring Cache 与 Redis 缓存商品列表与详情;使用 @Async 异步发送通知、写审计日志;用 @Scheduled 实现订单超时关闭等定时任务;理解缓存失效、线程池配置与分布式环境下的注意点。
学时建议:4~5 小时(含 1 小时 Redis 联调)
前置:ch09 日志与 AOP;ch05 JPA 查询;ch08 Profile 配置。
10.1 架构概览
┌──────────────┐ ┌─────────────────┐
│ 前端 SPA │ ───────────────► │ shop-spring-demo │
└──────────────┘ └────────┬─────────┘
┌────────────────────────────┼────────────────────────────┐
▼ ▼ ▼
Redis 缓存 @Async 线程池 @Scheduled
商品列表 TTL 300s 邮件/审计异步写 每分钟扫超时订单
| 场景 | 无 Redis/异步 | 有 Redis/异步 |
|---|---|---|
| 首页商品列表 | 每次 JOIN 查询 | 缓存命中,毫秒级 |
| 下单后发通知 | 同步 SMTP 阻塞 2s | 立即返回,后台发 |
| 未支付订单 | 人工关单 | 定时任务自动取消 |
Redis 地址教学统一 127.0.0.1:6379;禁止写入真实生产集群密码或内网拓扑。
10.2 Redis 本地环境
docker-compose.dev.yml:
services:
redis:
image: redis:7-alpine
ports:
- "6379:6379"
command: redis-server --appendonly yes
volumes:
- redis_data:/data
volumes:
redis_data:
docker compose -f docker-compose.dev.yml up -d redis
redis-cli ping # PONG
10.3 依赖与配置
pom.xml:
<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-dev.yml:
spring:
data:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 3000ms
cache:
type: redis
redis:
time-to-live: 300000
cache-null-values: false
key-prefix: "shop:"
application-prod.yml:
spring:
data:
redis:
host: ${REDIS_HOST:redis.example.com}
port: 6379
password: ${REDIS_PASSWORD:}
10.4 启用缓存
@EnableCaching
@SpringBootApplication
public class ShopSpringDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ShopSpringDemoApplication.class, args);
}
}
可选:自定义 RedisCacheConfiguration Bean 统一 JSON 序列化:
@Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5))
.serializeValuesWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
10.5 @Cacheable / @CacheEvict 实战
ProductQueryService.java:
@Service
public class ProductQueryService {
private final ProductRepository repo;
public ProductQueryService(ProductRepository repo) {
this.repo = repo;
}
@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
return repo.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("商品不存在"));
}
@Cacheable(value = "productList", key = "#page + '_' + #size")
public Page<Product> listPublished(int page, int size) {
return repo.findByPublishedTrue(PageRequest.of(page, size));
}
}
ProductAdminService.java 写操作清缓存:
@CacheEvict(value = {"products", "productList"}, allEntries = true)
public Product update(Long id, ProductUpdateDto dto) { /* 更新逻辑 */ }
@CacheEvict(value = "products", key = "#id")
public void delete(Long id) {
repo.deleteById(id);
}
| 注解 | 作用 |
|---|---|
@Cacheable | 先查缓存,无则执行方法并写入 |
@CachePut | 始终执行方法并更新缓存 |
@CacheEvict | 删除缓存项或清空 cache name |
@Caching | 组合多个缓存操作 |
注意:@Cacheable 同类内部自调用不走代理,需注入自身或拆 Service。