多级缓存框架(Redis + Caffeine)完整指南

多级缓存框架(Redis + Caffeine)完整指南

一、框架核心概述(是什么)

这是一套 本地缓存(Caffeine)+ 分布式缓存(Redis)的多级缓存框架,基于 Spring 生态实现,核心目标是兼顾缓存访问性能与分布式环境下的数据一致性。

核心特性

  • 多级缓存架构:一级缓存(本地 Caffeine)提升响应速度,二级缓存(Redis)保证分布式一致性
  • 灵活配置:支持过期时间、过期模式、NULL 值缓存、本地缓存容量限制等
  • 动态 Key 生成:基于 SpEL 表达式实现缓存 Key 动态拼接
  • 并发安全:内置分布式锁(Redisson),解决缓存穿透/击穿/雪崩问题
  • 完整 API:支持缓存增删改查、原子操作、批量处理等

技术栈依赖

  • 核心框架:Spring Boot、Spring Context
  • 缓存组件:Caffeine(本地缓存)、Redis(分布式缓存)
  • 分布式锁:Redisson
  • 序列化:Jackson
  • 表达式解析:Spring EL

二、框架核心价值(为什么用)

1. 解决的核心问题

问题场景 框架解决方案
缓存穿透(查询不存在的数据) 支持 NULL 值缓存 + 分布式锁防重复查询
缓存击穿(热点 Key 过期) 分布式锁 + 缓存自动续期
缓存雪崩(大量 Key 同时过期) 过期时间随机偏移 + 多级缓存降级
分布式缓存一致性 Redis 集中存储 + 缓存更新同步
本地缓存性能瓶颈 Caffeine 本地缓存(O(1) 访问速度)

2. 相比单一缓存的优势

  • 比纯 Redis 缓存:减少网络 IO 开销,本地缓存响应时间提升 10-100 倍
  • 比纯本地缓存:支持分布式部署,避免节点间数据不一致
  • 比 Spring Cache 原生:支持更灵活的配置(过期模式、容量限制)和更完善的并发控制

三、快速上手(怎么做)

1. 依赖配置(Maven)

xml 复制代码
<!-- Spring Boot 基础依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Caffeine 本地缓存 -->
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.8</version>
</dependency>

<!-- Redisson 分布式锁 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.25.0</version>
</dependency>

<!-- Jackson 序列化 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

2. 配置文件(application.yml)

yaml 复制代码
spring:
  application:
    name: demo-app # 用于 Redis Key 前缀
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    timeout: 3000ms
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 2

3. 基础使用示例

3.1 编程式使用(直接操作缓存 API)
java 复制代码
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class UserService {

    // 注入缓存管理器
    @Resource
    private CacheManager cacheManager;

    // 缓存配置(可通过 @ConfigurationProperties 注入)
    private final MultiCacheConfig userCacheConfig = new MultiCacheConfig(
        30, TimeUnit.MINUTES, 20, true, true // ttl=30分钟,允许缓存NULL,启用本地缓存
    );

    /**
     * 查询用户:缓存命中返回,未命中查库并缓存
     */
    public User getUserById(Long userId) {
        // 1. 获取缓存实例(缓存名称:userCache)
        Cache cache = cacheManager.getCache("userCache", userCacheConfig);
        // 2. 生成缓存Key(支持SpEL表达式,这里直接拼接)
        String cacheKey = "user:" + userId;
        
        // 3. 缓存查询(未命中时执行Supplier查库)
        return (User) cache.get(cacheKey, User.class, () -> {
            // 数据库查询逻辑(仅缓存未命中时执行)
            return userDao.selectById(userId);
        });
    }

    /**
     * 更新用户:同步更新数据库和缓存
     */
    public void updateUser(User user) {
        Cache cache = cacheManager.getCache("userCache", userCacheConfig);
        String cacheKey = "user:" + user.getId();
        
        // 1. 更新数据库
        userDao.updateById(user);
        // 2. 更新缓存(覆盖旧值)
        cache.put(cacheKey, user);
    }

    /**
     * 删除用户:同步删除数据库和缓存
     */
    public void deleteUser(Long userId) {
        Cache cache = cacheManager.getCache("userCache", userCacheConfig);
        String cacheKey = "user:" + userId;
        
        // 1. 删除数据库记录
        userDao.deleteById(userId);
        // 2. 删除缓存(避免脏数据)
        cache.evict(cacheKey);
    }
}
3.2 注解式使用(基于 AOP 切面)
java 复制代码
import java.lang.annotation.*;

