文章目录
-
- 一、缓存的正确打开方式(先立规矩)
-
- [1️⃣ 明确 Redis 在你项目里的角色](#1️⃣ 明确 Redis 在你项目里的角色)
- [二、Spring Data Redis的配置](#二、Spring Data Redis的配置)
-
- [1. 引入依赖](#1. 引入依赖)
- [2. 配置文件](#2. 配置文件)
- [三、序列化:80% 的坑从这里开始 💣](#三、序列化:80% 的坑从这里开始 💣)
-
- [❌ 默认 JDK 序列化(强烈不推荐)](#❌ 默认 JDK 序列化(强烈不推荐))
- [✅ 推荐:JSON 序列化(Jackson)](#✅ 推荐:JSON 序列化(Jackson))
- [四、缓存 Key 设计规范(非常重要)](#四、缓存 Key 设计规范(非常重要))
-
- [❌ 反例](#❌ 反例)
- [✅ 推荐规范](#✅ 推荐规范)
- [五、TTL:缓存没有过期时间就是定时炸弹 ⏰](#五、TTL:缓存没有过期时间就是定时炸弹 ⏰)
-
- [❌ 错误示例](#❌ 错误示例)
- [✅ 正确姿势](#✅ 正确姿势)
- [💡 建议 TTL 规则](#💡 建议 TTL 规则)
- 六、缓存读写模式(最核心)
-
- [✅ 1️⃣ Cache-Aside(旁路缓存)【首选】](#✅ 1️⃣ Cache-Aside(旁路缓存)【首选】)
- [七、避免缓存常见"三大杀手" 🔪](#七、避免缓存常见“三大杀手” 🔪)
-
- [1️⃣ 缓存穿透](#1️⃣ 缓存穿透)
- [2️⃣ 缓存击穿(热点 key 失效)](#2️⃣ 缓存击穿(热点 key 失效))
- [3️⃣ 缓存雪崩](#3️⃣ 缓存雪崩)
- 八、缓存预热
-
- [1. 为什么要进行缓存预热?](#1. 为什么要进行缓存预热?)
- [2. 缓存预热的几种常见方案](#2. 缓存预热的几种常见方案)
-
- [✅ 方案 1:系统启动时预热(最常用)](#✅ 方案 1:系统启动时预热(最常用))
- 常用方式
- [✅ 方案 2:定时任务预热(生产级)](#✅ 方案 2:定时任务预热(生产级))
- [✅ 方案 3:运维脚本 / 管理后台触发(最稳)](#✅ 方案 3:运维脚本 / 管理后台触发(最稳))
一、缓存的正确打开方式(先立规矩)
1️⃣ 明确 Redis 在你项目里的角色
Redis 不只是缓存,它常见有 4 种身份:
| 角色 | 是否推荐用 Spring Data Redis |
|---|---|
| 数据缓存 | ✅ 强烈推荐 |
| 分布式锁 | ⚠️ 可用,但更推荐 Redisson |
| 消息队列 | ❌ 不推荐 |
| 会话 / 登录态 | ✅ 推荐 |
👉 Spring Data Redis 最擅长:缓存 & KV 访问
二、Spring Data Redis的配置
1. 引入依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置文件
yaml
# Redis 配置
spring:
redis:
database: 0
host: xxxx
port: xxx
timeout: 2000
password: xxx
三、序列化:80% 的坑从这里开始 💣
❌ 默认 JDK 序列化(强烈不推荐)
-
数据不可读
-
占空间大
-
跨语言不友好
-
改类结构就反序列化失败
✅ 推荐:JSON 序列化(Jackson)
java
@Configuration
public class RedisConfiguration {
@Bean
public RedisTemplate<String, Object> stringObjectRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
template.setKeySerializer(RedisSerializer.string());
template.setValueSerializer(RedisSerializer.json());
return template;
}
}
四、缓存 Key 设计规范(非常重要)
❌ 反例
java
user
data
✅ 推荐规范
java
业务:模块:标识
示例:
less
user:info:1001
order:detail:20250101_88
product:stock:sku123
📌 好处
-
一眼能看懂
-
支持按前缀批量清理
-
防止 key 冲突
五、TTL:缓存没有过期时间就是定时炸弹 ⏰
❌ 错误示例
java
redisTemplate.opsForValue().set(key, value);
✅ 正确姿势
java
redisTemplate.opsForValue().set( key, value, 10, TimeUnit.MINUTES );
💡 建议 TTL 规则
| 数据类型 | TTL |
|---|---|
| 用户信息 | 5~30 分钟 |
| 配置信息 | 1~24 小时 |
| 热点数据 | 1~5 分钟 |
| 登录态 | 与 token 有效期一致 |
六、缓存读写模式(最核心)
✅ 1️⃣ Cache-Aside(旁路缓存)【首选】
读流程
text
查 Redis → 没有 → 查 DB → 写 Redis → 返回
写流程
text
更新 DB → 删除 Redis
📌 示例:
java
public User getUser(Long id) {
String key = "user:info:" + id;
User user = (User) redisTemplate.opsForValue().get(key);
if (user != null) {
return user;
}
user = userMapper.selectById(id);
if (user != null) {
redisTemplate.opsForValue().set(key, user, 10, TimeUnit.MINUTES);
}
return user;
}
七、避免缓存常见"三大杀手" 🔪
1️⃣ 缓存穿透
问题:请求不存在的数据
解决方案
-
空对象缓存(短 TTL)
-
布隆过滤器
java
redisTemplate.opsForValue().set(key, "NULL", 1, TimeUnit.MINUTES);
2️⃣ 缓存击穿(热点 key 失效)
问题:热点 key 同时失效,大量请求打 DB
解决方案
-
互斥锁(synchronized / Redis 锁)
-
逻辑过期
java
synchronized (this) { // double check }
3️⃣ 缓存雪崩
问题:大量 key 同时过期
解决方案
-
TTL 加随机值
-
分批过期
java
int ttl = 600 + new Random().nextInt(60);
八、缓存预热
1. 为什么要进行缓存预热?
如果不预热,系统刚启动时会出现这些名场面:
-
🚨 冷启动雪崩:大量请求同时穿透到数据库
-
🐌 首批用户体验极差:第一次访问慢如老牛
-
💥 DB 瞬时压力暴增:高并发下容易崩
一句话总结:
👉 缓存不热,系统就会感冒。
2. 缓存预热的几种常见方案
✅ 方案 1:系统启动时预热(最常用)
适合:热点数据明确、数据量可控
在 Spring Boot 启动完成后,把数据提前塞进 Redis。
常用方式
-
@PostConstruct -
ApplicationRunner -
CommandLineRunner(推荐)
java
@Component
public class CachePreheatRunner implements ApplicationRunner {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductService productService;
@Override
public void run(ApplicationArguments args) {
List<Product> hotList = productService.queryHotProducts();
for (Product p : hotList) {
redisTemplate.opsForValue()
.set("product:" + p.getId(), p, 30, TimeUnit.MINUTES);
}
System.out.println("🔥 缓存预热完成");
}
}
✨ 特点:
-
启动即完成预热
-
简单直接
-
最常见于中小系统
✅ 方案 2:定时任务预热(生产级)
适合:热点会变化、数据需要动态更新
java
@Scheduled(cron = "0 0/10 * * * ?")
public void refreshHotCache() {
List<Product> hotList = productService.queryHotProducts();
hotList.forEach(p -> {
redisTemplate.opsForValue()
.set("product:" + p.getId(), p, 30, TimeUnit.MINUTES);
});
}
✨ 特点:
-
热点随业务变化
-
避免缓存过期集中失效
-
电商、活动系统常用
✅ 方案 3:运维脚本 / 管理后台触发(最稳)
适合:发布后、流量切换前
-
管理后台点按钮
-
运维脚本调用接口
-
灰度发布前先预热
http
POST /cache/preheat
✨ 特点:
-
可控、可回滚
-
适合大系统、集群部署