Redis和Caffeine如何选择?
面试场景
面试官:”你的项目中缓存用的是Redis还是本地缓存?为什么?”
这道题考察对不同缓存方案的理解和选型能力。
核心对比
| 对比维度 | Redis | Caffeine |
|---|---|---|
| 部署位置 | 远程服务 | JVM进程内 |
| 访问延迟 | 1-5ms | 纳秒级 |
| 容量上限 | 独立部署,可达TB | 受JVM堆限制 |
| 数据一致性 | 天然一致 | 多实例数据不一致 |
| 可用性 | 需高可用部署 | 随应用,无单点 |
| 适用场景 | 共享数据 | 热点数据 |
Redis的优势场景
1. 数据需要跨实例共享
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 实例1 │ │ 实例2 │ │ 实例3 │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ │ │
└──────┬──────┴──────┬──────┘
│ │
┌────┴─────────────┴────┐
│ Redis │
│ (数据全局一致) │
└───────────────────────┘
典型场景:
- Session共享
- 分布式锁
- 全局计数器
2. 数据量大
// 商品详情缓存,数据量可能达GB级
@Cacheable(value = "products", key = "#productId")
public Product getProduct(Long productId) {
return productMapper.findById(productId);
}
3. 需要丰富的数据结构
// 排行榜:使用ZSet
redis.opsForZSet().add("rank:sales", productId, score);
redis.opsForZSet().reverseRangeWithScores("rank:sales", 0, 9);
// 消息队列:使用List
redis.opsForList().leftPush("queue:orders", order);
redis.opsForList().rightPop("queue:orders");
// 位图统计:使用BitMap
redis.opsForValue().setBit("user:sign:" + userId, dayOfMonth, true);
Caffeine的优势场景
1. 超高频访问的热点数据
// 配置信息:访问频次高,变化少
@Bean
public Cache<String, Config> configCache() {
return Caffeine.newBuilder()
.maximumSize(100)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
}
2. 对延迟极度敏感
Redis访问:1-5ms
Caffeine访问:<0.01ms(纳秒级)
每一毫秒都重要的场景:
- 调用链路长的系统
- 高频接口(万级QPS)
- 实时竞价系统
3. 容忍短暂数据不一致
// 商品分类信息:各实例缓存不同步,但影响不大
Cache<Long, Category> categoryCache = Caffeine.newBuilder()
.maximumSize(500)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
最佳实践:多级缓存
架构设计
请求
│
├── L1: Caffeine(进程内)
│ 命中率:60%,延迟:<0.1ms
│
├── L2: Redis(分布式)
│ 命中率:35%,延迟:1-5ms
│
└── L3: 数据库
命中率:5%,延迟:10-100ms
代码实现
@Service
public class ProductService {
// L1: 本地缓存
private Cache<Long, Product> localCache = Caffeine.newBuilder()
.maximumSize(10000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build();
@Autowired
private RedisTemplate<String, Product> redisTemplate;
public Product getProduct(Long productId) {
// L1: 本地缓存
Product product = localCache.getIfPresent(productId);
if (product != null) {
return product;
}
// L2: Redis
String key = "product:" + productId;
product = redisTemplate.opsForValue().get(key);
if (product != null) {
localCache.put(productId, product);
return product;
}
// L3: 数据库
product = productMapper.findById(productId);
if (product != null) {
redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS);
localCache.put(productId, product);
}
return product;
}
}
缓存更新策略
// 数据变更时,清理各级缓存
public void updateProduct(Product product) {
productMapper.update(product);
// 清理Redis
redisTemplate.delete("product:" + product.getId());
// 清理本地缓存(通过MQ广播到所有实例)
kafkaTemplate.send("cache-evict",
new CacheEvictEvent("product", product.getId()));
}
@KafkaListener(topics = "cache-evict")
public void onCacheEvict(CacheEvictEvent event) {
if ("product".equals(event.getCacheName())) {
localCache.invalidate(event.getKey());
}
}
选型决策树
需要跨实例共享?
│
├── 是 → Redis
│
└── 否 → 数据量大吗?
│
├── 是 → Redis
│
└── 否 → 访问频率高吗?
│
├── 是 → Caffeine
│
└── 否 → Redis(简单统一)
常见问题处理
问题1:本地缓存数据不一致
方案:MQ广播失效消息
// 数据变更时广播
rocketMQTemplate.convertAndSend("cache-evict", key);
// 各实例监听并清理本地缓存
@RocketMQMessageListener(topic = "cache-evict")
public class CacheEvictListener implements RocketMQListener<String> {
public void onMessage(String key) {
localCache.invalidate(key);
}
}
问题2:本地缓存OOM
方案:合理设置容量上限
Caffeine.newBuilder()
.maximumSize(10000) // 数量限制
.maximumWeight(100_000_000) // 或权重限制(100MB)
.weigher((k, v) -> v.getBytes().length)
.build();
问题3:缓存穿透
方案:缓存空值
public Product getProduct(Long productId) {
Product product = cache.getIfPresent(productId);
if (product != null) {
return product == NULL_PRODUCT ? null : product;
}
product = productMapper.findById(productId);
if (product == null) {
cache.put(productId, NULL_PRODUCT); // 缓存空对象
} else {
cache.put(productId, product);
}
return product;
}
面试答题框架
选型依据:
- 共享性:Redis天然全局一致
- 性能:Caffeine纳秒级,Redis毫秒级
- 容量:Redis可达TB,Caffeine受堆限制
典型场景:
- Redis:Session、分布式锁、大容量缓存
- Caffeine:热点数据、配置信息、对延迟敏感
最佳实践:
- 多级缓存:L1 Caffeine + L2 Redis
- 一致性保障:MQ广播失效消息
总结
| 场景 | 推荐方案 |
|---|---|
| Session共享 | Redis |
| 分布式锁 | Redis |
| 热点商品 | Caffeine + Redis |
| 配置信息 | Caffeine |
| 排行榜 | Redis ZSet |
| 通用缓存 | 多级缓存 |