实现多级缓存

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

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

九、结语

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

相关推荐
白露与泡影6 小时前
Redis 缓存实战:雪崩、穿透、击穿一次讲透
数据库·redis·缓存
snow@li12 小时前
Redis:数据库语法速查表 / 全景梳理
数据库·redis·缓存
Alluxio13 小时前
Alluxio + Anyscale Ray框架,实现跨区域训练数据读取速度20倍提升
人工智能·分布式·机器学习·缓存·ai
手握风云-20 小时前
Redis:不只是缓存那么简单(十四)
redis·缓存
手握风云-2 天前
Redis:不只是缓存那么简单(十五)
数据库·redis·缓存
谙忆2 天前
图片CDN与边缘优化实战:缓存策略、图片处理型CDN与动态裁剪
java·spring·缓存
小杍随笔2 天前
【 .npmrc 终极配置指南:统一管理 npm/pnpm 缓存与全局目录,告别磁盘混乱】
前端·缓存·npm
IvorySQL2 天前
PG 日报|新增 VFD 缓存监控视图,精细化数据库调优
大数据·数据库·人工智能·缓存·postgresql·区块链
吴声子夜歌2 天前
Redis 6.x——监控指标
数据库·redis·缓存
heimeiyingwang2 天前
【架构实战】缓存一致性:Cache Aside与双写问题的破解
spring·缓存·架构