// 1. 自定义缓存注解(可扩展)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCacheable {
    String cacheName(); // 缓存名称
    String key() default ""; // SpEL表达式Key
    MultiCacheConfig config() default @MultiCacheConfig(); // 缓存配置
}

// 2. 注解切面实现(基于 AbstractCacheAspectSupport)
@Aspect
@Component
public class CacheAspect extends AbstractCacheAspectSupport {

    @Resource
    private CacheManager cacheManager;

    @Around("@annotation(myCacheable)")
    public Object cacheAroundAdvice(ProceedingJoinPoint joinPoint, MyCacheable myCacheable) throws Throwable {
        // 获取方法信息
        Method method = getSpecificMethod(joinPoint);
        Object[] args = joinPoint.getArgs();
        Object target = joinPoint.getTarget();
        
        // 解析缓存Key(支持SpEL表达式)
        String cacheKey = (String) generateKey(myCacheable.key(), method, args, target);
        // 获取缓存实例
        Cache cache = cacheManager.getCache(myCacheable.cacheName(), myCacheable.config());
        
        // 缓存查询:命中返回,未命中执行目标方法并缓存
        return cache.get(cacheKey, method.getReturnType(), () -> {
            try {
                return joinPoint.proceed();
            } catch (Throwable e) {
                throw new RuntimeException("缓存加载失败", e);
            }
        });
    }
}

// 3. 业务层使用注解
@Service
public class ProductService {

    @MyCacheable(
        cacheName = "productCache",
        key = "#root.methodName + ':' + #p0", // SpEL:方法名+第一个参数
        config = @MultiCacheConfig(ttl = 60, timeUnit = TimeUnit.MINUTES, cacheNullValues = true)
    )
    public Product getProductById(Long productId) {
        // 数据库查询逻辑(缓存未命中时执行)
        return productDao.selectById(productId);
    }
}

四、核心组件详解

1. 缓存核心接口(Cache)

定义缓存操作标准 API,所有缓存实现的顶层接口:

arduino 复制代码
public interface Cache {
    // 获取缓存(未命中返回null)
    <T> T get(String key, Class<T> resultType);
    // 获取缓存(未命中执行valueLoader加载并缓存)
    <T> T get(String key, Class<T> resultType, Supplier<Object> valueLoader);
    // 添加/覆盖缓存
    void put(String key, Object value);
    // 原子操作:不存在时才添加
    boolean putIfAbsent(String key, Object value);
    // 单个删除
    void evict(String key);
    // 批量删除
    void multiEvict(Collection<String> keys);
    // 清空缓存
    void clear();
}

2. 多级缓存实现(MultiLevelCache)

整合 Caffeine 本地缓存和 Redis 分布式缓存:

kotlin 复制代码
public class MultiLevelCache implements Cache {
    private final String cacheName;
    private final CaffeineCache localCache; // 一级缓存(本地)
    private final RedisCache remoteCache; // 二级缓存(Redis)
    private final boolean cacheNullValues; // 是否缓存NULL值
    private final boolean enableLocalCache; // 是否启用本地缓存

    // 核心逻辑:查询缓存时先查本地,再查Redis,最后查库
    @Override
    public <T> T get(String key, Class<T> resultType, Supplier<Object> valueLoader) {
        // 1. 查本地缓存
        if (enableLocalCache) {
            T localValue = localCache.get(key, resultType);
            if (resultType.isInstance(localValue) && !NullValue.isNullValue(localValue)) {
                return localValue;
            }
        }

        // 2. 查Redis缓存
        T remoteValue = remoteCache.get(key, resultType);
        if (resultType.isInstance(remoteValue) && !NullValue.isNullValue(remoteValue)) {
            // 同步到本地缓存
            if (enableLocalCache) {
                localCache.put(key, remoteValue);
            }
            return remoteValue;
        }

        // 3. 缓存未命中,执行加载逻辑(查库)
        Object value = valueLoader.get();
        // 4. 缓存结果(支持NULL值)
        put(key, value);
        return resultType.cast(fromStoreValue(value));
    }

