本文目录导读:

针对Java缓存结构的规整化设计,这里整理了一套从简单到复杂的分层设计案例,涵盖了常见场景下的最佳实践和代码结构。
缓存结构设计的核心原则
在写代码之前,先明确要遵循的架构思路:
- 分层原则: L1(本地) -> L2(分布式)-> DB
- 单一职责: 缓存管理类只负责缓存逻辑,不混入业务代码
- 可替换性: 底层实现(Redis, Caffeine, Map)可配置切换
- 防御式设计: 缓存穿透、击穿、雪崩的处理
案例 1:标准分层缓存结构(最推荐)
这是最常见的多级缓存结构,适用于用户信息、商品详情等高频访问场景。
项目包结构
com.example.cache
├── config
│ └── CacheConfig.java // 缓存配置(过期时间、大小)
├── multiLevel
│ ├── CacheData.java // 缓存数据包装类
│ ├── CacheLoader.java // 缓存加载器接口
│ └── MultiLevelCache.java // 多级缓存实现
├── service
│ └── UserService.java // 业务服务(使用缓存)
└── common
└── CacheNotFoundException.java // 自定义异常
核心实现代码
① 缓存数据包装类
// CacheData.java - 封装缓存值和加载来源
@Data
@AllArgsConstructor
public class CacheData<T> {
private T value;
private String source; // L1/L2/DB
private long loadTime; // 加载时间戳,用于判断是否过期
}
② 多级缓存管理器
// MultiLevelCache.java - 核心多级缓存实现
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import redis.clients.jedis.Jedis;
import java.util.concurrent.TimeUnit;
public class MultiLevelCache<K, V> {
// L1: 本地缓存 (Caffeine) - 极快,容量小
private final Cache<K, V> localCache;
// L2: 分布式缓存 (Redis) - 较快,容量大
private final Jedis redisClient;
// L3: 数据库加载器
private final CacheLoader<K, V> dbLoader;
// 统计指标(可选)
private final AtomicLong l1Hits = new AtomicLong(0);
private final AtomicLong l2Hits = new AtomicLong(0);
public MultiLevelCache(Jedis redisClient, CacheLoader<K, V> dbLoader) {
this.redisClient = redisClient;
this.dbLoader = dbLoader;
// 初始化L1本地缓存:最大1000条,过期5分钟
this.localCache = Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats() // 开启统计
.build();
}
@SuppressWarnings("unchecked")
public V get(K key) {
// 1. 尝试L1本地缓存
V value = localCache.getIfPresent(key);
if (value != null) {
l1Hits.incrementAndGet();
return value;
}
// 2. 尝试L2 Redis缓存(序列化后存储)
String redisValue = redisClient.get(key.toString());
if (redisValue != null) {
l2Hits.incrementAndGet();
// 反序列化
value = deserialize(redisValue);
// 回填L1缓存
localCache.put(key, value);
return value;
}
// 3. 回源DB查询
value = dbLoader.load(key);
if (value != null) {
// 同时回填L1和L2
localCache.put(key, value);
redisClient.setex(key.toString(), 3600, serialize(value)); // 1小时过期
}
return value;
}
// 包含防御措施的带锁版本(防击穿)
public V getWithLock(K key) {
// 校验空值缓存(防穿透)
V value = localCache.getIfPresent(key);
if (value != null) {
return value instanceof NullValueCache ? null : value;
}
// 分布式锁(防击穿)
String lockKey = "LOCK:" + key;
if (acquireLock(lockKey)) {
try {
// 双重检查
V v = localCache.getIfPresent(key);
if (v != null) return v;
// 查DB
value = dbLoader.load(key);
// 缓存空值对象(防穿透)
localCache.put(key, value != null ? value : new NullValueCache());
if (value != null) {
redisClient.setex(key.toString(), 3600, serialize(value));
} else {
redisClient.setex(key.toString(), 60, ""); // 短暂空值缓存
}
} finally {
releaseLock(lockKey);
}
}
return value;
}
public void invalidate(K key) {
localCache.invalidate(key);
redisClient.del(key.toString());
}
}
③ 缓存加载器接口
// CacheLoader.java - 让业务方实现DB加载逻辑
@FunctionalInterface
public interface CacheLoader<K, V> {
V load(K key);
}
④ 防穿透的空值对象
// NullValueCache.java - 用于缓存null防止穿透
public class NullValueCache implements Serializable {
// 标记类,不包含实际数据
}
业务使用示例
// UserService.java
@Service
public class UserService {
private final MultiLevelCache<String, User> userCache;
public UserService() {
// 构造时注入DB加载器
userCache = new MultiLevelCache<>(redisClient, userId -> {
// 这是L3:真正查询数据库
return userMapper.selectById(userId);
});
}
public User getUser(String userId) {
return userCache.getWithLock("USER:" + userId);
}
public void updateUser(String userId, User user) {
userMapper.update(user);
userCache.invalidate("USER:" + userId); // 主动失效
}
}
案例 2:注解驱动缓存结构(Spring Cache风格)
如果你用Spring框架,可以封装自己的缓存注解,让代码更简洁。
自定义注解
// MultiLevelCacheable.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MultiLevelCacheable {
String key(); // 缓存键
int ttlSeconds() default 600; // 过期时间
boolean useLocalCache() default true; // 是否用L1
}
注解解析AOP
// CacheAspect.java
@Aspect
@Component
public class CacheAspect {
private final MultiLevelCache<String, Object> multiLevelCache;
@Around("@annotation(cacheable)")
public Object cacheAround(ProceedingJoinPoint pjp, MultiLevelCacheable cacheable) {
String key = resolveKey(cacheable.key(), pjp.getArgs());
// 尝试从缓存获取
Object cachedValue = multiLevelCache.get(key);
if (cachedValue != null) {
return cachedValue;
}
// 执行原方法(即DB查询)
Object result = pjp.proceed();
// 回填缓存
multiLevelCache.put(key, result, cacheable.ttlSeconds());
return result;
}
private String resolveKey(String keyTemplate, Object[] args) {
// 解析key中的占位符,如 #userId
// 实际使用Spring Expression Language (SpEL)
return keyTemplate;
}
}
使用方式
@Service
public class ProductService {
@MultiLevelCacheable(key = "'PRODUCT:' + #productId", ttlSeconds = 600)
public Product getProduct(String productId) {
return productMapper.selectById(productId);
}
}
案例 3:针对特定场景的规整结构
场景:热点数据 + 频繁更新的计数器
缓存结构:
L1: Redis Hash (热点数据)
L2: Redis String (通用数据)
L3: MySQL (持久化)
// HotDataCache.java
public class HotDataCache {
// 热更新工具:每5秒批量刷回DB
@Scheduled(fixedRate = 5000)
public void flushHotData() {
// 将内存中的热数据增量刷到MySQL
}
// 热点判定:访问频率 > 阈值
public boolean isHotKey(String key) {
return accessCounter.get(key) > 100; // 每分钟100次
}
}
通用工程化和可视化监控
缓存监控端点
// /actuator/cache-metrics 返回
{
"multiLevelCache": {
"l1HitRate": 0.85,
"l2HitRate": 0.12,
"totalRequests": 100000,
"cacheSize": 980
}
}
缓存管理控制台
提供一个简单的REST API来管理缓存:
POST /cache/invalidate:手动失效某个keyGET /cache/stats:查看命中率POST /cache/preload:预热热门数据
规整化总结表
| 维度 | 推荐做法 | 避免的做法 |
|---|---|---|
| 层级 | L1(Caffeine)+L2(Redis)+DB | 单层缓存或全部本地 |
| 过期策略 | 本地短(5min)+Redis中(1h) | 统一过期时间 |
| 穿透处理 | 缓存空值对象 + Bloom Filter | 不处理或仅判断null |
| 击穿处理 | 分布式锁 + 双重检查 | 无锁的回源 |
| 序列化 | protobuf 或 JSON(定制) | 使用默认Java序列化 |
| 代码结构 | 独立的CacheManager + 业务接口 | 在Service里直接new Cache |
| 可观测性 | 暴露命中率、大小、加载耗时 | 不记录任何指标 |
最终建议:对于工程级项目,采用 案例1(手写多级缓存) 作为基座,配合 案例2(注解式) 提高开发效率,可以让缓存代码既规整又灵活。