实现多级缓存

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

你可能已经用 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
  • 避免缓存大对象(如图片、长文本)

九、结语

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

相关推荐
ShineWinsu11 小时前
对于Redis:缓存的解析
java·redis·缓存·缓存穿透·缓存击穿·缓存雪崩·缓存预热
害人终害己11 小时前
redis修改密码的地方在哪里
数据库·redis·缓存
小静AI工程实验室11 小时前
robots.txt 不是门禁:RFC 9309 规则、缓存与 Python 爬虫的 10 个边界
爬虫·python·缓存
for_ever_love__11 小时前
缓存穿透、击穿、雪崩:三个经典问题与完整解决方案
java·数据库·redis·缓存·哈希算法·布隆过滤器·雪崩
hweiyu0021 小时前
Redis命令:HSTRLEN
redis·缓存
ShineWinsu1 天前
对于Redis:主从复制的解析
linux·数据库·c++·redis·缓存·面试·主从复制
ShineWinsu1 天前
对于Redis:事务的解析
数据库·redis·mysql·缓存·面试·事务·acid
JavaPub-rodert1 天前
公共 Docker 镜像源总失效?用 Harbor Proxy Cache 自建 Docker Hub 镜像缓存
java·缓存·docker
hweiyu002 天前
Redis命令:HRANDFIELD
redis·缓存
sinat_286945192 天前
大模型推理:部署方式与性能优化思路
人工智能·算法·缓存·chatgpt·性能优化