4. 缓存基础:本地缓存(Caffeine)与分布式缓存(Redis)在 Spring Boot 中的集成
在限流熔断治理中,缓存是降低下游压力、提升响应速度的核心手段。然而,本地缓存与分布式缓存在一致性模型、失效策略和集成方式上存在显著差异。本章聚焦 Caffeine(本地)与 Redis(分布式)在 Spring Boot 中的标准化集成,为后续缓存一致性错误处理奠定基础。
4.1 缓存选型对比:Caffeine vs. Redis
| 维度 | Caffeine(本地缓存) | Redis(分布式缓存) |
|---|---|---|
| 存储位置 | JVM 堆内内存 | 独立进程 / 外部服务器 |
| 访问延迟 | 纳秒级(无网络开销) | 微秒~毫秒级(网络 RTT) |
| 容量上限 | 受限于堆大小(通常 GB 级) | 受限于服务器内存(TB 级) |
| 一致性模型 | 单机强一致(同 JVM 内) | 最终一致(主从/集群模式) |
| 失效策略 | 基于大小、时间、引用 | TTL、LRU、LFU、惰性删除 |
| 典型场景 | 热点数据、配置、令牌桶状态 | 全局共享、跨实例状态、分布式锁 |
选型建议:限流熔断场景中,本地缓存适合存储每个实例独立的计数器、令牌桶状态;分布式缓存适合存储全局黑名单、限流规则配置、熔断开关状态。两者常组合使用,形成多级缓存架构。
4.2 Caffeine 本地缓存集成
4.2.1 依赖与配置
// pom.xml
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
// application.yml
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=500,expireAfterWrite=60s
4.2.2 手动创建 Caffeine 缓存(推荐用于限流)
@Configuration
public class CacheConfig {
@Bean
public Cache<String, RateLimitState> rateLimitCache() {
return Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.recordStats()
.build();
}
}
// 使用示例
@Service
public class RateLimitService {
@Autowired
private Cache<String, RateLimitState> rateLimitCache;
public boolean tryAcquire(String key) {
RateLimitState state = rateLimitCache.get(key, k -> new RateLimitState(100));
return state.tryAcquire();
}
}
4.2.3 关键参数说明
| 参数 | 说明 | 推荐值(限流场景) |
|---|---|---|
maximumSize |
最大条目数,基于 W-TinyLFU 淘汰 | 10,000 ~ 100,000 |
expireAfterWrite |
写入后过期时间 | 1 ~ 5 分钟 |
expireAfterAccess |
最后一次访问后过期时间 | 30 ~ 60 秒 |
recordStats |
开启命中率统计 | 生产环境建议开启 |
注意:本地缓存在多实例部署时,每个 JVM 持有独立副本。若限流状态仅依赖本地缓存,则总限流阈值 = 单实例阈值 × 实例数。如需全局精确限流,必须结合 Redis 或分布式协调组件。
4.3 Redis 分布式缓存集成
4.3.1 依赖与配置
// pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
// application.yml
spring:
redis:
host: 192.168.1.100
port: 6379
password: ${REDIS_PASSWORD}
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
4.3.2 RedisTemplate 与 StringRedisTemplate
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 Jackson2JsonRedisSerializer 序列化 value
Jackson2JsonRedisSerializer<Object> serializer =
new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
template.setKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
// 限流计数器操作示例
@Service
public class RedisRateLimitService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
public boolean incrementAndCheck(String key, int limit, long windowSeconds) {
Long current = stringRedisTemplate.opsForValue().increment(key);
if (current == 1) {
stringRedisTemplate.expire(key, windowSeconds, TimeUnit.SECONDS);
}
return current <= limit;
}
}
4.3.3 Redis 数据结构选型
| 数据结构 | 适用场景 | 限流熔断示例 |
|---|---|---|
| String | 计数器、简单 KV | 滑动窗口计数器、令牌桶剩余量 |
| Hash | 结构化对象 | 熔断器状态(状态、失败次数、半开时间) |
| ZSet | 滑动窗口、时间排序 | 精确滑动窗口限流(时间戳作为 score) |
| List | 队列、FIFO | 请求排队、削峰填谷 |
4.4 多级缓存架构设计
在限流熔断治理中,推荐采用 Caffeine(L1) + Redis(L2) 的多级缓存架构,兼顾性能与一致性。
核心原则:
- 读流程:先查 Caffeine → 命中直接返回;未命中查 Redis → 回填 Caffeine。
- 写流程:先更新 Redis → 异步失效 Caffeine(或主动删除本地缓存条目)。
- 一致性兜底:本地缓存设置较短的 TTL(如 5~10 秒),容忍短暂不一致。
4.5 缓存一致性错误处理策略
多级缓存必然引入一致性问题。在限流熔断场景中,常见的缓存一致性错误包括:
- 本地缓存脏读:实例 A 更新了限流阈值,实例 B 的本地缓存仍为旧值。
- Redis 主从延迟:写入主节点后,从节点未同步,读取到旧值。
- 缓存穿透:大量请求查询不存在的数据,直接打到数据库。
- 缓存雪崩:大量缓存同时过期,导致瞬时压力集中。
限流场景下的容忍策略:限流状态允许秒级不一致(多放过或少放过几个请求不影响整体稳定性)。因此,本地缓存 TTL 可设为 5~10 秒,Redis 作为权威源,定期拉取或通过 Pub/Sub 推送变更。
4.6 集成最佳实践清单
- 本地缓存优先:限流状态、令牌桶等高频读写数据优先使用 Caffeine,降低 Redis 压力。
- Redis 做权威源:全局规则、黑名单、熔断开关等必须由 Redis 统一管理。
- 设置合理的 TTL:本地缓存 TTL 建议 5~30 秒,Redis 缓存 TTL 建议 1~5 分钟。
- 监控缓存命中率:通过 Caffeine 的
recordStats()和 Redis 的 INFO 命令监控。 - 兜底降级:Redis 不可用时,自动降级为纯本地缓存模式,保证限流不中断。
- 避免缓存穿透:对不存在的数据也缓存空值(TTL 较短),或使用布隆过滤器。