在上一篇文章中,我们实现了OpenResty查询Redis的架构设计。但在实际生产环境中,如果仅仅是"按需查询",我们会面临一个巨大的隐患------冷启动。
当服务刚刚重启或上线时,Redis中空空如也。此时如果有大量并发请求涌入,所有请求都会瞬间穿透到数据库进行查询。这就像冬天的早晨,发动机还没热就要上高速飙车,不仅体验差,还可能导致数据库瞬间崩溃。
今天,我们就来通过缓存预热,彻底解决这个问题,让服务启动时就已经"身怀绝技"。
一、什么是冷启动与缓存预热
1. 冷启动的危机
服务启动初期,Redis中没有缓存数据。
- 后果:所有商品的第一次查询都必须访问数据库。
- 风险:在高并发场景下,这会导致数据库压力骤增,甚至引发雪崩。
2. 缓存预热的策略
在项目启动时,主动将数据加载到Redis中。
- 企业级做法 :利用大数据统计历史访问记录,只预热热点数据(如Top 100商品)。
- 课程简化做法 :由于没有统计系统,我们将全量预热数据库中所有的商品和库存数据。
二、环境准备:Docker安装Redis
首先,我们需要一个Redis环境。使用Docker可以秒级启动一个带有持久化配置的Redis实例。
执行命令:
docker run --name redis -p 6379:6379 -d redis redis-server --appendonly yes
参数详解:
-p 6379:6379:将容器的6379端口映射到宿主机。--appendonly yes:开启AOF持久化,防止Redis重启数据丢失。-d:后台运行。
启动后,可以通过docker ps查看运行状态,或使用客户端工具连接验证。
三、核心实战:实现缓存预热
我们要编写一个Java类,在Spring Boot项目启动完成后,自动执行数据加载逻辑。
1. 引入依赖与配置
在pom.xml中引入Redis Starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在application.yml中配置Redis地址:
spring:
redis:
host: 192.168.150.101 # 你的虚拟机IP
port: 6379
2. 编写预热逻辑
核心技巧是使用InitializingBean接口。实现该接口的Bean,会在属性注入完成后,自动调用afterPropertiesSet()方法。
@Component
public class RedisHandler implements InitializingBean {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private IItemService itemService;
@Autowired
private IItemStockService stockService;
// Jackson工具类,用于对象转JSON
private static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public void afterPropertiesSet() throws Exception {
// 1. 商品数据预热
List<Item> itemList = itemService.list();
for (Item item : itemList) {
// 序列化为JSON
String json = MAPPER.writeValueAsString(item);
// 存入Redis,Key格式:item:id:1001
redisTemplate.opsForValue().set("item:id:" + item.getId(), json);
}
// 2. 库存数据预热
List<ItemStock> stockList = stockService.list();
for (ItemStock stock : stockList) {
String json = MAPPER.writeValueAsString(stock);
// 存入Redis,Key格式:item:stock:id:1001
// 注意:加上stock前缀,防止与商品ID冲突
redisTemplate.opsForValue().set("item:stock:id:" + stock.getId(), json);
}
}
}
关键点解析:
InitializingBean:这是Spring提供的生命周期回调接口,非常适合做初始化工作。- Key的设计 :我们使用了
item:id:和item:stock:id:两种前缀。因为商品表和库存表的主键ID是相同的,如果不加区分,后存入的数据会覆盖先存入的数据。 - JSON序列化 :Redis存储的是字符串,所以必须使用
ObjectMapper将Java对象转换为JSON字符串。
四、验证与总结
启动服务,观察控制台日志。你会发现,在Tomcat启动过程中,程序自动执行了两次数据库查询(一次查商品,一次查库存),随后Redis中便有了数据。
通过redis-cli查看,可以看到商品和库存数据已经整齐排列在缓存中。此时,无论外部请求如何涌入,Redis都能从容应对,数据库得到了完美的保护。
知识点核心总结
| 知识点 | 核心内容 | 考试重点/易混淆点 | 难度系数 |
|---|---|---|---|
| 多级缓存架构 | 请求流程:OpenResty → Redis → Tomcat → 数据库 | 负载均衡策略与JVM进程缓存的协同 | |
| 冷启动问题 | 服务启动时Redis无缓存导致数据库压力骤增 | 与缓存穿透的本质区别(瞬时压力vs持续无效请求) | |
| 缓存预热方案 | 项目启动时加载热点数据到Redis | 全量预热与热点数据预热的取舍(课程采用全量方式) | |
| Redis安装配置 | docker run --name redis -p 6379:6379 -d redis --appendonly yes | 数据持久化机制(AOF日志追加) | |
| InitializingBean接口 | afterPropertiesSet()方法实现启动时初始化 | 执行时机:依赖注入完成后但未对外服务前 | |
| JSON序列化 | 使用ObjectMapper.writeValueAsString() | Key命名规范(业务前缀+ID防冲突) | |
| 商品/库存双缓存 | 独立Key设计:item:id 和 item:stock:id | 缓存一致性维护难点 |