本文目录导读:

针对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(注解式) 提高开发效率,可以让缓存代码既规整又灵活。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播