Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > Caffeine结合Redis实现多级缓存

Caffeine结合Redis空值缓存实现多级缓存

作者:三水不滴

本文介绍了SpringBoot整合Caffeine本地缓存+Redis分布式缓存+空值缓存的三级缓存方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

在高并发系统中,缓存是提升响应速度、减轻数据库压力的核心手段,但单一缓存方案往往难以应对复杂场景 —— 本地缓存缺乏分布式一致性,Redis 缓存存在网络开销,还可能遭遇穿透、雪崩、击穿等致命问题。本文基于实战案例,详解 SpringBoot 整合Caffeine 本地缓存 + Redis 分布式缓存 + 空值缓存的三级缓存方案,从架构设计到代码落地,构建高可用、低延迟的缓存体系。

一、多级缓存架构设计:为什么要 “三级联动”?

传统缓存方案要么依赖单一本地缓存(无法分布式共享),要么仅用 Redis(网络 IO 开销影响性能),而三级缓存架构通过 “本地缓存 + 分布式缓存 + 数据库” 的层级设计,实现了 “速度” 与 “一致性” 的平衡:

  1. 第一级:Caffeine 本地缓存基于 Java 内存的高性能缓存,读写延迟低至纳秒级,专门存储热点数据(如高频访问的商品信息、配置参数),避免重复查询 Redis 和数据库,提升核心接口响应速度。
  2. 第二级:Redis 分布式缓存分布式环境下的共享缓存,解决本地缓存数据不一致问题,同时承担 “中间缓冲” 角色,减少数据库直接访问压力。
  3. 第三级:数据库数据最终存储源,仅在缓存未命中时触发查询,保证数据可靠性。

核心优势

二、核心问题解决方案:三大缓存难题逐个击破

1. 缓存穿透:拦截无效查询

问题:恶意请求查询不存在的数据(如 ID=-1 的商品),导致缓存失效后直接穿透到数据库,引发性能问题。解决方案:空值缓存 + 布隆过滤器双重防护

2. 缓存雪崩:避免集中失效

问题:大量缓存数据在同一时间过期,或 Redis 集群宕机,导致所有请求瞬间涌向数据库,引发数据库雪崩。解决方案:随机过期时间 + 优雅降级

3. 缓存击穿:保护热点数据

问题:热点数据(如秒杀商品)缓存过期瞬间,大量并发请求穿透到数据库,导致数据库压力骤增。解决方案:热点数据预热 + 分布式锁

三、实战落地:SpringBoot 整合三级缓存

1. 依赖配置

首先引入核心依赖(Maven 示例),包含 SpringBoot 缓存 starter、Caffeine、Redis、Redisson(分布式锁):

<!-- SpringBoot缓存核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Caffeine本地缓存 -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>
<!-- Redis依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redisson分布式锁 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.3</version>
</dependency>

2. 核心配置文件(application.yml)

配置 Caffeine 缓存参数、Redis 连接信息、分布式锁等:

spring:
  # Redis配置
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 2
        max-wait: 1000ms
    timeout: 3000ms
  # 缓存配置
  cache:
    type: caffeine
    caffeine:
      # 初始容量、最大容量、过期时间(写入后30分钟过期)
      initial-capacity: 100
      maximum-size: 1000
      expire-after-write: 30m

# 自定义缓存配置
cache:
  # 空值缓存过期时间(5分钟)
  null-value-expire: 5m
  # 热点数据预热key前缀
  hot-data-prefix: "hot:"
  # 分布式锁前缀
  lock-prefix: "cache:lock:"

3. 核心代码实现

(1)缓存配置类:初始化 Caffeine 和 Redis 缓存

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class CacheConfig {

    // Caffeine缓存管理器(本地缓存)
    @Bean
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        // 配置Caffeine缓存参数:初始容量100,最大容量1000,写入后30分钟过期
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .initialCapacity(100)
                .maximumSize(1000)
                .expireAfterWrite(Duration.ofMinutes(30)));
        return cacheManager;
    }

    // Redis缓存管理器(分布式缓存)
    @Bean
    public RedisCacheManager redisCacheManager(RedisConnectionFactory connectionFactory) {
        // 序列化配置(避免Redis存储乱码)
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30)) // 默认过期时间30分钟
                .serializeKeysWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair
                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));

        // 自定义不同缓存的过期时间(如空值缓存5分钟)
        Map<String, RedisCacheConfiguration> cacheConfigs = new HashMap<>();
        cacheConfigs.put("nullValueCache", config.entryTtl(Duration.ofMinutes(5)));

        return RedisCacheManager.builder(connectionFactory)
                .cacheDefaults(config)
                .withInitialCacheConfigurations(cacheConfigs)
                .build();
    }
}

(2)缓存工具类:封装三级缓存查询逻辑

核心逻辑:先查 Caffeine→再查 Redis→最后查数据库,同时处理空值缓存、分布式锁、缓存更新:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Component
public class CacheUtil {