    // 其他方法:put/evict/clear 均同步操作本地和Redis缓存
}

3. 缓存管理器(CacheManager)

负责缓存实例的创建和管理,采用懒加载模式:

arduino 复制代码
public interface CacheManager {
    // 获取缓存(不存在返回null)
    Cache getCache(String name);
    // 获取缓存(不存在则创建)
    Cache getCache(String name, MultiCacheConfig config);
}

// 抽象实现类
public abstract class AbstractCacheManager implements CacheManager {
    // 缓存容器(ConcurrentHashMap保证线程安全)
    private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);

    @Override
    public Cache getCache(String name, MultiCacheConfig config) {
        // 双重检查锁:避免重复创建缓存
        Cache cache = cacheMap.get(name);
        if (cache != null) {
            return cache;
        }
        synchronized (cacheMap) {
            cache = cacheMap.get(name);
            if (cache == null) {
                // 创建缓存实例(子类实现具体缓存类型)
                cache = createCache(name, config);
                cacheMap.put(name, cache);
            }
            return cache;
        }
    }

    // 抽象方法:由子类实现缓存创建逻辑
    protected abstract Cache createCache(String name, MultiCacheConfig config);
}

4. 分布式锁组件(Redisson)

解决并发场景下的缓存问题,核心 API:

arduino 复制代码
public interface DistributedLocker {
    // 获取单个锁
    boolean lock(String lockKey, long leaseTime, long waitTime, TimeUnit unit);
    // 获取联锁(多个Key)
    boolean multiLock(Collection<String> lockKeys, long leaseTime, long waitTime, TimeUnit unit);
    // 释放锁
    void unlock(String lockKey);
    // 释放联锁
    void multiUnlock(Collection<String> lockKeys);
}

// 工具类封装(静态调用)
public class DistributedLockUtils {
    private static DistributedLocker locker;

    // 获取锁并执行逻辑
    public static <T> T executeWithLock(String lockKey, long leaseTime, long waitTime, TimeUnit unit, Supplier<T> supplier) {
        if (locker.lock(lockKey, leaseTime, waitTime, unit)) {
            try {
                return supplier.get();
            } finally {
                locker.unlock(lockKey);
            }
        }
        throw new RuntimeException("获取锁失败");
    }
}

5. 工具类

5.1 JSON 序列化工具(JsonUtils)
typescript 复制代码
public class JsonUtils {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
        .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

    // 对象转JSON字符串
    public static String toJson(Object obj) {
        try {
            return OBJECT_MAPPER.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("JSON序列化失败", e);
        }
    }

    // JSON字符串转对象
    public static <T> T fromJson(String json, Class<T> clazz) {
        try {
            return OBJECT_MAPPER.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("JSON反序列化失败", e);
        }
    }
}
5.2 SpEL 表达式解析工具(CacheExpressionEvaluator)
typescript 复制代码
public class CacheExpressionEvaluator extends CachedExpressionEvaluator {
    private final Map<ExpressionKey, Expression> keyCache = new ConcurrentHashMap<>(64);

    // 创建表达式上下文(包含方法、参数、目标对象等)
    public EvaluationContext createContext(Method method, Object[] args, Object target) {
        CacheExpressionRootObject root = new CacheExpressionRootObject(method, args, target);
        return new MethodBasedEvaluationContext(root, method, args, getParameterNameDiscoverer());
    }

    // 解析SpEL表达式
    public Object evaluateKey(String keyExpr, Method method, Object[] args, Object target) {
        if (StringUtils.isEmpty(keyExpr)) {
            return "";
        }
        EvaluationContext context = createContext(method, args, target);
        ExpressionKey key = new ExpressionKey(keyExpr, method);
        return keyCache.computeIfAbsent(key, k -> getExpressionParser().parseExpression(keyExpr))
            .getValue(context);
    }
}

五、进阶配置与最佳实践

1. 缓存策略配置

1.1 过期模式选择
java 复制代码
// 过期模式枚举
public enum ExpireMode {
    WRITE, // 写入后开始计时(适合写少读多场景)
    ACCESS // 访问后刷新过期时间(适合热点数据)
}

