基于Spring生态,整合Caffeine本地缓存与Redis分布式缓存的多级框架,通过本地高速响应与分布式一致性协同,解决缓存穿透、击穿、雪崩等问题。支持灵活配置、SpEL动态Key、Redisson分布式锁,提供编程式和注解式API,适用于高并发场景。
Redis+Caffeine多级缓存框架实现
作者:技术拾光
时间:2026-06-07
浏览:569
文章目录
一、框架核心概述(是什么)
该框架的核心定位为一套本地缓存(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)
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-redis com.github.ben-manes.caffeine caffeine 3.1.8 org.redisson redisson-spring-boot-starter 3.25.0 com.fasterxml.jackson.core jackson-databind
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)
编程式方式在 Service 中注入 CacheManager 操作缓存:
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
@Resource
private CacheManager cacheManager;
private final MultiCacheConfig userCacheConfig = new MultiCacheConfig(
30, TimeUnit.MINUTES, 20, true, true
);
public User getUserById(Long userId) {
Cache cache = cacheManager.getCache("userCache", userCacheConfig);
String cacheKey = "user:" + userId;
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();
userDao.updateById(user);
cache.put(cacheKey, user);
}
public void deleteUser(Long userId) {
Cache cache = cacheManager.getCache("userCache", userCacheConfig);
String cacheKey = "user:" + userId;
userDao.deleteById(userId);
cache.evict(cacheKey);
}
}
3.2 注解式使用(基于 AOP 切面)
若偏好注解风格,可自定义 @MyCacheable:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyCacheable {
String cacheName();
String key() default "";
MultiCacheConfig config() default @MultiCacheConfig();
}
@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();
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);
}
});
}
}
@Service
public class ProductService {
@MyCacheable(
cacheName = "productCache",
key = "#root.methodName + ':' + #p0",
config = @MultiCacheConfig(ttl = 60, timeUnit = TimeUnit.MINUTES, cacheNullValues = true)
)
public Product getProductById(Long productId) {
return productDao.selectById(productId);
}
}
四、核心组件详解
1. 缓存核心接口(Cache)
该接口是所有缓存操作的顶层契约,定义标准 API:
public interface Cache {
T get(String key, Class resultType);
T get(String key, Class resultType, Supplier
2. 多级缓存实现(MultiLevelCache)
该类将本地 Caffeine 和远程 Redis 整合,核心思路:先查本地,再查 Redis,最后查库。代码如下:
public class MultiLevelCache implements Cache {
private final String cacheName;
private final CaffeineCache localCache;
private final RedisCache remoteCache;
private final boolean cacheNullValues;
private final boolean enableLocalCache;
@Override
public T get(String key, Class resultType, Supplier
3. 缓存管理器(CacheManager)
负责缓存的创建和托管,采用懒加载 + 双重检查锁,避免重复创建:
public interface CacheManager {
Cache getCache(String name);
Cache getCache(String name, MultiCacheConfig config);
}
public abstract class AbstractCacheManager implements CacheManager {
private final ConcurrentMap 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)
用于解决并发场景,提供单个锁和联锁操作:
public interface DistributedLocker {
boolean lock(String lockKey, long leaseTime, long waitTime, TimeUnit unit);
boolean multiLock(Collection lockKeys, long leaseTime, long waitTime, TimeUnit unit);
void unlock(String lockKey);
void multiUnlock(Collection lockKeys);
}
public class DistributedLockUtils {
private static DistributedLocker locker;
public static T executeWithLock(String lockKey, long leaseTime, long waitTime, TimeUnit unit, Supplier 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);
public static String toJson(Object obj) { /* 略 */ }
public static T fromJson(String json, Class clazz) { /* 略 */ }
}
5.2 SpEL 表达式解析工具(CacheExpressionEvaluator)
public class CacheExpressionEvaluator extends CachedExpressionEvaluator {
private final Map keyCache = new ConcurrentHashMap<>(64);
public EvaluationContext createContext(Method method, Object[] args, Object target) { /* 略 */ }
public Object evaluateKey(String keyExpr, Method method, Object[] args, Object target) { /* 略 */ }
}
五、进阶配置与最佳实践
实际落地时需特别注意以下几个关键点。以下给出通用配置和代码模板。
1. 缓存策略配置
1.1 过期模式选择
public enum ExpireMode {
WRITE, // 写入后开始计时
ACCESS // 访问后刷新过期时间
}
MultiCacheConfig hotDataConfig = new MultiCacheConfig();
hotDataConfig.setExpireMode(ExpireMode.ACCESS.name());
hotDataConfig.setTtl(10);
hotDataConfig.setEnableFirstCache(true);
1.2 防止缓存雪崩
public class MultiCacheConfig {
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);
2.2 Redis 缓存调优
spring:
redis:
lettuce:
pool:
max-active: 16
max-idle: 8
min-idle: 4
timeout: 2000ms
3. 常见问题解决方案
3.1 缓存穿透(查询不存在的数据)
public User getUserById(Long userId) {
String cacheKey = "user:" + userId;
Cache cache = cacheManager.getCache("userCache", config);
User user = cache.get(cacheKey, User.class);
if (user != null) {
return user;
}
return DistributedLockUtils.executeWithLock(
"lock:user:" + userId, 5, 3, TimeUnit.SECONDS,
() -> {
User cachedUser = cache.get(cacheKey, User.class);
if (cachedUser != null) {
return cachedUser;
}
User dbUser = userDao.selectById(userId);
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,
() -> {
Product product = cache.get(cacheKey, Product.class);
if (product != null) {
return product;
}
Product dbProduct = productDao.selectById(productId);
cache.put(cacheKey, dbProduct);
return dbProduct;
}
);
}
3.3 缓存一致性(更新/删除后同步缓存)
@Transactional
public void updateProduct(Product product) {
productDao.updateById(product);
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 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 {
private long ttl = 10;
private TimeUnit timeUnit = TimeUnit.MINUTES;
private int ttlProportion = 10;
private boolean cacheNullValues = false;
private boolean enableLocalCache = false;
private int initialCapacity = 10;
private long maximumSize = 3000;
private String expireMode = ExpireMode.WRITE.name();
// getter/setter 省略
public long getTtlToSeconds() {
return timeUnit.toSeconds(ttl);
}
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 keys) { return redisTemplate.delete(keys); }
public boolean hasKey(String key) { return redisTemplate.hasKey(key); }
public boolean expire(String key, long ttl, TimeUnit unit) { return redisTemplate.expire(key, ttl, unit); }
}
实用工具推荐
电子包浆
使用 ›绿化、压缩、噪点和虚拟水印,自由调节做旧程度。
图片压缩
使用 ›选择画质和输出格式,预览压缩前后的大小。
相关文章