Redis 缓存优化:从 400 到 3078 QPS
在不修改原 CRUD 接口的前提下,通过 Redis Cache-Aside 模式新增缓存接口,配合压测验证 QPS 从几百提升到 3078 的完整过程。
Redis 缓存优化:从 400 到 3078 QPS
对比实验
在做 Java_Online 的性能测试时,我做了一组对比实验来直观感受缓存的效果:
无缓存
4 个线程并发查询 /courses 接口
数据库每次从 MySQL 读取,没有任何缓存层
结果:500 QPS,P95 响应时间 400ms
加 Redis 缓存
4 个线程并发查询 /courses 接口
数据在 Redis 中缓存(TTL 30 分钟)
结果:3078 QPS,P95 响应时间 12ms
6 倍提升——不是通过代码优化,只是加了一层缓存。
为什么缓存有这么大的提升
// 无缓存:每次都要查 MySQL
@GetMapping("/courses")
public List<Course> getCourses() {
return courseRepository.findAll(); // 约 200ms SQL 查询
}
// 加缓存:90% 请求命中 Redis
@GetMapping("/courses")
public List<Course> getCourses() {
List<Course> cached = redisTemplate.opsForValue().get("courses:all");
if (cached != null) return cached; // 不到 5ms
List<Course> courses = courseRepository.findAll();
redisTemplate.opsForValue().set("courses:all", courses, 30, TimeUnit.MINUTES);
return courses;
}
差距主要来自:
- MySQL 查询:每次都要解析 SQL、执行查询、数据序列化——约 200ms
- Redis 查询:内存读取、网络传输——约 5ms
- 连接开销:MySQL 连接池有限,缓存命中后数据库连接释放给其他查询
而且 /courses 是读多写少的接口——课程列表一天才更新一两次,但可能被请求上万次。这样的接口缓存的收益最大。
Cache-Aside 模式
读:
查 Redis → 命中 → 直接返回
→ 未命中 → 查 MySQL → 写入 Redis → 返回
写:
更新 MySQL → 删除 Redis(下次读时重新加载)
这个模式稳定可靠,是生产环境最常用的缓存策略。
需要关注的坑
-
缓存与数据库一致性问题:更新数据库后删除缓存不是强一致方案。在极端并发下,读线程可能在缓存删除前读到旧数据。对于大部分应用这个短暂的不一致可以接受。
-
缓存雪崩:如果所有数据同时过期,所有请求会同时打到数据库。解决方案:TTL 加随机偏移。
-
缓存预热:服务刚启动时缓存是空的,第一批请求会穿透到数据库。可以在启动时做一次初始化加载:
@PostConstruct
public void warmup() {
List<Course> courses = courseRepository.findAll();
redisTemplate.opsForValue().set("courses:all", courses, 30, TimeUnit.MINUTES);
log.info("缓存预热完成:加载了 {} 门课程", courses.size());
}
总结
一次简单的缓存集成带来了 6 倍的性能提升。这说明:
- 对于读多写少的接口,缓存是最立竿见影的优化手段
- 在优化代码之前,先考虑能否用缓存
- 需要关注一致性问题,确保缓存不会导致业务逻辑出错