// 配置示例:热点数据使用ACCESS模式
MultiCacheConfig hotDataConfig = new MultiCacheConfig();
hotDataConfig.setExpireMode(ExpireMode.ACCESS.name());
hotDataConfig.setTtl(10); // 10分钟
hotDataConfig.setEnableFirstCache(true);
1.2 防止缓存雪崩
arduino 复制代码
// 配置过期时间随机偏移(避免大量Key同时过期)
public class MultiCacheConfig {
    // 最大偏移比例(10%)
    private static final double MAX_OFFSET_RATIO = 0.1;

    // 获取带随机偏移的过期时间(秒)
    public long getRandomTtl() {
        long baseTtl = getTtlToSeconds();
        double offset = baseTtl * MAX_OFFSET_RATIO;
        // 随机增减偏移量
        return (long) (baseTtl + (Math.random() * 2 * offset - offset));
    }
}

2. 性能调优

2.1 本地缓存(Caffeine)调优
arduino 复制代码
// 高性能配置:基于访问频率和过期时间
CaffeineCacheConfig config = new CaffeineCacheConfig();
config.setInitialCapacity(100); // 初始容量
config.setMaximumSize(5000); // 最大容量(避免内存溢出)
config.setExpireMode(ExpireMode.ACCESS); // 访问过期
config.setTtl(5); // 5分钟过期
2.2 Redis 缓存调优
yaml 复制代码
spring:
  redis:
    lettuce:
      pool:
        max-active: 16 # 最大连接数
        max-idle: 8 # 最大空闲连接
        min-idle: 4 # 最小空闲连接
    timeout: 2000ms # 超时时间(避免长时间阻塞)

3. 常见问题解决方案

3.1 缓存穿透(查询不存在的数据)
kotlin 复制代码
// 方案:缓存NULL值 + 分布式锁
public User getUserById(Long userId) {
    String cacheKey = "user:" + userId;
    Cache cache = cacheManager.getCache("userCache", config);
    
    // 1. 查缓存(包括NULL值)
    User user = cache.get(cacheKey, User.class);
    if (user != null) {
        return user;
    }
    
    // 2. 分布式锁:防止并发查询不存在的数据
    return DistributedLockUtils.executeWithLock(
        "lock:user:" + userId, 5, 3, TimeUnit.SECONDS,
        () -> {
            // 3. 再次查缓存(避免锁等待期间已缓存)
            User cachedUser = cache.get(cacheKey, User.class);
            if (cachedUser != null) {
                return cachedUser;
            }
            // 4. 查库(不存在则返回null)
            User dbUser = userDao.selectById(userId);
            // 5. 缓存结果(包括NULL值)
            cache.put(cacheKey, dbUser);
            return dbUser;
        }
    );
}
3.2 缓存击穿(热点Key过期)
ini 复制代码
// 方案:缓存自动续期 + 分布式锁
public Product getHotProduct(Long productId) {
    String cacheKey = "hot:product:" + productId;
    Cache cache = cacheManager.getCache("hotProductCache", config);
    
    return DistributedLockUtils.executeWithLock(
        cacheKey + ":lock", -1, 3, TimeUnit.SECONDS, // leaseTime=-1:自动续期
        () -> {
            Product product = cache.get(cacheKey, Product.class);
            if (product != null) {
                // 访问后刷新过期时间(ACCESS模式自动实现)
                return product;
            }
            // 查库并缓存
            Product dbProduct = productDao.selectById(productId);
            cache.put(cacheKey, dbProduct);
            return dbProduct;
        }
    );
}
3.3 缓存一致性(更新/删除后同步缓存)
typescript 复制代码
// 方案:更新数据库后同步更新缓存,删除数据库后删除缓存
@Transactional
public void updateProduct(Product product) {
    // 1. 更新数据库(事务内)
    productDao.updateById(product);
    // 2. 更新缓存(事务提交后执行,避免事务回滚导致缓存脏数据)
    TransactionSynchronizationManager.registerSynchronization(
        new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                Cache cache = cacheManager.getCache("productCache", config);
                cache.put("product:" + product.getId(), product);
            }
        }
    );
}

六、完整可复用核心代码

1. 缓存配置类

typescript 复制代码
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.beans.factory.annotation.Value;

