《SpringBoot 3 + Caffeine 企业级缓存最佳实践方案(一)》
《SpringBoot 3 + Caffeine 企业级缓存最佳实践方案(二)》
文章目录
-
- 一、架构设计与核心配置
-
- [1. 架构设计](#1. 架构设计)
-
- [1.1 多级缓存架构设计](#1.1 多级缓存架构设计)
-
- [1.1.1 整体架构图](#1.1.1 整体架构图)
- [1.1.2 缓存读取流程](#1.1.2 缓存读取流程)
- [1.1.3 缓存写入流程](#1.1.3 缓存写入流程)
- [1.2 缓存分层策略](#1.2 缓存分层策略)
-
- [1.2.1 缓存分层矩阵](#1.2.1 缓存分层矩阵)
- [1.2.2 缓存类型定义](#1.2.2 缓存类型定义)
- [1.3 缓存同步机制设计](#1.3 缓存同步机制设计)
-
- [1.3.1 基于Redis Pub/Sub的同步方案](#1.3.1 基于Redis Pub/Sub的同步方案)
- [1.3.2 缓存同步消息模型](#1.3.2 缓存同步消息模型)
- [1.4 缓存更新策略](#1.4 缓存更新策略)
-
- [1.4.1 四种主流更新策略对比](#1.4.1 四种主流更新策略对比)
- [1.4.2 Cache Aside 模式(推荐)](#1.4.2 Cache Aside 模式(推荐))
- [1.4.3 主动更新与被动失效](#1.4.3 主动更新与被动失效)
- [2. 技术选型与依赖](#2. 技术选型与依赖)
-
- [2.1 核心技术栈](#2.1 核心技术栈)
- [2.2 Maven依赖配置](#2.2 Maven依赖配置)
- [3. 核心配置实现](#3. 核心配置实现)
-
- [3.1 项目结构](#3.1 项目结构)
- [3.2 主启动类](#3.2 主启动类)
- [3.3 Caffeine核心配置](#3.3 Caffeine核心配置)
- [3.4 配置属性类](#3.4 配置属性类)
- [3.5 异步执行器配置](#3.5 异步执行器配置)
- 二、核心功能实现与高级特性
-
- [1. 自定义缓存注解](#1. 自定义缓存注解)
-
- [1.1 增强型缓存注解定义](#1.1 增强型缓存注解定义)
-
- [1.1.1 @EnhancedCacheable 注解](#1.1.1 @EnhancedCacheable 注解)
- [1.1.2 @EnhancedCacheEvict 注解](#1.1.2 @EnhancedCacheEvict 注解)
- [1.1.3 @EnhancedCachePut 注解](#1.1.3 @EnhancedCachePut 注解)
- [1.2 缓存注解处理器](#1.2 缓存注解处理器)
- [1.3 注解使用示例](#1.3 注解使用示例)
- [2. 缓存Key生成策略](#2. 缓存Key生成策略)
-
- [2.1 Key生成器接口](#2.1 Key生成器接口)
- [2.2 默认Key生成器实现](#2.2 默认Key生成器实现)
- [2.3 自定义Key生成器](#2.3 自定义Key生成器)
- [3. 缓存加载器实现](#3. 缓存加载器实现)
-
- [3.1 异步缓存加载器](#3.1 异步缓存加载器)
- [3.2 批量缓存加载器](#3.2 批量缓存加载器)
- [4. 缓存防护机制](#4. 缓存防护机制)
-
- [4.1 缓存穿透防护](#4.1 缓存穿透防护)
-
- [4.1.1 布隆过滤器实现](#4.1.1 布隆过滤器实现)
- [4.1.2 空值缓存策略](#4.1.2 空值缓存策略)
- [4.2 缓存击穿防护](#4.2 缓存击穿防护)
- [4.3 缓存雪崩防护](#4.3 缓存雪崩防护)
- [5. 分布式缓存一致性](#5. 分布式缓存一致性)
-
- [5.1 缓存同步管理器](#5.1 缓存同步管理器)
- [5.2 缓存失效通知](#5.2 缓存失效通知)
- [6. 缓存预热机制](#6. 缓存预热机制)
-
- [6.1 预热数据加载器](#6.1 预热数据加载器)
- [6.2 预热任务调度](#6.2 预热任务调度)
文档目录
- 一:架构设计与核心配置 二:核心功能实现与高级特性 (本文档)
- 三:监控运维与实战案例 四:性能优化与最佳实践
一、架构设计与核心配置
1. 架构设计
1.1 多级缓存架构设计
1.1.1 整体架构图
复制代码
┌─────────────────────────────────────────────────────────────────┐
│ 应用层 (Application Layer) │
│ Controllers / Services │
└────────────────────────────┬────────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────────┐
│ 缓存抽象层 (Cache Abstraction) │
│ Spring Cache Abstraction + 自定义注解 │
│ @EnhancedCacheable | @EnhancedCacheEvict | @EnhancedCachePut │
└────────────────────────────┬────────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────────┐
│ 缓存管理层 (Cache Management Layer) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ L1: Caffeine 本地缓存 (进程内) │ │
│ │ ┌──────────┬──────────┬──────────┬──────────┐ │ │
│ │ │ 热点数据 │ 常规数据 │ 冷数据 │ 限流缓存 │ │ │
│ │ │ 100ms │ 5min │ 30min │ 1s │ │ │
│ │ │ 10000条 │ 5000条 │ 1000条 │ 10000条 │ │ │
│ │ └──────────┴──────────┴──────────┴──────────┘ │ │
│ │ - 超高性能 (ns级响应) │ │
│ │ - 零网络开销 │ │
│ │ - JVM堆内存存储 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Cache Miss / Sync │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ L2: Redis 分布式缓存 (跨进程) │ │
│ │ - 集群共享 │ │
│ │ - 持久化支持 │ │
│ │ - 跨节点同步 │ │
│ │ - 缓存预热数据 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ │ Pub/Sub 消息 │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 缓存同步组件 (Cache Sync) │ │
│ │ - Redis Pub/Sub 消息订阅 │ │
│ │ - 节点间缓存失效通知 │ │
│ │ - 最终一致性保证 │ │
│ └─────────────────────────────────────────────────────────┘ │
└────────────────────────────┬────────────────────────────────────┘
│
│ Cache Miss
▼
┌─────────────────────────────────────────────────────────────────┐
│ 数据访问层 (Data Access Layer) │
│ Repository / DAO │
│ MySQL / PostgreSQL / MongoDB │
└─────────────────────────────────────────────────────────────────┘
1.1.2 缓存读取流程
复制代码
用户请求
│
▼
┌─────────────────┐
│ L1: Caffeine │
│ 本地缓存查询 │
└────┬────────────┘
│
├─ 命中 ──────────────────────────┐
│ │
└─ 未命中 │
│ │
▼ │
┌─────────────────┐ │
│ L2: Redis │ │
│ 分布式缓存查询 │ │
└────┬────────────┘ │
│ │
├─ 命中 ─────────┐ │
│ │ │
│ 回填L1缓存 │
│ │ │
└─ 未命中 │ │
│ │ │
▼ │ │
┌─────────────┐ │ │
│ 数据库查询 │ │ │
└─────┬───────┘ │ │
│ │ │
写入L1缓存 │ │
│ │ │
写入L2缓存 │ │
│ │ │
└───────────┴──────────────┘
│
▼
返回结果
1.1.3 缓存写入流程
复制代码
数据更新请求
│
▼
┌──────────────┐
│ 更新数据库 │
└──────┬───────┘
│
▼
┌──────────────────────────┐
│ 缓存更新策略选择 │
└──────┬───────────────────┘
│
├─────────────┬─────────────┬──────────────┐
│ │ │ │
▼ ▼ ▼ ▼
Cache Aside Write Through Write Behind Refresh Ahead
(删除缓存) (同步更新) (异步写入) (提前刷新)
│ │ │ │
▼ ▼ ▼ ▼
删除L1缓存 更新L1缓存 队列缓冲更新 异步预加载
│ │ │ │
▼ ▼ ▼ ▼
删除L2缓存 更新L2缓存 批量写数据库 定时刷新
│ │ │ │
▼ ▼ ▼ ▼
发布失效消息 发布更新消息 延迟持久化 保持热度
│ │ │ │
└─────────────┴─────────────┴──────────────┘
│
▼
其他节点同步更新/删除本地缓存
1.2 缓存分层策略
1.2.1 缓存分层矩阵
| 缓存层级 |
访问频率 |
命中率目标 |
容量 |
TTL |
适用场景 |
| 热点数据层 |
极高 (>1000 QPS) |
>95% |
10,000+ |
100s-300s |
秒杀商品、热门文章、活跃用户 |
| 常规数据层 |
中等 (100-1000 QPS) |
>85% |
5,000 |
5min-10min |
普通商品、用户信息、配置数据 |
| 冷数据层 |
较低 (<100 QPS) |
>70% |
1,000 |
30min-1h |
历史订单、归档数据、长尾商品 |
| 限流数据层 |
极高 (瞬时) |
N/A |
10,000+ |
1s-60s |
接口限流、防刷控制、验证码 |
| 字典数据层 |
稳定 |
>90% |
2,000 |
1h-24h |
枚举配置、地区数据、系统参数 |
1.2.2 缓存类型定义
java
复制代码
/**
* 缓存类型枚举
* 定义不同业务场景的缓存策略
*
* @author Enterprise Team
* @version 1.0.0
*/
@Getter
@RequiredArgsConstructor
public enum CacheType {
/**
* 热点数据缓存
* - 访问频率极高(命中率 > 80%)
* - 短过期时间,快速刷新
* - 大容量配置
*
* 使用场景:
* - 秒杀商品缓存
* - 热门文章/视频
* - 实时榜单数据
* - 活跃用户会话
*/
HOT_DATA("hotData", 10000, 100, 300, true),
/**
* 常规数据缓存
* - 访问频率中等(命中率 50-80%)
* - 中等过期时间
* - 中等容量配置
*
* 使用场景:
* - 普通商品详情
* - 用户基础信息
* - 文章内容
* - 订单详情
*/
NORMAL_DATA("normalData", 5000, 300, 600, true),
/**
* 冷数据缓存
* - 访问频率较低(命中率 < 50%)
* - 长过期时间
* - 小容量配置
*
* 使用场景:
* - 历史订单
* - 归档数据
* - 长尾商品
* - 冷门内容
*/
COLD_DATA("coldData", 1000, 1800, 3600, false),
/**
* 用户信息缓存
* - 用户会话相关数据
* - 中等过期时间
*
* 使用场景:
* - 用户个人资料
* - 用户权限信息
* - 用户偏好设置
*/
USER_INFO("userInfo", 5000, 600, 1800, true),
/**
* 字典数据缓存
* - 系统配置、枚举等静态数据
* - 长过期时间
* - 主动刷新
*
* 使用场景:
* - 地区数据
* - 枚举配置
* - 系统参数
* - 分类树
*/
DICTIONARY("dictionary", 2000, 3600, 7200, true),
/**
* 接口限流缓存
* - 短时间窗口统计
* - 极短过期时间
*
* 使用场景:
* - API限流
* - 防刷控制
* - 验证码校验
* - IP黑名单
*/
RATE_LIMIT("rateLimit", 10000, 1, 60, false),
/**
* 商品信息缓存
* - 电商业务核心数据
* - 中等过期时间
* - 异步刷新
*
* 使用场景:
* - 商品详情
* - 库存信息
* - 价格数据
* - SKU属性
*/
PRODUCT_INFO("productInfo", 8000, 300, 900, true);
private final String cacheName; // 缓存名称
private final int maximumSize; // 最大容量
private final int expireAfterWrite; // 写入后过期时间(秒)
private final int expireAfterAccess; // 访问后过期时间(秒)
private final boolean refreshAfterWrite; // 是否启用异步刷新
}
1.3 缓存同步机制设计
1.3.1 基于Redis Pub/Sub的同步方案
复制代码
节点A Redis 节点B 节点C
│ │ │ │
│ 1. 更新数据 │ │ │
├──────────────────────>│ │ │
│ │ │ │
│ 2. 删除本地缓存 │ │ │
│ │ │ │
│ 3. 发布失效消息 │ │ │
├──────────────────────>│ │ │
│ Topic: cache:evict │ │ │
│ Data: {cacheName, │ │ │
│ key, │ │ │
│ nodeId} │ │ │
│ │ │ │
│ │ 4. 订阅并接收消息 │ │
│ ├───────────────────────>│ │
│ ├────────────────────────┼───────────────────────>│
│ │ │ │
│ │ 5. 过滤自身消息 │ │
│ │ (nodeId不同) │ │
│ │ │ │
│ │ 6. 删除本地缓存 6. 删除本地缓存
│ │ │ │
│ │ 7. 返回ACK 7. 返回ACK
│ │<───────────────────────│ │
│ │<────────────────────────────────────────────────│
│ │ │ │
│<──────8. 同步完成─────┤ │ │
1.3.2 缓存同步消息模型
java
复制代码
/**
* 缓存同步消息
* 用于节点间缓存数据同步
*/
@Data
@Builder
public class CacheSyncMessage implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 操作类型
*/
private OperationType operationType;
/**
* 缓存名称
*/
private String cacheName;
/**
* 缓存Key
*/
private String cacheKey;
/**
* 缓存值(仅用于更新操作)
*/
private Object cacheValue;
/**
* 发送节点ID(避免循环处理)
*/
private String sourceNodeId;
/**
* 消息时间戳
*/
private LocalDateTime timestamp;
/**
* Key前缀(用于批量删除)
*/
private String keyPrefix;
/**
* 操作类型枚举
*/
public enum OperationType {
EVICT, // 删除单个缓存
EVICT_ALL, // 删除所有缓存
EVICT_BY_PREFIX, // 按前缀删除
UPDATE // 更新缓存
}
}
1.4 缓存更新策略
1.4.1 四种主流更新策略对比
| 策略 |
适用场景 |
优点 |
缺点 |
一致性 |
实现复杂度 |
| Cache Aside |
通用场景 |
简单可靠 |
短暂不一致 |
最终一致 |
⭐ |
| Write Through |
强一致性要求 |
一致性好 |
性能较差 |
强一致 |
⭐⭐⭐ |
| Write Behind |
高并发写入 |
性能最优 |
可能丢数据 |
最终一致 |
⭐⭐⭐⭐ |
| Refresh Ahead |
热点数据 |
命中率高 |
资源消耗大 |
弱一致 |
⭐⭐⭐ |
1.4.2 Cache Aside 模式(推荐)
java
复制代码
/**
* Cache Aside 模式实现
* 读操作:先读缓存,未命中再读数据库,然后写入缓存
* 写操作:先更新数据库,再删除缓存
*/
public class CacheAsidePattern {
/**
* 读取数据
*/
public User getUser(Long userId) {
// 1. 从缓存读取
User user = cacheService.get("userInfo", String.valueOf(userId));
if (user != null) {
return user; // 缓存命中
}
// 2. 缓存未命中,从数据库读取
user = userRepository.findById(userId);
if (user != null) {
// 3. 写入缓存
cacheService.put("userInfo", String.valueOf(userId), user);
}
return user;
}
/**
* 更新数据
*/
public void updateUser(User user) {
// 1. 先更新数据库
userRepository.update(user);
// 2. 再删除缓存(而不是更新缓存)
cacheService.evict("userInfo", String.valueOf(user.getId()));
// 3. 发布缓存失效消息(分布式环境)
cacheSyncService.publishEvictMessage("userInfo", String.valueOf(user.getId()));
}
}
1.4.3 主动更新与被动失效
java
复制代码
/**
* 主动更新:适用于数据变更可控的场景
*/
@EnhancedCachePut(
cacheType = CacheType.USER_INFO,
key = "#user.id",
syncDistributed = true
)
public User updateUserActive(User user) {
userRepository.update(user);
return user; // 返回值会写入缓存
}
/**
* 被动失效:适用于数据变更不确定的场景
*/
@EnhancedCacheEvict(
cacheType = CacheType.USER_INFO,
key = "#userId",
syncDistributed = true
)
public void updateUserPassive(Long userId, Map<String, Object> updates) {
userRepository.updateFields(userId, updates);
// 方法执行后自动删除缓存
}
/**
* 定时刷新:适用于热点数据
*/
@Scheduled(fixedDelay = 300000) // 5分钟刷新一次
public void refreshHotData() {
List<String> hotKeys = hotKeyDetector.getHotKeys(100);
hotKeys.forEach(key -> {
// 异步刷新
CompletableFuture.runAsync(() -> {
Object data = loadDataFromDB(key);
cacheService.put("hotData", key, data);
});
});
}
2. 技术选型与依赖
2.1 核心技术栈
| 技术 |
版本 |
说明 |
| Spring Boot |
3.2.1 |
基础框架 |
| Caffeine |
3.1.8 |
本地缓存 |
| Redis |
7.0+ |
分布式缓存 |
| Redisson |
3.25.2 |
分布式锁 |
| Guava |
32.1.3 |
布隆过滤器 |
| Micrometer |
1.12.1 |
监控指标 |
| JDK |
17+ |
运行环境 |
2.2 Maven依赖配置
xml
复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.1</version>
<relativePath/>
</parent>
<groupId>com.enterprise</groupId>
<artifactId>caffeine-cache-practice</artifactId>
<version>1.0.0</version>
<name>Enterprise Caffeine Cache Practice</name>
<description>SpringBoot 3 + Caffeine 企业级缓存最佳实践</description>
<properties>
<java.version>17</java.version>
<caffeine.version>3.1.8</caffeine.version>
<redisson.version>3.25.2</redisson.version>
<guava.version>32.1.3-jre</guava.version>
<micrometer.version>1.12.1</micrometer.version>
</properties>
<dependencies>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Cache 抽象 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!-- Caffeine 本地缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>${caffeine.version}</version>
</dependency>
<!-- Redis 分布式缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redisson 分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
<!-- Spring Boot Actuator 监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Micrometer Prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>${micrometer.version}</version>
</dependency>
<!-- Guava 布隆过滤器 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<!-- AOP 切面编程 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Lombok 代码简化 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Configuration Processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. 核心配置实现
3.1 项目结构
复制代码
caffeine-cache-practice/
├── src/main/java/com/enterprise/cache/
│ ├── CacheApplication.java # 启动类
│ │
│ ├── annotation/ # 自定义注解
│ │ ├── EnhancedCacheable.java # 增强缓存注解
│ │ ├── EnhancedCacheEvict.java # 增强删除注解
│ │ └── EnhancedCachePut.java # 增强更新注解
│ │
│ ├── aspect/ # AOP切面
│ │ └── EnhancedCacheAspect.java # 缓存切面处理器
│ │
│ ├── config/ # 配置类
│ │ ├── CaffeineConfig.java # Caffeine配置
│ │ ├── RedisMessageConfig.java # Redis消息配置
│ │ ├── AsyncExecutorConfig.java # 异步执行器配置
│ │ └── properties/
│ │ └── CaffeineProperties.java # 配置属性
│ │
│ ├── enums/ # 枚举类
│ │ └── CacheType.java # 缓存类型
│ │
│ ├── service/ # 服务层
│ │ ├── CacheService.java # 缓存服务接口
│ │ ├── BloomFilterService.java # 布隆过滤器服务
│ │ ├── DistributedLockService.java # 分布式锁服务
│ │ ├── CacheSyncService.java # 缓存同步服务
│ │ └── impl/
│ │ └── CacheServiceImpl.java # 缓存服务实现
│ │
│ ├── listener/ # 监听器
│ │ └── CacheRemovalListener.java # 缓存移除监听器
│ │
│ ├── loader/ # 加载器
│ │ └── AsyncCacheLoader.java # 异步缓存加载器
│ │
│ ├── model/ # 数据模型
│ │ └── CacheSyncMessage.java # 缓存同步消息
│ │
│ ├── metrics/ # 监控指标
│ │ ├── CacheMetricsCollector.java # 指标收集器
│ │ └── HotKeyDetector.java # 热点Key检测器
│ │
│ └── example/ # 示例代码
│ ├── entity/
│ ├── repository/
│ ├── service/
│ └── controller/
│
├── src/main/resources/
│ ├── application.yml # 主配置文件
│ ├── application-dev.yml # 开发环境
│ ├── application-test.yml # 测试环境
│ └── application-prod.yml # 生产环境
│
└── src/test/java/ # 测试代码
└── com/enterprise/cache/test/
3.2 主启动类
java
复制代码
package com.enterprise.cache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
/**
* 缓存应用启动类
*
* 启用功能:
* - @EnableCaching: Spring缓存抽象支持
* - @EnableAsync: 异步方法支持
* - @EnableScheduling: 定时任务支持
*
* @author Enterprise Team
* @version 1.0.0
*/
@SpringBootApplication
@EnableCaching
@EnableAsync
@EnableScheduling
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
3.3 Caffeine核心配置
java
复制代码
package com.enterprise.cache.config;
import com.enterprise.cache.config.properties.CaffeineProperties;
import com.enterprise.cache.enums.CacheType;
import com.enterprise.cache.listener.CacheRemovalListener;
import com.enterprise.cache.loader.AsyncCacheLoader;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.cache.CaffeineCacheMetrics;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Caffeine 缓存核心配置
*
* 功能特性:
* 1. 支持多缓存实例,每个实例独立配置
* 2. 集成Prometheus监控指标
* 3. 支持异步刷新机制
* 4. 支持移除事件监听
* 5. 支持自定义过期策略
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Configuration
@EnableCaching
@RequiredArgsConstructor
public class CaffeineConfig {
private final CaffeineProperties caffeineProperties;
private final MeterRegistry meterRegistry;
private final CacheRemovalListener removalListener;
private final AsyncCacheLoader asyncCacheLoader;
/**
* 主缓存管理器
* 使用动态配置,支持多缓存实例
*
* @return CacheManager实例
*/
@Bean
@Primary
public CacheManager caffeineCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
// 设置所有缓存名称
String[] cacheNames = Arrays.stream(CacheType.values())
.map(CacheType::getCacheName)
.toArray(String[]::new);
cacheManager.setCacheNames(Arrays.asList(cacheNames));
// 设置默认缓存配置
cacheManager.setCaffeine(buildDefaultCaffeine());
// 允许空值缓存(防止缓存穿透)
cacheManager.setAllowNullValues(true);
log.info("Caffeine CacheManager initialized with caches: {}",
Arrays.toString(cacheNames));
return cacheManager;
}
/**
* 构建默认 Caffeine 配置
*
* @return Caffeine构建器
*/
private Caffeine<Object, Object> buildDefaultCaffeine() {
CaffeineProperties.CacheSpec spec = caffeineProperties.getDefaultSpec();
Caffeine<Object, Object> builder = Caffeine.newBuilder()
.initialCapacity(spec.getInitialCapacity())
.maximumSize(spec.getMaximumSize())
.expireAfterWrite(spec.getExpireAfterWrite(), TimeUnit.SECONDS)
.expireAfterAccess(spec.getExpireAfterAccess(), TimeUnit.SECONDS);
// 添加移除监听器
builder.removalListener(removalListener);
// 启用统计
if (spec.getRecordStats()) {
builder.recordStats();
}
// 弱引用/软引用配置(GC友好)
if (spec.getWeakKeys()) {
builder.weakKeys();
}
if (spec.getWeakValues()) {
builder.weakValues();
}
if (spec.getSoftValues()) {
builder.softValues();
}
return builder;
}
/**
* 缓存实例Map
* 提供直接访问各个缓存实例的能力
*
* @return 缓存实例映射
*/
@Bean
public Map<String, Cache<String, Object>> cacheMap() {
Map<String, Cache<String, Object>> map = new HashMap<>();
map.put(CacheType.HOT_DATA.getCacheName(), hotDataCache());
map.put(CacheType.NORMAL_DATA.getCacheName(), normalDataCache());
map.put(CacheType.USER_INFO.getCacheName(), userInfoCache());
map.put(CacheType.PRODUCT_INFO.getCacheName(), productInfoCache());
map.put(CacheType.RATE_LIMIT.getCacheName(), rateLimitCache());
return map;
}
/**
* 热点数据缓存实例
*
* 特点:
* - 大容量:10,000条
* - 短过期:100秒
* - 高性能:适合秒杀、热榜等场景
*
* @return Cache实例
*/
@Bean("hotDataCache")
public Cache<String, Object> hotDataCache() {
Cache<String, Object> cache = Caffeine.newBuilder()
.initialCapacity(200)
.maximumSize(CacheType.HOT_DATA.getMaximumSize())
.expireAfterWrite(CacheType.HOT_DATA.getExpireAfterWrite(), TimeUnit.SECONDS)
.recordStats()
.removalListener(removalListener)
.build();
// 注册到 Prometheus
CaffeineCacheMetrics.monitor(meterRegistry, cache,
CacheType.HOT_DATA.getCacheName());
log.info("Hot data cache initialized: maxSize={}, expireAfterWrite={}s",
CacheType.HOT_DATA.getMaximumSize(),
CacheType.HOT_DATA.getExpireAfterWrite());
return cache;
}
/**
* 常规数据缓存实例
*
* 特点:
* - 中等容量:5,000条
* - 中等过期:5分钟写入过期,10分钟访问过期
* - 通用场景:商品详情、用户信息等
*
* @return Cache实例
*/
@Bean("normalDataCache")
public Cache<String, Object> normalDataCache() {
Cache<String, Object> cache = Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(CacheType.NORMAL_DATA.getMaximumSize())
.expireAfterWrite(CacheType.NORMAL_DATA.getExpireAfterWrite(),
TimeUnit.SECONDS)
.expireAfterAccess(CacheType.NORMAL_DATA.getExpireAfterAccess(),
TimeUnit.SECONDS)
.recordStats()
.removalListener(removalListener)
.build();
CaffeineCacheMetrics.monitor(meterRegistry, cache,
CacheType.NORMAL_DATA.getCacheName());
log.info("Normal data cache initialized");
return cache;
}
/**
* 用户信息缓存实例
*
* @return Cache实例
*/
@Bean("userInfoCache")
public Cache<String, Object> userInfoCache() {
Cache<String, Object> cache = Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(CacheType.USER_INFO.getMaximumSize())
.expireAfterWrite(CacheType.USER_INFO.getExpireAfterWrite(),
TimeUnit.SECONDS)
.expireAfterAccess(CacheType.USER_INFO.getExpireAfterAccess(),
TimeUnit.SECONDS)
.recordStats()
.removalListener(removalListener)
.build();
CaffeineCacheMetrics.monitor(meterRegistry, cache,
CacheType.USER_INFO.getCacheName());
return cache;
}
/**
* 商品信息缓存实例
*
* @return Cache实例
*/
@Bean("productInfoCache")
public Cache<String, Object> productInfoCache() {
Cache<String, Object> cache = Caffeine.newBuilder()
.initialCapacity(100)
.maximumSize(CacheType.PRODUCT_INFO.getMaximumSize())
.expireAfterWrite(CacheType.PRODUCT_INFO.getExpireAfterWrite(),
TimeUnit.SECONDS)
.recordStats()
.removalListener(removalListener)
.build();
CaffeineCacheMetrics.monitor(meterRegistry, cache,
CacheType.PRODUCT_INFO.getCacheName());
return cache;
}
/**
* 限流缓存实例
*
* 特点:
* - 极短过期:1秒
* - 大容量:10,000条
* - 无访问续期:expireAfterAccess未设置
*
* @return Cache实例
*/
@Bean("rateLimitCache")
public Cache<String, Object> rateLimitCache() {
Cache<String, Object> cache = Caffeine.newBuilder()
.initialCapacity(1000)
.maximumSize(CacheType.RATE_LIMIT.getMaximumSize())
.expireAfterWrite(CacheType.RATE_LIMIT.getExpireAfterWrite(),
TimeUnit.SECONDS)
.recordStats()
.build();
CaffeineCacheMetrics.monitor(meterRegistry, cache,
CacheType.RATE_LIMIT.getCacheName());
log.info("Rate limit cache initialized");
return cache;
}
/**
* 字典数据缓存实例(支持异步刷新)
*
* 特点:
* - 长过期:1小时
* - 异步刷新:30分钟后自动刷新
* - 适用场景:配置数据、枚举等静态数据
*
* @return LoadingCache实例
*/
@Bean("dictionaryCache")
public LoadingCache<String, Object> dictionaryCache() {
LoadingCache<String, Object> cache = Caffeine.newBuilder()
.initialCapacity(50)
.maximumSize(CacheType.DICTIONARY.getMaximumSize())
.expireAfterWrite(CacheType.DICTIONARY.getExpireAfterWrite(),
TimeUnit.SECONDS)
.refreshAfterWrite(1800, TimeUnit.SECONDS) // 30分钟异步刷新
.recordStats()
.removalListener(removalListener)
.buildAsync(asyncCacheLoader)
.synchronous();
CaffeineCacheMetrics.monitor(meterRegistry, cache,
CacheType.DICTIONARY.getCacheName());
log.info("Dictionary cache initialized with async refresh");
return cache;
}
}
3.4 配置属性类
java
复制代码
package com.enterprise.cache.config.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* Caffeine 缓存配置属性
*
* 支持:
* 1. 多环境配置(dev/test/prod)
* 2. 多实例独立配置
* 3. 动态配置热更新
* 4. 统计和预热配置
*
* @author Enterprise Team
* @version 1.0.0
*/
@Data
@Component
@ConfigurationProperties(prefix = "cache.caffeine")
public class CaffeineProperties {
/**
* 是否启用缓存
*/
private boolean enabled = true;
/**
* 默认配置(所有缓存的基础配置)
*/
private CacheSpec defaultSpec = new CacheSpec();
/**
* 各缓存实例的特定配置
* Key: 缓存名称
* Value: 缓存配置
*/
private Map<String, CacheSpec> specs = new HashMap<>();
/**
* 统计配置
*/
private StatsConfig stats = new StatsConfig();
/**
* 预热配置
*/
private WarmUpConfig warmUp = new WarmUpConfig();
/**
* 缓存规格配置
*/
@Data
public static class CacheSpec {
/**
* 初始容量
* 建议设置为 maximumSize 的 10-20%
*/
private Integer initialCapacity = 100;
/**
* 最大容量
* 超过此值会根据LRU策略驱逐
*/
private Integer maximumSize = 1000;
/**
* 写入后过期时间(秒)
* 从写入时开始计时
*/
private Integer expireAfterWrite = 600;
/**
* 访问后过期时间(秒)
* 从最后一次读或写开始计时
*/
private Integer expireAfterAccess = 300;
/**
* 刷新时间(秒)
* 用于异步刷新,需配合AsyncCacheLoader使用
*/
private Integer refreshAfterWrite;
/**
* 是否开启统计
* 开启后可以获取命中率、驱逐次数等指标
*/
private Boolean recordStats = true;
/**
* 是否使用弱引用键
* 适用于大量短生命周期的键
*/
private Boolean weakKeys = false;
/**
* 是否使用弱引用值
* GC时可能被回收
*/
private Boolean weakValues = false;
/**
* 是否使用软引用值
* 内存不足时会被GC回收
*/
private Boolean softValues = false;
}
/**
* 统计配置
*/
@Data
public static class StatsConfig {
/**
* 是否启用统计
*/
private boolean enabled = true;
/**
* 统计输出间隔(秒)
*/
private int outputInterval = 60;
/**
* 命中率阈值(低于此值告警)
*/
private double hitRateThreshold = 0.7;
}
/**
* 预热配置
*/
@Data
public static class WarmUpConfig {
/**
* 是否启用预热
*/
private boolean enabled = false;
/**
* 预热延迟启动时间(秒)
* 应用启动后延迟多久开始预热
*/
private int delaySeconds = 30;
/**
* 预热数据批次大小
*/
private int batchSize = 100;
}
}
3.5 异步执行器配置
java
复制代码
package com.enterprise.cache.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 异步任务执行器配置
*
* 提供三个专用线程池:
* 1. cacheRefreshExecutor - 缓存刷新
* 2. cacheWarmUpExecutor - 缓存预热
* 3. cacheStatsExecutor - 缓存统计
*
* @author Enterprise Team
* @version 1.0.0
*/
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncExecutorConfig {
/**
* 缓存刷新执行器
*
* 配置说明:
* - 核心线程数:4(根据CPU核心数调整)
* - 最大线程数:8
* - 队列容量:200
* - 拒绝策略:CallerRunsPolicy(调用者运行)
*
* @return Executor
*/
@Bean("cacheRefreshExecutor")
public Executor cacheRefreshExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("cache-refresh-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setKeepAliveSeconds(60);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(30);
executor.initialize();
return executor;
}
/**
* 缓存预热执行器
*
* 配置说明:
* - 核心线程数:2
* - 最大线程数:4
* - 队列容量:50
*
* @return Executor
*/
@Bean("cacheWarmUpExecutor")
public Executor cacheWarmUpExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("cache-warmup-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setKeepAliveSeconds(60);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(30);
executor.initialize();
return executor;
}
/**
* 缓存统计执行器
*
* 配置说明:
* - 核心线程数:1
* - 最大线程数:2
* - 拒绝策略:DiscardOldestPolicy(丢弃最旧任务)
*
* @return Executor
*/
@Bean("cacheStatsExecutor")
public Executor cacheStatsExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("cache-stats-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
executor.setKeepAliveSeconds(60);
executor.initialize();
return executor;
}
}
二、核心功能实现与高级特性
1. 自定义缓存注解
1.1 增强型缓存注解定义
1.1.1 @EnhancedCacheable 注解
java
复制代码
package com.enterprise.cache.annotation;
import com.enterprise.cache.enums.CacheType;
import java.lang.annotation.*;
/**
* 增强型缓存注解
*
* 相比Spring @Cacheable的增强:
* 1. 支持缓存穿透防护(空值缓存、布隆过滤器)
* 2. 支持缓存击穿防护(互斥锁)
* 3. 支持缓存雪崩防护(随机过期时间)
* 4. 支持降级策略
* 5. 支持异步刷新
* 6. 支持监控打点
*
* @author Enterprise Team
* @version 1.0.0
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnhancedCacheable {
/**
* 缓存名称
* 对应CacheType中的cacheName
*/
String cacheName();
/**
* 缓存类型
* 默认为常规数据缓存
*/
CacheType cacheType() default CacheType.NORMAL_DATA;
/**
* 缓存Key
* 支持SpEL表达式,例如:#userId, #user.id
*/
String key();
/**
* 缓存条件
* 支持SpEL表达式,返回true时才缓存
* 例如:#result != null
*/
String condition() default "";
/**
* 排除缓存条件
* 支持SpEL表达式,返回true时不缓存
* 例如:#result.isEmpty()
*/
String unless() default "";
/**
* 是否启用缓存穿透防护
* 开启后会缓存null值,防止频繁查询不存在的数据
*/
boolean preventPenetration() default true;
/**
* 空值缓存时间(秒)
* 仅在preventPenetration=true时生效
*/
int nullValueExpire() default 60;
/**
* 是否启用缓存击穿防护
* 开启后使用互斥锁,同一时间只有一个线程加载数据
*/
boolean preventBreakdown() default false;
/**
* 互斥锁等待超时时间(毫秒)
*/
long lockTimeout() default 3000;
/**
* 是否启用缓存雪崩防护
* 开启后会在过期时间上增加随机偏移
*/
boolean preventAvalanche() default true;
/**
* 过期时间随机偏移范围(秒)
* 例如设置300,实际过期时间会在 [0, 300] 秒内随机
*/
int expireRandomRange() default 300;
/**
* 是否启用布隆过滤器
* 适用于海量数据场景,快速判断key是否可能存在
*/
boolean useBloomFilter() default false;
/**
* 降级返回值
* 当缓存异常时返回的默认值(SpEL表达式)
*/
String fallback() default "";
/**
* 是否异步刷新
* 开启后过期数据先返回旧值,后台异步加载新值
*/
boolean asyncRefresh() default false;
/**
* 是否记录监控指标
*/
boolean recordMetrics() default true;
/**
* 缓存描述
* 用于监控和日志
*/
String description() default "";
}
1.1.2 @EnhancedCacheEvict 注解
java
复制代码
package com.enterprise.cache.annotation;
import java.lang.annotation.*;
/**
* 增强型缓存失效注解
*
* 增强功能:
* 1. 支持批量删除(通配符匹配)
* 2. 支持异步删除
* 3. 支持分布式同步删除
* 4. 支持条件删除
*
* @author Enterprise Team
* @version 1.0.0
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnhancedCacheEvict {
/**
* 缓存名称
*/
String cacheName();
/**
* 缓存Key
* 支持SpEL表达式
* 支持通配符(*)批量删除
* 例如:user:* 删除所有user开头的缓存
*/
String key() default "";
/**
* 是否删除所有缓存
*/
boolean allEntries() default false;
/**
* 是否在方法执行前删除缓存
* true: 方法执行前删除
* false: 方法执行后删除(默认)
*/
boolean beforeInvocation() default false;
/**
* 删除条件
* 支持SpEL表达式
*/
String condition() default "";
/**
* 是否异步删除
* 适用于大量删除操作,避免阻塞业务线程
*/
boolean async() default false;
/**
* 是否同步到其他节点
* 通过Redis Pub/Sub通知其他节点删除本地缓存
*/
boolean syncToCluster() default true;
}
1.1.3 @EnhancedCachePut 注解
java
复制代码
package com.enterprise.cache.annotation;
import com.enterprise.cache.enums.CacheType;
import java.lang.annotation.*;
/**
* 增强型缓存更新注解
*
* 增强功能:
* 1. 支持条件更新
* 2. 支持异步更新
* 3. 支持分布式同步
* 4. 支持TTL动态设置
*
* @author Enterprise Team
* @version 1.0.0
*/
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnhancedCachePut {
/**
* 缓存名称
*/
String cacheName();
/**
* 缓存类型
*/
CacheType cacheType() default CacheType.NORMAL_DATA;
/**
* 缓存Key
* 支持SpEL表达式
*/
String key();
/**
* 更新条件
* 支持SpEL表达式
*/
String condition() default "";
/**
* 排除条件
*/
String unless() default "";
/**
* 是否异步更新
*/
boolean async() default false;
/**
* 是否同步到其他节点
*/
boolean syncToCluster() default true;
/**
* 自定义TTL(秒)
* -1表示使用默认配置
*/
int ttl() default -1;
}
1.2 缓存注解处理器
java
复制代码
package com.enterprise.cache.aspect;
import com.enterprise.cache.annotation.EnhancedCacheable;
import com.enterprise.cache.annotation.EnhancedCacheEvict;
import com.enterprise.cache.annotation.EnhancedCachePut;
import com.enterprise.cache.service.CacheService;
import com.enterprise.cache.util.SpelExpressionUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 缓存注解切面处理器
*
* 处理增强型缓存注解的核心逻辑:
* 1. 缓存查询
* 2. 缓存更新
* 3. 缓存删除
* 4. 防护机制
* 5. 监控打点
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Aspect
@Order(1)
@Component
@RequiredArgsConstructor
public class CacheAspect {
private final CacheService cacheService;
private final SpelExpressionUtils spelUtils;
/**
* 互斥锁Map(用于缓存击穿防护)
* Key: cacheName:cacheKey
*/
private final java.util.concurrent.ConcurrentHashMap<String, Lock> lockMap = new java.util.concurrent.ConcurrentHashMap<>();
/**
* 处理 @EnhancedCacheable 注解
*/
@Around("@annotation(enhancedCacheable)")
public Object handleCacheable(ProceedingJoinPoint joinPoint, EnhancedCacheable enhancedCacheable) throws Throwable {
// 1. 解析缓存Key
String cacheKey = parseCacheKey(joinPoint, enhancedCacheable.key());
String cacheName = enhancedCacheable.cacheName();
String fullCacheKey = cacheName + ":" + cacheKey;
log.debug("Cache query - cacheName: {}, key: {}", cacheName, cacheKey);
// 2. 检查condition条件
if (StringUtils.hasText(enhancedCacheable.condition())) {
boolean condition = spelUtils.parseCondition(joinPoint, enhancedCacheable.condition());
if (!condition) {
log.debug("Condition not met, skip cache for key: {}", fullCacheKey);
return joinPoint.proceed();
}
}
// 3. 查询缓存
Object cachedValue = cacheService.get(cacheName, cacheKey);
if (cachedValue != null) {
// 缓存命中
log.debug("Cache hit for key: {}", fullCacheKey);
// 记录监控指标
if (enhancedCacheable.recordMetrics()) {
recordCacheHit(cacheName);
}
return cachedValue;
}
// 4. 缓存未命中 - 判断是否需要防击穿
if (enhancedCacheable.preventBreakdown()) {
return handleCacheBreakdown(joinPoint, enhancedCacheable, fullCacheKey);
}
// 5. 执行目标方法
Object result = joinPoint.proceed();
// 6. 判断unless条件
if (StringUtils.hasText(enhancedCacheable.unless())) {
boolean unless = spelUtils.parseUnless(result, enhancedCacheable.unless());
if (unless) {
log.debug("Unless condition met, skip caching for key: {}", fullCacheKey);
return result;
}
}
// 7. 缓存结果
cacheResult(enhancedCacheable, cacheName, cacheKey, result);
// 8. 记录监控指标
if (enhancedCacheable.recordMetrics()) {
recordCacheMiss(cacheName);
}
return result;
}
/**
* 处理缓存击穿(互斥锁机制)
*/
private Object handleCacheBreakdown(ProceedingJoinPoint joinPoint,
EnhancedCacheable enhancedCacheable,
String fullCacheKey) throws Throwable {
Lock lock = lockMap.computeIfAbsent(fullCacheKey, k -> new ReentrantLock());
try {
// 尝试获取锁
boolean acquired = lock.tryLock(enhancedCacheable.lockTimeout(), TimeUnit.MILLISECONDS);
if (!acquired) {
log.warn("Failed to acquire lock for key: {}, timeout: {}ms",
fullCacheKey, enhancedCacheable.lockTimeout());
// 降级处理
if (StringUtils.hasText(enhancedCacheable.fallback())) {
return spelUtils.parseFallback(enhancedCacheable.fallback());
}
// 直接查询数据库
return joinPoint.proceed();
}
// 获取锁成功,再次检查缓存(双重检查)
String cacheKey = parseCacheKey(joinPoint, enhancedCacheable.key());
Object cachedValue = cacheService.get(enhancedCacheable.cacheName(), cacheKey);
if (cachedValue != null) {
return cachedValue;
}
// 执行目标方法
Object result = joinPoint.proceed();
// 缓存结果
cacheResult(enhancedCacheable, enhancedCacheable.cacheName(), cacheKey, result);
return result;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Lock interrupted for key: {}", fullCacheKey, e);
throw e;
} finally {
lock.unlock();
}
}
/**
* 缓存结果
*/
private void cacheResult(EnhancedCacheable enhancedCacheable,
String cacheName,
String cacheKey,
Object result) {
if (result == null && !enhancedCacheable.preventPenetration()) {
// 不缓存null值
return;
}
// 计算过期时间
int expireTime = enhancedCacheable.cacheType().getExpireAfterWrite();
// 防雪崩:添加随机偏移
if (enhancedCacheable.preventAvalanche()) {
int randomOffset = (int) (Math.random() * enhancedCacheable.expireRandomRange());
expireTime += randomOffset;
}
// null值使用短过期时间
if (result == null) {
expireTime = enhancedCacheable.nullValueExpire();
}
// 异步刷新模式
if (enhancedCacheable.asyncRefresh()) {
cacheService.putAsync(cacheName, cacheKey, result, expireTime);
} else {
cacheService.put(cacheName, cacheKey, result, expireTime);
}
log.debug("Cached result for key: {}:{}, expire: {}s", cacheName, cacheKey, expireTime);
}
/**
* 处理 @EnhancedCacheEvict 注解
*/
@Around("@annotation(enhancedCacheEvict)")
public Object handleCacheEvict(ProceedingJoinPoint joinPoint, EnhancedCacheEvict enhancedCacheEvict) throws Throwable {
String cacheName = enhancedCacheEvict.cacheName();
// beforeInvocation = true,方法执行前删除缓存
if (enhancedCacheEvict.beforeInvocation()) {
evictCache(joinPoint, enhancedCacheEvict);
}
Object result = joinPoint.proceed();
// beforeInvocation = false,方法执行后删除缓存
if (!enhancedCacheEvict.beforeInvocation()) {
evictCache(joinPoint, enhancedCacheEvict);
}
return result;
}
/**
* 删除缓存
*/
private void evictCache(ProceedingJoinPoint joinPoint, EnhancedCacheEvict enhancedCacheEvict) {
String cacheName = enhancedCacheEvict.cacheName();
// 检查条件
if (StringUtils.hasText(enhancedCacheEvict.condition())) {
boolean condition = spelUtils.parseCondition(joinPoint, enhancedCacheEvict.condition());
if (!condition) {
log.debug("Evict condition not met for cache: {}", cacheName);
return;
}
}
// 删除所有缓存
if (enhancedCacheEvict.allEntries()) {
if (enhancedCacheEvict.async()) {
cacheService.clearAsync(cacheName);
} else {
cacheService.clear(cacheName);
}
log.info("Evicted all entries in cache: {}", cacheName);
return;
}
// 删除指定Key
String cacheKey = parseCacheKey(joinPoint, enhancedCacheEvict.key());
// 支持通配符删除
if (cacheKey.contains("*")) {
cacheService.evictPattern(cacheName, cacheKey);
log.info("Evicted cache with pattern: {}:{}", cacheName, cacheKey);
} else {
if (enhancedCacheEvict.async()) {
cacheService.evictAsync(cacheName, cacheKey);
} else {
cacheService.evict(cacheName, cacheKey);
}
log.debug("Evicted cache: {}:{}", cacheName, cacheKey);
}
// 同步到集群
if (enhancedCacheEvict.syncToCluster()) {
cacheService.publishEvictMessage(cacheName, cacheKey);
}
}
/**
* 处理 @EnhancedCachePut 注解
*/
@Around("@annotation(enhancedCachePut)")
public Object handleCachePut(ProceedingJoinPoint joinPoint, EnhancedCachePut enhancedCachePut) throws Throwable {
Object result = joinPoint.proceed();
// 检查条件
if (StringUtils.hasText(enhancedCachePut.condition())) {
boolean condition = spelUtils.parseCondition(joinPoint, enhancedCachePut.condition());
if (!condition) {
return result;
}
}
// 检查unless
if (StringUtils.hasText(enhancedCachePut.unless())) {
boolean unless = spelUtils.parseUnless(result, enhancedCachePut.unless());
if (unless) {
return result;
}
}
String cacheName = enhancedCachePut.cacheName();
String cacheKey = parseCacheKey(joinPoint, enhancedCachePut.key());
// 获取TTL
int ttl = enhancedCachePut.ttl();
if (ttl == -1) {
ttl = enhancedCachePut.cacheType().getExpireAfterWrite();
}
// 更新缓存
if (enhancedCachePut.async()) {
cacheService.putAsync(cacheName, cacheKey, result, ttl);
} else {
cacheService.put(cacheName, cacheKey, result, ttl);
}
// 同步到集群
if (enhancedCachePut.syncToCluster()) {
cacheService.publishUpdateMessage(cacheName, cacheKey, result);
}
log.debug("Updated cache: {}:{}", cacheName, cacheKey);
return result;
}
/**
* 解析缓存Key(支持SpEL表达式)
*/
private String parseCacheKey(ProceedingJoinPoint joinPoint, String keyExpression) {
return spelUtils.parseKey(joinPoint, keyExpression);
}
/**
* 记录缓存命中
*/
private void recordCacheHit(String cacheName) {
// 集成监控系统(Prometheus/Micrometer)
// metricsService.recordCacheHit(cacheName);
}
/**
* 记录缓存未命中
*/
private void recordCacheMiss(String cacheName) {
// 集成监控系统
// metricsService.recordCacheMiss(cacheName);
}
}
1.3 注解使用示例
java
复制代码
package com.enterprise.cache.example;
import com.enterprise.cache.annotation.EnhancedCacheable;
import com.enterprise.cache.annotation.EnhancedCacheEvict;
import com.enterprise.cache.annotation.EnhancedCachePut;
import com.enterprise.cache.enums.CacheType;
import org.springframework.stereotype.Service;
/**
* 缓存注解使用示例
*/
@Service
public class UserCacheExample {
/**
* 示例1: 基础查询缓存
* - 缓存用户信息
* - 启用穿透防护(null值缓存)
* - 启用雪崩防护(随机过期时间)
*/
@EnhancedCacheable(
cacheName = "userInfo",
cacheType = CacheType.USER_INFO,
key = "'user:' + #userId",
preventPenetration = true,
preventAvalanche = true,
description = "用户基础信息缓存"
)
public UserDTO getUserById(Long userId) {
// 从数据库查询
return userRepository.findById(userId).orElse(null);
}
/**
* 示例2: 条件缓存
* - 只有当用户状态为ACTIVE时才缓存
*/
@EnhancedCacheable(
cacheName = "userInfo",
cacheType = CacheType.USER_INFO,
key = "'user:' + #userId",
condition = "#userId > 0",
unless = "#result == null || #result.status != 'ACTIVE'"
)
public UserDTO getActiveUser(Long userId) {
return userRepository.findActiveUser(userId);
}
/**
* 示例3: 缓存击穿防护
* - 热点数据使用互斥锁
* - 同一时间只有一个线程加载
*/
@EnhancedCacheable(
cacheName = "hotData",
cacheType = CacheType.HOT_DATA,
key = "'product:' + #productId",
preventBreakdown = true,
lockTimeout = 5000,
fallback = "getDefaultProduct()"
)
public ProductDTO getHotProduct(Long productId) {
return productRepository.findById(productId).orElse(null);
}
/**
* 示例4: 异步刷新
* - 过期后先返回旧值
* - 后台异步加载新值
*/
@EnhancedCacheable(
cacheName = "normalData",
cacheType = CacheType.NORMAL_DATA,
key = "'article:' + #articleId",
asyncRefresh = true
)
public ArticleDTO getArticle(Long articleId) {
return articleRepository.findById(articleId).orElse(null);
}
/**
* 示例5: 更新缓存
* - 更新数据后同步更新缓存
*/
@EnhancedCachePut(
cacheName = "userInfo",
cacheType = CacheType.USER_INFO,
key = "'user:' + #user.id",
syncToCluster = true
)
public UserDTO updateUser(UserDTO user) {
return userRepository.save(user);
}
/**
* 示例6: 删除缓存
* - 删除用户后清除缓存
*/
@EnhancedCacheEvict(
cacheName = "userInfo",
key = "'user:' + #userId",
syncToCluster = true
)
public void deleteUser(Long userId) {
userRepository.deleteById(userId);
}
/**
* 示例7: 批量删除缓存
* - 使用通配符删除所有用户缓存
*/
@EnhancedCacheEvict(
cacheName = "userInfo",
key = "'user:*'",
async = true,
syncToCluster = true
)
public void clearAllUserCache() {
// 业务逻辑
}
/**
* 示例8: 条件删除
* - 只有当删除成功时才清除缓存
*/
@EnhancedCacheEvict(
cacheName = "userInfo",
key = "'user:' + #userId",
condition = "#result == true"
)
public boolean softDeleteUser(Long userId) {
return userRepository.softDelete(userId);
}
}
2. 缓存Key生成策略
2.1 Key生成器接口
java
复制代码
package com.enterprise.cache.key;
import org.aspectj.lang.ProceedingJoinPoint;
/**
* 缓存Key生成器接口
*
* @author Enterprise Team
* @version 1.0.0
*/
public interface CacheKeyGenerator {
/**
* 生成缓存Key
*
* @param joinPoint 切点
* @param keyExpression SpEL表达式
* @return 缓存Key
*/
String generateKey(ProceedingJoinPoint joinPoint, String keyExpression);
/**
* 根据参数生成Key
*
* @param prefix 前缀
* @param params 参数
* @return 缓存Key
*/
String generateKey(String prefix, Object... params);
}
2.2 默认Key生成器实现
java
复制代码
package com.enterprise.cache.key.impl;
import com.enterprise.cache.key.CacheKeyGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* 默认缓存Key生成器
*
* 策略:
* 1. 简单类型:直接toString
* 2. 对象类型:JSON序列化后MD5
* 3. 集合类型:拼接后MD5
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component("defaultKeyGenerator")
@RequiredArgsConstructor
public class DefaultCacheKeyGenerator implements CacheKeyGenerator {
private final ObjectMapper objectMapper;
@Override
public String generateKey(ProceedingJoinPoint joinPoint, String keyExpression) {
// 这里简化处理,实际会使用SpEL解析
Object[] args = joinPoint.getArgs();
return generateKey("", args);
}
@Override
public String generateKey(String prefix, Object... params) {
if (params == null || params.length == 0) {
return prefix;
}
StringBuilder keyBuilder = new StringBuilder();
if (StringUtils.hasText(prefix)) {
keyBuilder.append(prefix).append(":");
}
for (Object param : params) {
if (param == null) {
keyBuilder.append("null");
} else if (isSimpleType(param)) {
keyBuilder.append(param);
} else {
// 复杂对象序列化为JSON再MD5
String json = toJson(param);
String md5 = md5(json);
keyBuilder.append(md5);
}
keyBuilder.append(":");
}
// 移除最后一个冒号
if (keyBuilder.length() > 0 && keyBuilder.charAt(keyBuilder.length() - 1) == ':') {
keyBuilder.setLength(keyBuilder.length() - 1);
}
return keyBuilder.toString();
}
/**
* 判断是否为简单类型
*/
private boolean isSimpleType(Object obj) {
return obj instanceof String
|| obj instanceof Number
|| obj instanceof Boolean
|| obj instanceof Character
|| obj.getClass().isPrimitive();
}
/**
* 对象转JSON
*/
private String toJson(Object obj) {
try {
return objectMapper.writeValueAsString(obj);
} catch (JsonProcessingException e) {
log.error("Failed to serialize object to JSON: {}", obj, e);
return obj.toString();
}
}
/**
* MD5加密
*/
private String md5(String text) {
return DigestUtils.md5DigestAsHex(text.getBytes(StandardCharsets.UTF_8));
}
}
2.3 自定义Key生成器
java
复制代码
package com.enterprise.cache.key.impl;
import com.enterprise.cache.key.CacheKeyGenerator;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* 业务自定义Key生成器
*
* 示例:根据业务规则生成Key
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component("businessKeyGenerator")
public class BusinessCacheKeyGenerator implements CacheKeyGenerator {
@Override
public String generateKey(ProceedingJoinPoint joinPoint, String keyExpression) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
// 业务规则:方法名 + 参数拼接
String params = Arrays.stream(args)
.map(Object::toString)
.collect(Collectors.joining("_"));
return methodName + ":" + params;
}
@Override
public String generateKey(String prefix, Object... params) {
return prefix + ":" + Arrays.stream(params)
.map(Object::toString)
.collect(Collectors.joining("_"));
}
}
3. 缓存加载器实现
3.1 异步缓存加载器
java
复制代码
package com.enterprise.cache.loader;
import com.github.benmanes.caffeine.cache.AsyncCacheLoader;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.springframework.stereotype.Component;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
/**
* 异步缓存加载器
*
* 功能:
* 1. 支持异步加载数据
* 2. 过期后先返回旧值,后台刷新
* 3. 避免缓存击穿
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
public class AsyncCaffeineCacheLoader implements AsyncCacheLoader<String, Object> {
private final Executor executor;
private final Function<String, Object> dataLoader;
public AsyncCaffeineCacheLoader(Executor cacheRefreshExecutor,
Function<String, Object> dataLoader) {
this.executor = cacheRefreshExecutor;
this.dataLoader = dataLoader;
}
@Override
public @NonNull CompletableFuture<Object> asyncLoad(@NonNull String key,
@NonNull Executor executor) {
return CompletableFuture.supplyAsync(() -> {
log.debug("Async loading cache for key: {}", key);
try {
Object value = dataLoader.apply(key);
if (value == null) {
log.warn("Loaded null value for key: {}", key);
}
return value;
} catch (Exception e) {
log.error("Failed to load cache for key: {}", key, e);
throw new RuntimeException("Cache load failed: " + key, e);
}
}, this.executor);
}
@Override
public @NonNull CompletableFuture<Object> asyncReload(
@NonNull String key,
@NonNull Object oldValue,
@NonNull Executor executor) {
return CompletableFuture.supplyAsync(() -> {
log.debug("Async reloading cache for key: {}, old value exists: {}",
key, oldValue != null);
try {
Object newValue = dataLoader.apply(key);
if (newValue == null) {
log.warn("Reloaded null value for key: {}, returning old value", key);
return oldValue; // 返回旧值
}
return newValue;
} catch (Exception e) {
log.error("Failed to reload cache for key: {}, returning old value", key, e);
return oldValue; // 降级返回旧值
}
}, this.executor);
}
}
3.2 批量缓存加载器
java
复制代码
package com.enterprise.cache.loader;
import com.github.benmanes.caffeine.cache.CacheLoader;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 批量缓存加载器
*
* 功能:
* 1. 支持批量加载
* 2. 减少数据库查询次数
* 3. 提高加载效率
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class BatchCaffeineCacheLoader implements CacheLoader<String, Object> {
private final Function<Set<String>, Map<String, Object>> batchLoader;
@Override
public @Nullable Object load(@NonNull String key) {
log.debug("Loading single cache for key: {}", key);
try {
Map<String, Object> result = batchLoader.apply(Set.of(key));
return result.get(key);
} catch (Exception e) {
log.error("Failed to load cache for key: {}", key, e);
return null;
}
}
@Override
public @NonNull Map<String, Object> loadAll(@NonNull Set<? extends String> keys) {
log.debug("Batch loading cache for {} keys", keys.size());
try {
Set<String> keySet = keys.stream()
.map(Object::toString)
.collect(Collectors.toSet());
Map<String, Object> result = batchLoader.apply(keySet);
log.debug("Batch loaded {} entries", result.size());
return result != null ? result : new HashMap<>();
} catch (Exception e) {
log.error("Failed to batch load cache for keys: {}", keys, e);
return new HashMap<>();
}
}
}
4. 缓存防护机制
4.1 缓存穿透防护
4.1.1 布隆过滤器实现
java
复制代码
package com.enterprise.cache.guard;
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ConcurrentHashMap;
/**
* 布隆过滤器管理器
*
* 用于缓存穿透防护:
* 1. 快速判断Key是否可能存在
* 2. 避免查询不存在的数据
* 3. 支持多个过滤器实例
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
public class BloomFilterManager {
/**
* 布隆过滤器容器
* Key: 过滤器名称
* Value: 布隆过滤器实例
*/
private final ConcurrentHashMap<String, BloomFilter<String>> bloomFilters = new ConcurrentHashMap<>();
/**
* 默认预期插入数量
*/
private static final int DEFAULT_EXPECTED_INSERTIONS = 1_000_000;
/**
* 默认误判率
*/
private static final double DEFAULT_FPP = 0.01;
@PostConstruct
public void init() {
log.info("BloomFilter manager initialized");
}
/**
* 创建布隆过滤器
*
* @param name 过滤器名称
* @param expectedInsertions 预期插入数量
* @param fpp 误判率 (False Positive Probability)
*/
public void createBloomFilter(String name, int expectedInsertions, double fpp) {
BloomFilter<String> filter = BloomFilter.create(
Funnels.stringFunnel(StandardCharsets.UTF_8),
expectedInsertions,
fpp
);
bloomFilters.put(name, filter);
log.info("Created BloomFilter: {}, expectedInsertions: {}, fpp: {}",
name, expectedInsertions, fpp);
}
/**
* 创建默认配置的布隆过滤器
*/
public void createBloomFilter(String name) {
createBloomFilter(name, DEFAULT_EXPECTED_INSERTIONS, DEFAULT_FPP);
}
/**
* 添加元素到布隆过滤器
*/
public void put(String filterName, String key) {
BloomFilter<String> filter = bloomFilters.get(filterName);
if (filter == null) {
log.warn("BloomFilter not found: {}, creating default", filterName);
createBloomFilter(filterName);
filter = bloomFilters.get(filterName);
}
filter.put(key);
}
/**
* 批量添加元素
*/
public void putAll(String filterName, Iterable<String> keys) {
keys.forEach(key -> put(filterName, key));
}
/**
* 判断元素是否可能存在
*
* @return true: 可能存在, false: 一定不存在
*/
public boolean mightContain(String filterName, String key) {
BloomFilter<String> filter = bloomFilters.get(filterName);
if (filter == null) {
log.warn("BloomFilter not found: {}, returning false", filterName);
return false;
}
return filter.mightContain(key);
}
/**
* 获取过滤器
*/
public BloomFilter<String> getFilter(String name) {
return bloomFilters.get(name);
}
/**
* 移除过滤器
*/
public void removeFilter(String name) {
bloomFilters.remove(name);
log.info("Removed BloomFilter: {}", name);
}
}
4.1.2 空值缓存策略
java
复制代码
package com.enterprise.cache.guard;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Optional;
/**
* 缓存穿透防护器
*
* 策略:
* 1. 布隆过滤器前置判断
* 2. 空值缓存(短TTL)
* 3. 限流降级
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CachePenetrationGuard {
private final BloomFilterManager bloomFilterManager;
/**
* 空值缓存标记
*/
public static final String NULL_VALUE = "@@NULL@@";
/**
* 空值缓存过期时间(秒)
*/
private static final int NULL_VALUE_EXPIRE = 60;
/**
* 检查Key是否可能存在
*
* @param filterName 过滤器名称
* @param key 缓存Key
* @return true: 可能存在, false: 一定不存在
*/
public boolean mightExist(String filterName, String key) {
boolean exists = bloomFilterManager.mightContain(filterName, key);
if (!exists) {
log.debug("Key definitely not exists (BloomFilter): {}", key);
}
return exists;
}
/**
* 注册存在的Key到布隆过滤器
*/
public void registerKey(String filterName, String key) {
bloomFilterManager.put(filterName, key);
log.debug("Registered key to BloomFilter: {}", key);
}
/**
* 批量注册Key
*/
public void registerKeys(String filterName, Iterable<String> keys) {
bloomFilterManager.putAll(filterName, keys);
log.info("Batch registered keys to BloomFilter");
}
/**
* 判断是否为空值缓存
*/
public boolean isNullValue(Object value) {
return NULL_VALUE.equals(value);
}
/**
* 获取空值缓存过期时间
*/
public int getNullValueExpire() {
return NULL_VALUE_EXPIRE;
}
/**
* 包装可能为null的值
*/
public Object wrapNullValue(Object value) {
return value != null ? value : NULL_VALUE;
}
/**
* 解包可能的空值
*/
public Optional<Object> unwrapNullValue(Object value) {
if (isNullValue(value)) {
return Optional.empty();
}
return Optional.ofNullable(value);
}
}
4.2 缓存击穿防护
java
复制代码
package com.enterprise.cache.guard;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
/**
* 缓存击穿防护器
*
* 策略:
* 1. 互斥锁机制(同一Key只有一个线程加载)
* 2. 永不过期(逻辑过期 + 异步刷新)
* 3. 提前刷新
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
public class CacheBreakdownGuard {
/**
* 互斥锁容器
* Key: cacheKey
* Value: Lock
*/
private final ConcurrentHashMap<String, Lock> lockMap = new ConcurrentHashMap<>();
/**
* 默认锁超时时间(秒)
*/
private static final long DEFAULT_LOCK_TIMEOUT = 3;
/**
* 使用互斥锁保护缓存加载
*
* @param key 缓存Key
* @param loader 数据加载器
* @param timeout 锁超时时间(秒)
* @return 加载的数据
*/
public <T> T loadWithLock(String key, Supplier<T> loader, long timeout) {
Lock lock = lockMap.computeIfAbsent(key, k -> new ReentrantLock());
try {
// 尝试获取锁
boolean acquired = lock.tryLock(timeout, TimeUnit.SECONDS);
if (!acquired) {
log.warn("Failed to acquire lock for key: {}, timeout: {}s", key, timeout);
// 超时返回null或抛出异常
return null;
}
try {
log.debug("Lock acquired for key: {}", key);
return loader.get();
} finally {
lock.unlock();
log.debug("Lock released for key: {}", key);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Lock interrupted for key: {}", key, e);
return null;
} finally {
// 清理锁(可选:避免内存泄漏)
lockMap.remove(key);
}
}
/**
* 使用默认超时时间
*/
public <T> T loadWithLock(String key, Supplier<T> loader) {
return loadWithLock(key, loader, DEFAULT_LOCK_TIMEOUT);
}
/**
* 获取锁数量(监控用)
*/
public int getLockCount() {
return lockMap.size();
}
/**
* 清理所有锁
*/
public void clearLocks() {
lockMap.clear();
log.info("All locks cleared");
}
}
4.3 缓存雪崩防护
java
复制代码
package com.enterprise.cache.guard;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
/**
* 缓存雪崩防护器
*
* 策略:
* 1. 过期时间随机化
* 2. 缓存预热
* 3. 限流降级
* 4. 多级缓存
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
public class CacheAvalancheGuard {
/**
* 默认随机偏移范围(秒)
*/
private static final int DEFAULT_RANDOM_RANGE = 300;
/**
* 添加随机过期时间偏移
*
* @param baseExpire 基础过期时间(秒)
* @param randomRange 随机范围(秒)
* @return 添加偏移后的过期时间
*/
public int addRandomExpire(int baseExpire, int randomRange) {
int randomOffset = ThreadLocalRandom.current().nextInt(randomRange);
int finalExpire = baseExpire + randomOffset;
log.debug("Base expire: {}s, random offset: {}s, final expire: {}s",
baseExpire, randomOffset, finalExpire);
return finalExpire;
}
/**
* 使用默认随机范围
*/
public int addRandomExpire(int baseExpire) {
return addRandomExpire(baseExpire, DEFAULT_RANDOM_RANGE);
}
/**
* 获取随机过期时间(百分比偏移)
*
* @param baseExpire 基础过期时间
* @param percentRange 百分比范围 (0-100)
* @return 随机过期时间
*/
public int getRandomExpireByPercent(int baseExpire, int percentRange) {
if (percentRange < 0 || percentRange > 100) {
throw new IllegalArgumentException("percentRange must be between 0 and 100");
}
int maxOffset = (int) (baseExpire * percentRange / 100.0);
int randomOffset = ThreadLocalRandom.current().nextInt(maxOffset);
return baseExpire + randomOffset;
}
/**
* 错峰过期策略
* 将缓存过期时间分散到不同时间段
*
* @param baseExpire 基础过期时间
* @param bucketCount 时间桶数量
* @param bucketIndex 当前桶索引
* @return 错峰后的过期时间
*/
public int getStaggeredExpire(int baseExpire, int bucketCount, int bucketIndex) {
if (bucketCount <= 0 || bucketIndex < 0 || bucketIndex >= bucketCount) {
throw new IllegalArgumentException("Invalid bucket parameters");
}
int bucketSize = baseExpire / bucketCount;
int offset = bucketSize * bucketIndex;
log.debug("Staggered expire - base: {}s, bucket: {}/{}, offset: {}s",
baseExpire, bucketIndex, bucketCount, offset);
return baseExpire + offset;
}
}
5. 分布式缓存一致性
5.1 缓存同步管理器
java
复制代码
package com.enterprise.cache.sync;
import com.enterprise.cache.event.CacheEvictEvent;
import com.enterprise.cache.event.CacheUpdateEvent;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.stereotype.Component;
/**
* 缓存同步管理器
*
* 基于Redis Pub/Sub实现分布式缓存一致性:
* 1. 发布缓存失效消息
* 2. 发布缓存更新消息
* 3. 订阅并处理其他节点的消息
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CacheSyncManager {
private final StringRedisTemplate redisTemplate;
private final ObjectMapper objectMapper;
/**
* 缓存失效Topic
*/
public static final String CACHE_EVICT_TOPIC = "cache:evict";
/**
* 缓存更新Topic
*/
public static final String CACHE_UPDATE_TOPIC = "cache:update";
/**
* 发布缓存失效消息
*
* @param cacheName 缓存名称
* @param cacheKey 缓存Key
*/
public void publishEvictMessage(String cacheName, String cacheKey) {
try {
CacheEvictEvent event = new CacheEvictEvent(cacheName, cacheKey);
String message = objectMapper.writeValueAsString(event);
redisTemplate.convertAndSend(CACHE_EVICT_TOPIC, message);
log.debug("Published cache evict message: {}:{}", cacheName, cacheKey);
} catch (Exception e) {
log.error("Failed to publish evict message: {}:{}", cacheName, cacheKey, e);
}
}
/**
* 发布缓存更新消息
*
* @param cacheName 缓存名称
* @param cacheKey 缓存Key
* @param value 更新的值
*/
public void publishUpdateMessage(String cacheName, String cacheKey, Object value) {
try {
CacheUpdateEvent event = new CacheUpdateEvent(cacheName, cacheKey, value);
String message = objectMapper.writeValueAsString(event);
redisTemplate.convertAndSend(CACHE_UPDATE_TOPIC, message);
log.debug("Published cache update message: {}:{}", cacheName, cacheKey);
} catch (Exception e) {
log.error("Failed to publish update message: {}:{}", cacheName, cacheKey, e);
}
}
/**
* 发布批量失效消息
*/
public void publishBatchEvictMessage(String cacheName, Iterable<String> keys) {
keys.forEach(key -> publishEvictMessage(cacheName, key));
}
/**
* 发布清空缓存消息
*/
public void publishClearMessage(String cacheName) {
publishEvictMessage(cacheName, "*");
}
}
5.2 缓存失效通知
java
复制代码
package com.enterprise.cache.sync;
import com.enterprise.cache.event.CacheEvictEvent;
import com.enterprise.cache.event.CacheUpdateEvent;
import com.enterprise.cache.service.CacheService;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;
/**
* 缓存同步消息监听器
*
* 监听其他节点发送的缓存失效/更新消息
* 并同步更新本地缓存
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CacheSyncListener implements MessageListener {
private final CacheService cacheService;
private final ObjectMapper objectMapper;
@Override
public void onMessage(Message message, byte[] pattern) {
try {
String channel = new String(message.getChannel());
String body = new String(message.getBody());
log.debug("Received cache sync message from channel: {}", channel);
if (CacheSyncManager.CACHE_EVICT_TOPIC.equals(channel)) {
handleEvictMessage(body);
} else if (CacheSyncManager.CACHE_UPDATE_TOPIC.equals(channel)) {
handleUpdateMessage(body);
}
} catch (Exception e) {
log.error("Failed to handle cache sync message", e);
}
}
/**
* 处理缓存失效消息
*/
private void handleEvictMessage(String messageBody) {
try {
CacheEvictEvent event = objectMapper.readValue(messageBody, CacheEvictEvent.class);
String cacheName = event.getCacheName();
String cacheKey = event.getCacheKey();
if ("*".equals(cacheKey)) {
// 清空整个缓存
cacheService.clearLocal(cacheName);
log.info("Cleared local cache: {}", cacheName);
} else {
// 删除指定Key
cacheService.evictLocal(cacheName, cacheKey);
log.debug("Evicted local cache: {}:{}", cacheName, cacheKey);
}
} catch (Exception e) {
log.error("Failed to handle evict message: {}", messageBody, e);
}
}
/**
* 处理缓存更新消息
*/
private void handleUpdateMessage(String messageBody) {
try {
CacheUpdateEvent event = objectMapper.readValue(messageBody, CacheUpdateEvent.class);
String cacheName = event.getCacheName();
String cacheKey = event.getCacheKey();
Object value = event.getValue();
// 更新本地缓存
cacheService.putLocal(cacheName, cacheKey, value);
log.debug("Updated local cache: {}:{}", cacheName, cacheKey);
} catch (Exception e) {
log.error("Failed to handle update message: {}", messageBody, e);
}
}
}
6. 缓存预热机制
6.1 预热数据加载器
java
复制代码
package com.enterprise.cache.warmup;
import com.enterprise.cache.service.CacheService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
/**
* 缓存预热数据加载器
*
* 功能:
* 1. 应用启动时预加载热点数据
* 2. 批量加载数据
* 3. 异步预热,不阻塞启动
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CacheWarmUpLoader {
private final CacheService cacheService;
/**
* 预热用户信息缓存
*/
public void warmUpUserCache(List<Long> userIds) {
log.info("Starting to warm up user cache, count: {}", userIds.size());
long startTime = System.currentTimeMillis();
int successCount = 0;
for (Long userId : userIds) {
try {
// 从数据库加载用户信息
// UserDTO user = userService.loadUser(userId);
// 放入缓存
// cacheService.put("userInfo", "user:" + userId, user);
successCount++;
} catch (Exception e) {
log.error("Failed to warm up user cache: {}", userId, e);
}
}
long costTime = System.currentTimeMillis() - startTime;
log.info("User cache warm-up completed, success: {}/{}, cost: {}ms",
successCount, userIds.size(), costTime);
}
/**
* 批量预热缓存(通用方法)
*
* @param cacheName 缓存名称
* @param dataMap Key-Value映射
*/
public void batchWarmUp(String cacheName, Map<String, Object> dataMap) {
log.info("Batch warming up cache: {}, size: {}", cacheName, dataMap.size());
long startTime = System.currentTimeMillis();
int successCount = 0;
for (Map.Entry<String, Object> entry : dataMap.entrySet()) {
try {
cacheService.put(cacheName, entry.getKey(), entry.getValue());
successCount++;
} catch (Exception e) {
log.error("Failed to warm up cache: {}:{}", cacheName, entry.getKey(), e);
}
}
long costTime = System.currentTimeMillis() - startTime;
log.info("Cache warm-up completed for {}, success: {}/{}, cost: {}ms",
cacheName, successCount, dataMap.size(), costTime);
}
/**
* 预热字典数据
*/
public void warmUpDictionaryCache() {
log.info("Starting to warm up dictionary cache");
// 从数据库加载字典数据
// Map<String, DictDTO> dictMap = dictService.loadAllDictionaries();
// 批量预热
// batchWarmUp("dictionary", dictMap);
}
/**
* 预热热点商品
*/
public void warmUpHotProducts(List<Long> productIds) {
log.info("Starting to warm up hot products, count: {}", productIds.size());
// 类似用户缓存预热逻辑
}
}
6.2 预热任务调度
java
复制代码
package com.enterprise.cache.warmup;
import com.enterprise.cache.config.properties.CaffeineProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
/**
* 缓存预热任务调度器
*
* 功能:
* 1. 应用启动时自动预热
* 2. 定时刷新热点数据
* 3. 异步执行,不阻塞主流程
*
* @author Enterprise Team
* @version 1.0.0
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class CacheWarmUpScheduler {
private final CacheWarmUpLoader warmUpLoader;
private final CaffeineProperties caffeineProperties;
/**
* 应用启动后执行预热
*/
@Async("cacheWarmUpExecutor")
@EventListener(ApplicationReadyEvent.class)
public void onApplicationReady() {
if (!caffeineProperties.getWarmUp().isEnabled()) {
log.info("Cache warm-up is disabled");
return;
}
int delaySeconds = caffeineProperties.getWarmUp().getDelaySeconds();
log.info("Cache warm-up will start after {} seconds", delaySeconds);
try {
Thread.sleep(delaySeconds * 1000L);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Warm-up delay interrupted", e);
return;
}
executeWarmUp();
}
/**
* 执行预热任务
*/
private void executeWarmUp() {
log.info("=== Starting cache warm-up ===");
long startTime = System.currentTimeMillis();
try {
// 1. 预热字典数据
warmUpLoader.warmUpDictionaryCache();
// 2. 预热热点用户
// List<Long> hotUserIds = getHotUserIds();
// warmUpLoader.warmUpUserCache(hotUserIds);
// 3. 预热热点商品
// List<Long> hotProductIds = getHotProductIds();
// warmUpLoader.warmUpHotProducts(hotProductIds);
} catch (Exception e) {
log.error("Cache warm-up failed", e);
}
long costTime = System.currentTimeMillis() - startTime;
log.info("=== Cache warm-up completed, total cost: {}ms ===", costTime);
}
/**
* 定时刷新热点数据
* 每小时执行一次
*/
@Async("cacheWarmUpExecutor")
@Scheduled(cron = "0 0 * * * ?")
public void scheduledRefreshHotData() {
log.info("Starting scheduled hot data refresh");
// 刷新热点商品
// warmUpLoader.warmUpHotProducts(getHotProductIds());
}
/**
* 获取热点用户ID列表
* 可以从统计系统或推荐系统获取
*/
private List<Long> getHotUserIds() {
// TODO: 实现获取热点用户逻辑
return List.of();
}
/**
* 获取热点商品ID列表
*/
private List<Long> getHotProductIds() {
// TODO: 实现获取热点商品逻辑
return List.of();
}
}