4. 本地缓存 vs 分布式缓存:Caffeine、Redis、Memcached 选型对比
在微服务与分布式架构中,缓存选型直接影响系统的响应速度、数据一致性以及运维复杂度。本章从服务边界出发,系统对比 Caffeine(本地缓存)、Redis(分布式缓存) 与 Memcached(分布式缓存) 三者的核心差异,并给出基于业务场景的选型决策树。
4.1 缓存分类:本地 vs 分布式
缓存按部署位置分为两类:
- 本地缓存(进程内缓存):与应用运行在同一 JVM 进程中,如 Caffeine、Guava Cache、Ehcache。访问延迟极低(纳秒级),但数据无法跨节点共享。
- 分布式缓存(独立中间件):部署为独立服务,应用通过网络访问,如 Redis、Memcached。数据可被所有服务实例共享,但引入网络开销(毫秒级)。
架构原则:本地缓存适合“读多写少、数据量小、一致性要求低”的场景;分布式缓存适合“数据需共享、容量大、一致性要求高”的场景。两者常组合为多级缓存。
4.2 三款缓存核心特性对比
| 对比维度 | Caffeine | Redis | Memcached |
|---|---|---|---|
| 类型 | 本地进程内缓存 | 分布式键值存储 | 分布式键值存储 |
| 数据存储 | JVM 堆内存 | 独立进程内存 + 可选持久化 | 独立进程内存 |
| 访问延迟 | 纳秒级(< 100ns) | 毫秒级(0.1-5ms) | 毫秒级(0.1-5ms) |
| 数据结构 | 键值对(Value 为对象) | String、List、Set、ZSet、Hash、Stream 等 | 仅 String(键值对) |
| 持久化 | 不支持 | RDB / AOF 持久化 | 不支持 |
| 过期策略 | 基于 Window TinyLFU 淘汰 | 惰性删除 + 定期删除 + 8 种淘汰策略 | LRU 淘汰 + 过期时间 |
| 集群模式 | 不支持(单进程) | 主从、哨兵、Cluster 分片 | 客户端分片(无原生集群) |
| 数据一致性 | 强一致(单进程) | 最终一致(主从异步复制) | 最终一致(无复制机制) |
| 典型容量 | 几十 MB ~ 几 GB | 几 GB ~ 几百 GB | 几 GB ~ 几十 GB |
| Java 客户端 | Caffeine 原生 API | Jedis、Lettuce、Redisson | SpyMemcached、XMemcached |
关键结论:Caffeine 在延迟上碾压分布式缓存,但无法跨进程共享;Redis 在数据结构丰富度和持久化能力上远超 Memcached;Memcached 仅适合极简的纯 KV 缓存场景。
4.3 选型决策树
根据业务需求,按以下步骤决策:
- 是否需要跨服务实例共享数据?
- 否 → 使用 Caffeine(如:单实例配置、用户会话本地副本)
- 是 → 进入下一步
- 是否需要复杂数据结构(List、Set、ZSet、Hash)?
- 是 → 使用 Redis(如:排行榜、消息队列、计数器)
- 否 → 进入下一步
- 是否需要数据持久化或高可用?
- 是 → 使用 Redis(如:缓存不可丢失、主从切换)
- 否 → 使用 Memcached(如:纯临时数据、简单 KV 加速)
避坑提示:不要将 Memcached 用于需要原子操作(如 INCR/DECR 之外的复杂运算)或数据恢复的场景。Redis 的持久化功能会带来额外的写性能开销,在纯缓存场景下建议关闭 AOF 或使用 RDB 策略。
4.4 多级缓存架构:本地 + 分布式组合
在 Spring Boot 实践中,最成熟的方案是 Caffeine(L1) + Redis(L2) 多级缓存:
- L1(Caffeine):抗住 80% 以上的热点读请求,延迟 < 1μs。
- L2(Redis):提供数据共享与一致性保障,延迟 1-5ms。
- 数据库:作为最终数据源,兜底未命中数据。
4.5 Spring Boot 集成示例
以下展示 Caffeine + Redis 双级缓存的配置片段:
// 1. Caffeine 配置(本地缓存)
@Configuration
public class CaffeineConfig {
@Bean
public Cache<String, Object> caffeineCache() {
return Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats()
.build();
}
}
// 2. Redis 配置(分布式缓存)
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(
RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
// 3. 多级缓存服务
@Service
public class MultiLevelCacheService {
@Autowired
private Cache<String, Object> caffeineCache;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Object get(String key) {
// 先查 L1
Object val = caffeineCache.getIfPresent(key);
if (val != null) return val;
// 再查 L2
val = redisTemplate.opsForValue().get(key);
if (val != null) {
caffeineCache.put(key, val); // 回填 L1
return val;
}
// 查数据库(略)
return null;
}
}
4.6 选型总结矩阵
| 业务场景 | 推荐方案 | 理由 |
|---|---|---|
| 单实例热点数据(如配置、字典) | Caffeine | 零网络开销,纳秒级响应 |
| 分布式会话 / 用户 Token | Redis | 需跨实例共享,支持过期与持久化 |
| 排行榜 / 计数器 / 消息队列 | Redis | 原生 ZSet、List、Stream 数据结构 |
| 纯 KV 加速,无持久化要求 | Memcached | 简单高效,内存利用率高 |
| 高并发读,容忍短时不一致 | Caffeine + Redis | 多级缓存,兼顾性能与共享 |
架构演进建议:初期单实例使用 Caffeine 即可;随着服务拆分,引入 Redis 作为共享缓存层;当 Redis 成为瓶颈时,再考虑在 Redis 前叠加 Caffeine 作为 L1 热点缓存。不要一开始就追求多级缓存,避免过度设计。