@Configuration
@ConditionalOnBean(RedisTemplate.class)
public class CacheAutoConfiguration {

    @Value("${spring.application.name}")
    private String appName;

    // 注册缓存管理器
    @Bean
    public CacheManager cacheManager(RedisTemplate<String, String> redisTemplate) {
        return new DefaultCacheManager(appName, new RedisCacheTemplate(redisTemplate));
    }

    // 注册分布式锁
    @Bean
    public DistributedLocker distributedLocker(RedissonClient redissonClient) {
        return new RedissonDistributedLocker(redissonClient);
    }
}

2. 多级缓存配置模型

java 复制代码
import java.util.concurrent.TimeUnit;

public class MultiCacheConfig {
    // 缓存过期时间(默认10分钟)
    private long ttl = 10;
    // 时间单位(默认分钟)
    private TimeUnit timeUnit = TimeUnit.MINUTES;
    // 过期时间比例(用于动态调整,1-100)
    private int ttlProportion = 10;
    // 是否缓存NULL值(默认false)
    private boolean cacheNullValues = false;
    // 是否启用本地缓存(默认false)
    private boolean enableLocalCache = false;
    // 本地缓存初始容量(默认10)
    private int initialCapacity = 10;
    // 本地缓存最大容量(默认3000)
    private long maximumSize = 3000;
    // 过期模式(默认WRITE)
    private String expireMode = ExpireMode.WRITE.name();

    // Getter/Setter 省略

    // 转换为秒级过期时间
    public long getTtlToSeconds() {
        return timeUnit.toSeconds(ttl);
    }

    // 带比例的过期时间(最小3秒)
    public long getTtlWithProportion() {
        long base = getTtlToSeconds();
        long proportioned = (base * ttlProportion) / 100;
        return Math.max(proportioned, 3);
    }
}

// 过期模式枚举
enum ExpireMode {
    WRITE, ACCESS
}

3. 空值标识类

java 复制代码
import java.io.Serializable;

public class NullValue implements Serializable {
    private static final long serialVersionUID = 1L;
    public static final NullValue INSTANCE = new NullValue();

    private NullValue() {}

    // 反序列化时返回单例
    private Object readResolve() {
        return INSTANCE;
    }

    @Override
    public boolean equals(Object obj) {
        return obj == this || obj == null;
    }

    @Override
    public int hashCode() {
        return NullValue.class.hashCode();
    }

    // 判断是否为空值
    public static boolean isNull(Object value) {
        return value == null || value == INSTANCE;
    }
}

4. Redis 缓存模板

typescript 复制代码
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;

public class RedisCacheTemplate {
    private final StringRedisTemplate redisTemplate;

    public RedisCacheTemplate(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    // 设置缓存(带过期时间)
    public void set(String key, String value, long ttl, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, ttl, unit);
    }

    // 获取缓存
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // 删除缓存
    public boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    // 批量删除
    public long delete(Collection<String> keys) {
        return redisTemplate.delete(keys);
    }

    // 判断Key是否存在
    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    // 设置过期时间
    public boolean expire(String key, long ttl, TimeUnit unit) {
        return redisTemplate.expire(key, ttl, unit);
    }
}
相关推荐
哈哈哈笑什么1 小时前
分布式事务实战:订单服务 + 库存服务(基于本地消息表组件)
分布式·后端·rabbitmq
洞窝技术1 小时前
Redis 4.0 升级至 5.0 实施手册
java·redis
溪饱鱼1 小时前
NextJs + Cloudflare Worker 是出海最佳实践
前端·后端
哈哈哈笑什么1 小时前
完整分布式事务解决方案(本地消息表 + RabbitMQ)
分布式·后端·rabbitmq
LeeZhao@1 小时前
【狂飙全模态】狂飙AGI-智能答疑助手
数据库·人工智能·redis·语言模型·aigc·agi
小周在成长1 小时前
Java 抽象类 vs 接口:相同点与不同点
后端
expect7g1 小时前
Paimon Branch --- 流批一体化之二
大数据·后端·flink
幌才_loong1 小时前
.NET 8 实时推送魔法:SSE 让数据 “主动跑” 到客户端
后端
00后程序员1 小时前
如何解决浏览器HTTPS不安全连接警告及SSL证书问题
后端