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 秒),容忍短暂不一致。
多级缓存读写流程 多级缓存读写流程(Caffeine + Redis) 应用服务 Caffeine 本地缓存 (L1) Redis 分布式缓存 (L2) 数据库 / 后端服务 ① 读请求 ② 未命中,查 Redis ③ 未命中,查 DB ④ 写请求 ⑤ 异步失效 L1 说明: 读请求优先命中 L1,未命中穿透 L2 并回填 L1;写请求先更新 L2,再异步失效 L1,容忍短暂不一致。

4.5 缓存一致性错误处理策略

多级缓存必然引入一致性问题。在限流熔断场景中,常见的缓存一致性错误包括:

  • 本地缓存脏读:实例 A 更新了限流阈值,实例 B 的本地缓存仍为旧值。
  • Redis 主从延迟:写入主节点后,从节点未同步,读取到旧值。
  • 缓存穿透:大量请求查询不存在的数据,直接打到数据库。
  • 缓存雪崩:大量缓存同时过期,导致瞬时压力集中。
限流场景下的容忍策略:限流状态允许秒级不一致(多放过或少放过几个请求不影响整体稳定性)。因此,本地缓存 TTL 可设为 5~10 秒,Redis 作为权威源,定期拉取或通过 Pub/Sub 推送变更。
缓存一致性错误类型与处理 缓存一致性错误类型与处理策略 本地缓存脏读 Redis 主从延迟 缓存穿透 缓存雪崩 短 TTL + Pub/Sub 失效 读写分离 + 强制读主 布隆过滤器 + 空值缓存 过期时间加随机偏移 限流场景一致性容忍原则: • 允许秒级不一致,不追求强一致。 • 优先保证可用性和性能,一致性通过异步补偿和短 TTL 兜底。

4.6 集成最佳实践清单

  1. 本地缓存优先:限流状态、令牌桶等高频读写数据优先使用 Caffeine,降低 Redis 压力。
  2. Redis 做权威源:全局规则、黑名单、熔断开关等必须由 Redis 统一管理。
  3. 设置合理的 TTL:本地缓存 TTL 建议 5~30 秒,Redis 缓存 TTL 建议 1~5 分钟。
  4. 监控缓存命中率:通过 Caffeine 的 recordStats() 和 Redis 的 INFO 命令监控。
  5. 兜底降级:Redis 不可用时,自动降级为纯本地缓存模式,保证限流不中断。
  6. 避免缓存穿透:对不存在的数据也缓存空值(TTL 较短),或使用布隆过滤器。