一、框架核心概述(是什么)
这是一套 本地缓存(Caffeine)+ 分布式缓存(Redis)的多级缓存框架,基于 Spring 生态实现,核心目标是兼顾缓存访问性能与分布式环境下的数据一致性。
核心特性
多级缓存架构:一级缓存(本地 Caffeine)提升响应速度,二级缓存(Redis)保证分布式一致性灵活配置:支持过期时间、过期模式、NULL 值缓存、本地缓存容量限制等动态 Key 生成:基于 SpEL 表达式实现缓存 Key 动态拼接并发安全:内置分布式锁(Redisson),解决缓存穿透/击穿/雪崩问题完整 API:支持缓存增删改查、原子操作、批量处理等技术栈依赖
核心框架:Spring Boot、Spring Context缓存组件:Caffeine(本地缓存)、Redis(分布式缓存)分布式锁:Redisson序列化:Jackson表达式解析:Spring EL二、框架核心价值(为什么用)
1. 解决的核心问题
2. 相比单一缓存的优势
比纯 Redis 缓存:减少网络 IO 开销,本地缓存响应时间提升 10-100 倍比纯本地缓存:支持分布式部署,避免节点间数据不一致比 Spring Cache 原生:支持更灵活的配置(过期模式、容量限制)和更完善的并发控制三、快速上手(怎么做)
1. 依赖配置(Maven)
<!-- 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)
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)
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 切面)
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,所有缓存实现的顶层接口:
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 分布式缓存:
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)
负责缓存实例的创建和管理,采用懒加载模式:
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:
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)
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)
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 过期模式选择
// 过期模式枚举 public enum ExpireMode { WRITE, // 写入后开始计时(适合写少读多场景) ACCESS // 访问后刷新过期时间(适合热点数据) } // 配置示例:热点数据使用ACCESS模式 MultiCacheConfig hotDataConfig = new MultiCacheConfig(); hotDataConfig.setExpireMode(ExpireMode.ACCESS.name()); hotDataConfig.setTtl(10); // 10分钟 hotDataConfig.setEnableFirstCache(true);
1.2 防止缓存雪崩
// 配置过期时间随机偏移(避免大量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)调优
// 高性能配置:基于访问频率和过期时间 CaffeineCacheConfig config = new CaffeineCacheConfig(); config.setInitialCapacity(100); // 初始容量 config.setMaximumSize(5000); // 最大容量(避免内存溢出) config.setExpireMode(ExpireMode.ACCESS); // 访问过期 config.setTtl(5); // 5分钟过期
2.2 Redis 缓存调优
spring: redis: lettuce: pool: max-active: 16 # 最大连接数 max-idle: 8 # 最大空闲连接 min-idle: 4 # 最小空闲连接 timeout: 2000ms # 超时时间(避免长时间阻塞)
3. 常见问题解决方案
3.1 缓存穿透(查询不存在的数据)
// 方案:缓存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过期)
// 方案:缓存自动续期 + 分布式锁 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 缓存一致性(更新/删除后同步缓存)
// 方案:更新数据库后同步更新缓存,删除数据库后删除缓存 @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. 缓存配置类
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. 多级缓存配置模型
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. 空值标识类
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 缓存模板
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); } }
到此这篇关于Redis Caffeine多级缓存框架的实现的文章就介绍到这了,
