在后端开发中,API 接口的性能往往取决于数据存储与缓存策略。Spring Boot 生态成熟,MySQL 持久化可靠,但当并发量上来时,单一数据库容易成为瓶颈。引入 Redis(分布式)和 Caffeine(本地)构建多级缓存,是提升系统吞吐量的关键手段。
基础架构:Spring+MySQL
基于 Spring Boot 和 MyBatis-Plus 搭建 API 服务是标准做法。核心链路是 Controller 接收请求,Service 处理业务,Mapper 操作 MySQL。这种模式开发效率高、数据一致性好,但在高并发下,查询延迟和连接池耗尽是常见问题。
核心依赖
Maven 配置需包含 Web 启动器、ORM 工具、数据库驱动及连接池:
<!-- Spring Boot Web 核心 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus 启动器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Druid 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
实现流程
- 配置数据源:在
application.yml中指定 MySQL 地址、账号及连接池参数; - 实体映射:定义 Entity 类对应表结构,利用注解简化 ORM 配置;
- DAO 层:继承
BaseMapper即可实现 CRUD,无需手写 SQL; - Service 层:封装校验、权限等逻辑,调用 Mapper;
- Controller 层:通过
@RestController暴露接口路径。
多级缓存:Redis+Caffeine
多级缓存的核心是'近缓存优先'。Caffeine 驻留进程内,访问极快但无法共享;Redis 独立部署,支持集群但受网络影响。两者结合能兼顾速度与一致性。
组件特性对比
| 特性 | Caffeine | Redis |
|---|---|---|
| 存储位置 | 应用进程内 | 独立服务器/集群 |
| 访问延迟 | 微秒级 | 毫秒级 |
| 分布式支持 | 不支持 | 支持 |
| 容量限制 | JVM 堆内存 | GB/TB 级 |
| 持久化 | 无 | RDB/AOF |
工作流程
整合 Spring Cache 抽象后,读取流程如下:
- 查本地:先查 Caffeine,命中直接返回;
- 查分布:未命中则查 Redis,命中后回填 Caffeine;
- 查库:均未命中则查 MySQL,结果同步回 Redis 和 Caffeine;
- 更新策略:写操作先更库,再删缓存,防止不一致。
![图示:多级缓存交互流程]
下面是一个实际的分页列表接口示例,展示了如何手动控制多级缓存逻辑:
/**
* 分页图片列表 VO 多级缓存
* 逻辑:本地 -> 分布式 -> 数据库
*/
@Resource
private ValueOperations<String, String> stringValueWithRedisTemplate;
@Resource
private Cache<String, String> localCacheWithCaffeine;
@PostMapping("/list/page/vo/multi-level")
public BaseResponse<Page<PictureVo>> listPictureVOByPageWithMultiLevel(
@RequestBody PictureQueryRequest pictureQueryRequest,
HttpServletRequest request) {
// 1. 构造 Key
String queryCondition = JSONUtil.toJsonStr(pictureQueryRequest);
String cacheKey = String.format("hututu:list:%s", DigestUtils.md5DigestAsHex(queryCondition.getBytes()));
// 2. 查本地缓存 (Caffeine)
String cacheValue = localCacheWithCaffeine.getIfPresent(cacheKey);
if (cacheValue != null) {
return ResponseUtil.success(JSONUtil.toBean(cacheValue, Page.class));
}
// 3. 查分布式缓存 (Redis)
cacheValue = stringValueWithRedisTemplate.get(cacheKey);
if (cacheValue != null) {
// 回填本地缓存
localCacheWithCaffeine.put(cacheKey, cacheValue);
return ResponseUtil.success(JSONUtil.toBean(cacheValue, Page.class));
}
// 4. 查数据库
Page<Picture> picturePage = pictureService.page(new Page<>(current, pageSize), ...);
Page<PictureVo> result = pictureService.getPictureVOPage(picturePage, request);
// 5. 写入缓存
String jsonResult = JSONUtil.toJsonStr(result);
localCacheWithCaffeine.put(cacheKey, jsonResult);
stringValueWithRedisTemplate.set(cacheKey, jsonResult, 300, TimeUnit.SECONDS);
return ResponseUtil.success(result);
}
优劣势分析
任何方案都有代价,多级缓存也不例外。
优势
- 极致性能:Caffeine 的微秒级响应覆盖了大部分高频读,大幅减少 Redis 网络开销。实测显示,商品详情查询响应可从 50ms 降至 5ms 以内。
- 容灾能力:Redis 挂掉时,Caffeine 可作为兜底,保证核心功能可用;单实例故障时,其他节点仍能从 Redis 获取数据。
- 分布式适配:微服务场景下,Redis 保证多实例间状态一致,Caffeine 减少跨服务竞争。
劣势
- 复杂度增加:需要维护两套缓存配置,还要处理双写一致性问题。
- 资源消耗:JVM 堆内存被占用,且需额外部署 Redis 集群。
- 一致性风险:服务 A 更新了缓存,服务 B 的本地缓存可能还是旧的,直到过期。虽然可以用 Pub/Sub 通知失效,但这又增加了系统耦合。
适用场景与注意点
核心场景
适合高频读、低频写的业务,如商品详情、字典配置。也适用于高并发核心接口,如秒杀活动,用本地缓存抗住瞬时流量。
实践注意点
- 配置策略:Caffeine 建议设置 LRU 淘汰策略和最大容量,避免 OOM;Redis 开启 AOF 持久化,热点 Key 设置随机过期时间防雪崩。
- 一致性保障:推荐'先更库,再删缓存'。强一致性需求可引入消息队列通知各节点失效本地缓存。
- 常见问题防护:
- 穿透:对空值也缓存,或加布隆过滤器。
- 击穿:热点 Key 永不过期,配合后台刷新或互斥锁。
- 雪崩:过期时间加随机偏移,避免集体失效。
- 监控运维:接入 Actuator 监控命中率,Redis 内存使用率超阈值及时告警。
总结
Spring+MySQL 是基石,Redis+Caffeine 多级缓存则是应对高并发的利器。它通过'本地 + 分布式'的组合,在性能和一致性之间找到了平衡点。实际落地时,不要盲目上架构,需根据业务读写比例权衡。做好配置、监控和一致性策略,才能让这套体系真正稳定运行。