    @Resource
    private CacheManager caffeineCacheManager;
    @Resource
    private RedisCacheManager redisCacheManager;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private BloomFilterUtil bloomFilterUtil; // 布隆过滤器工具类

    // 缓存查询核心方法:key-缓存键,clazz-返回类型,dbLoader-数据库查询逻辑
    public <T> T getCache(String key, Class<T> clazz, DataLoader<T> dbLoader) {
        // 1. 布隆过滤器校验:无效key直接返回null
        if (!bloomFilterUtil.contains(key)) {
            return null;
        }

        // 2. 查询Caffeine本地缓存
        Cache caffeineCache = caffeineCacheManager.getCache("localCache");
        T localValue = caffeineCache.get(key, clazz);
        if (localValue != null) {
            return localValue;
        }

        // 3. 查询Redis分布式缓存
        Cache redisCache = redisCacheManager.getCache("redisCache");
        T redisValue = redisCache.get(key, clazz);
        if (redisValue != null) {
            // Redis命中,同步到本地缓存
            caffeineCache.put(key, redisValue);
            return redisValue;
        }

        // 4. 缓存未命中,分布式锁控制数据库查询
        RLock lock = redissonClient.getLock("cache:lock:" + key);
        try {
            // 尝试获取锁,最多等待3秒,持有锁10秒
            if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
                // 再次查询Redis(防止其他线程已更新缓存)
                redisValue = redisCache.get(key, clazz);
                if (redisValue != null) {
                    caffeineCache.put(key, redisValue);
                    return redisValue;
                }

                // 5. 查询数据库
                T dbValue = dbLoader.load();
                if (dbValue != null) {
                    // 数据库有结果,更新各级缓存
                    redisCache.put(key, dbValue);
                    caffeineCache.put(key, dbValue);
                } else {
                    // 数据库无结果,缓存空值(5分钟过期)
                    Cache nullValueCache = redisCacheManager.getCache("nullValueCache");
                    nullValueCache.put(key, null);
                    caffeineCache.put(key, null);
                }
                return dbValue;
            } else {
                // 获取锁失败,返回默认值或抛出异常
                throw new RuntimeException("缓存更新繁忙,请稍后重试");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return null;
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    // 数据加载函数式接口(封装数据库查询逻辑)
    @FunctionalInterface
    public interface DataLoader<T> {
        T load();
    }
}

(3)缓存更新与清除:保障数据一致性

当数据库数据发生变更(新增、修改、删除)时,需同步清除各级缓存,避免数据不一致:

// 缓存清除方法(用于数据库更新后)
public void clearCache(String key) {
    // 1. 清除本地缓存
    Cache caffeineCache = caffeineCacheManager.getCache("localCache");
    caffeineCache.evict(key);
    // 2. 清除Redis缓存
    Cache redisCache = redisCacheManager.getCache("redisCache");
    redisCache.evict(key);
    // 3. 清除空值缓存
    Cache nullValueCache = redisCacheManager.getCache("nullValueCache");
    nullValueCache.evict(key);
}

(4)热点数据预热:系统启动时加载

通过CommandLineRunner实现系统启动时预热热点数据,避免缓存冷启动:

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;

@Component
public class HotDataPreloader implements CommandLineRunner {

    @Resource
    private CacheUtil cacheUtil;
    @Resource
    private ProductMapper productMapper; // 数据库DAO层

    @Override
    public void run(String... args) throws Exception {
        // 加载热点商品数据(如销量前100的商品)
        List<Product> hotProducts = productMapper.selectHotProducts(100);
        for (Product product : hotProducts) {
            String key = "product:" + product.getId();
            // 存入本地缓存和Redis
            cacheUtil.caffeineCacheManager.getCache("localCache").put(key, product);
            cacheUtil.redisCacheManager.getCache("redisCache").put(key, product);
        }
        System.out.println("热点数据预热完成,共加载" + hotProducts.size() + "条数据");
    }
}

四、优化与监控:让缓存体系更稳定

1. 配置优化建议

2. 监控与告警

3. 注意事项

五、总结

SpringBoot+Caffeine+Redis + 空值缓存的三级缓存方案,通过 “本地缓存提效、分布式缓存保一致、空值缓存防穿透” 的设计,完美解决了高并发场景下的缓存核心难题。该方案不仅能将接口响应时间压缩至毫秒级,还能大幅降低数据库压力,同时具备故障隔离、优雅降级的高可用特性,适用于电商、支付、社交等各类高并发系统。

实际落地时,可根据业务场景灵活调整缓存参数(如过期时间、最大容量)和预热策略,结合监控工具持续优化,让缓存体系真正成为系统的 “性能加速器”。

到此这篇关于Caffeine结合Redis空值缓存实现多级缓存的文章就介绍到这了,更多相关Caffeine结合Redis实现多级缓存内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文