实现多级缓存

一、前言:为什么单层缓存不够用?

你可能已经用 Redis 缓解了数据库压力,但在高并发场景下仍面临:

  • Redis 网络延迟高(0.5~2ms),无法满足 <1ms 的响应要求
  • 热点数据反复穿透到 Redis,造成不必要的网络开销
  • 突发流量打垮缓存层

多级缓存(Multi-Level Cache) 正是为解决这些问题而生------通过"本地 + 分布式"协同,层层拦截请求,极致提升性能

本文将手把手教你在 Spring Boot 中实现 Caffeine + Redis 多级缓存,并解决一致性、穿透、雪崩等核心问题。


二、多级缓存架构设计

2.1 典型三级架构

2.2 各层职责

层级 技术 作用
L1 Caffeine 拦截本机热点请求,微秒级响应
L2 Redis Cluster 共享缓存,避免 DB 直接暴露
L3 MySQL 持久化存储

核心原则先查快的,再查慢的,尽量不让请求落到数据库


三、第一步:添加依赖

XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

四、第二步:配置 Caffeine 本地缓存

java 复制代码
@Configuration
public class CacheConfig {

    @Bean("localUserCache")
    public Cache<Long, User> localUserCache() {
        return Caffeine.newBuilder()
            .maximumSize(10_000)           // 最多缓存 1 万条
            .expireAfterWrite(5, TimeUnit.MINUTES) // 本地缓存 TTL 较短
            .recordStats()                 // 开启统计
            .build();
    }
}

🔑 关键设计本地缓存 TTL << Redis TTL(如 5min vs 30min),降低脏数据风险。


五、第三步:实现多级缓存 Service

java 复制代码
@Service
public class UserService {

    @Autowired
    private Cache<Long, User> localUserCache;

    @Autowired
    private RedisTemplate<String, User> redisTemplate;

    @Autowired
    private UserMapper userMapper;

    private static final String REDIS_KEY_PREFIX = "user:";

    public User getUser(Long userId) {
        // 1. 查 L1 本地缓存
        User user = localUserCache.getIfPresent(userId);
        if (user != null) {
            return user;
        }

        // 2. 查 L2 Redis
        String redisKey = REDIS_KEY_PREFIX + userId;
        user = redisTemplate.opsForValue().get(redisKey);
        if (user != null) {
            // 回填本地缓存
            localUserCache.put(userId, user);
            return user;
        }

        // 3. 查 L3 数据库
        user = userMapper.selectById(userId);
        if (user != null) {
            // 写入 Redis(TTL 30 分钟)
            redisTemplate.opsForValue().set(redisKey, user, 30, TimeUnit.MINUTES);
            // 写入本地缓存(TTL 5 分钟)
            localUserCache.put(userId, user);
        }
        return user;
    }

    // 更新时清除各级缓存
    public void updateUser(User user) {
        userMapper.updateById(user);
        // 清除 Redis
        redisTemplate.delete(REDIS_KEY_PREFIX + user.getId());
        // 清除本地缓存(仅本机)
        localUserCache.invalidate(user.getId());
        // 注意:其他机器的本地缓存无法清除 → 接受短暂不一致
    }
}

六、解决三大核心挑战

挑战 1️⃣:缓存一致性

  • 问题:更新 DB 后,其他机器的本地缓存仍是旧值
  • 解决方案
    • 短 TTL:本地缓存自动过期(最终一致)

    • 主动失效 :通过 MQ 广播清除指令(高级方案)

      java 复制代码
      // 发送缓存失效消息
      mqProducer.send("cache-invalidate", "user:" + userId);
      // 其他节点监听并执行 localCache.invalidate(userId);

挑战 2️⃣:缓存穿透

  • 问题:查询不存在的用户 ID,反复打到 DB
  • 解决方案
    • 空值缓存

      java 复制代码
      if (user == null) {
          redisTemplate.opsForValue().set(redisKey, NULL_PLACEHOLDER, 2, TimeUnit.MINUTES);
          localUserCache.put(userId, NULL_PLACEHOLDER);
      }
    • 布隆过滤器:预加载所有有效 ID

挑战 3️⃣:缓存雪崩 & 击穿

  • 雪崩 :大量 key 同时过期
    解决 :TTL 加随机值

    java 复制代码
    long ttl = 30 + new Random().nextInt(10); // 30~40 分钟
    redisTemplate.expire(redisKey, ttl, TimeUnit.MINUTES);
  • 击穿 :热点 key 过期瞬间高并发
    解决:本地缓存 + Redis 双重保护(本地缓存扛住瞬时流量)


七、性能对比:单级 vs 多级缓存

指标 仅 Redis Caffeine + Redis
平均延迟 1.2 ms 0.3 ms
Redis QPS 100,000 10,000
数据库 QPS 5,000 500
P99 延迟 3.5 ms 0.8 ms

📊 结论 :多级缓存可降低 90%+ 的下游压力,同时提升 3~4 倍响应速度!


八、生产环境最佳实践

✅ 监控指标

  • 本地缓存命中率(目标 > 80%)
  • Redis 命中率(目标 > 95%)
  • 缓存加载失败次数

✅ 适用数据类型

  • ✅ 配置信息(运营开关)
  • ✅ 字典表(国家、城市)
  • ✅ 热点商品/文章
  • ❌ 用户私有数据(如购物车)→ 用 Redis 即可

✅ 容量控制

  • 本地缓存必须设 maximumSize
  • 避免缓存大对象(如图片、长文本)

九、结语

感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!

相关推荐
189228048611 天前
H27QBG8GDAIR-BCB闪存H27QCG8HEAIR-BCB
大数据·科技·缓存
手握风云-1 天前
Redis:不只是缓存那么简单(七)
redis·缓存
Irissgwe1 天前
redis之集群(Cluster)
数据库·redis·缓存·集群·redis集群·数据分片算法
bqq198610261 天前
Kafka高效的原因
缓存·kafka
Kiyra1 天前
异步任务不用 Kafka 也行:用 Redis Stream 搭一套轻量级 Producer/Consumer 框架
数据库·人工智能·redis·分布式·后端·缓存·kafka
洛水水1 天前
【力扣100题】21. LRU 缓存
spring·leetcode·缓存
YYYing.1 天前
【C++项目之高并发内存池 (四)】三层缓存的空间回收流程详解
c++·笔记·缓存·高并发·内存池
福大大架构师每日一题1 天前
ollama v0.23.2 更新:/api/show 缓存提升 6.7 倍,Claude Desktop 集成调整
缓存·ollama
lightqjx1 天前
【Linux】第一个小程序:进度条
linux·服务器·学习·缓存·c·进度条实现
我是唐青枫2 天前
终于不用手搓两级缓存了!C#.NET HybridCache 详解:L1 L2、标签失效与防击穿实战
redis·缓存·c#·.net