实现多级缓存

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

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

九、结语

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

相关推荐
香蕉鼠片17 小时前
Redis
数据库·redis·缓存
小臭希17 小时前
Redis(NoSQL数据库,Linux-Ubuntu环境下)
数据库·redis·缓存
神の愛19 小时前
mybatis什么时候不走 缓存??
spring·缓存·mybatis
014-code20 小时前
Redis 删除缓存失败怎么办?重试、死信、补偿的工程化方案
数据库·redis·缓存
rannn_11120 小时前
【Redis|高级篇1】分布式缓存|持久化(RDB、AOF)、主从集群、哨兵、分片集群
java·redis·分布式·后端·缓存
難釋懷21 小时前
认识Canal
缓存
難釋懷2 天前
缓存同步
spring·缓存·mybatis
呼啦啦5612 天前
C++vector
java·c++·缓存
刘~浪地球2 天前
数据库与缓存--分库分表实战指南
网络·数据库·缓存
深蓝电商API2 天前
Redis在海淘场景下的缓存策略设计
数据库·redis·缓存·海淘