结合你的 Java 后端开发背景(Spring Boot)和对高性能、缓存架构的关注,以下是 Caffeine 缓存库 的 零基础到实战优化指南,聚焦「理论 + 可执行代码 + Spring Boot 集成 + 多级缓存实践」,完全适配你的技术栈和需求:
一、Caffeine 核心认知
1. 是什么?
- 定位:Java 领域高性能本地缓存库,基于 Google Guava Cache 改进,性能提升 10-100 倍(官方 benchmark 数据),支持丰富的缓存策略和过期机制。
- 核心优势 :
- 基于「W-TinyLFU」淘汰算法(兼顾频率和近期性),缓存命中率远超 LRU/LFU;
- 支持异步加载、过期时间(固定 / 滑动)、最大容量限制、缓存刷新等特性;
- 线程安全,并发性能优异(底层使用 ConcurrentHashMap 优化);
- 轻量级(依赖仅 100KB 左右),无额外依赖。
- 适用场景 :
- 后端服务本地缓存(热点数据、配置信息、接口结果缓存);
- 多级缓存架构中的「本地缓存层」(搭配 Redis 实现「本地缓存 + 分布式缓存」);
- 高并发场景下减少数据库 / Redis 访问压力(如秒杀、高频查询接口)。
二、快速入门:基础使用(纯 Java)
1. 引入依赖(Maven)
xml
<!-- Caffeine 核心依赖 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version> <!-- 最新稳定版 -->
</dependency>
2. 核心 API 实践(可直接运行)
示例 1:基本缓存(手动 put/get/ 过期配置)
java
运行
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Cache;
import java.util.concurrent.TimeUnit;
public class CaffeineBasicDemo {
public static void main(String[] args) throws InterruptedException {
// 1. 构建缓存实例(配置:最大容量100,过期时间5秒(写入后))
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100) // 最大缓存条目数
.expireAfterWrite(5, TimeUnit.SECONDS) // 写入后5秒过期
.build();
// 2. 写入缓存
cache.put("user:1", "张三");
cache.put("user:2", "李四");
// 3. 读取缓存(存在则返回值,不存在返回null)
System.out.println(cache.getIfPresent("user:1")); // 输出:张三
// 4. 过期测试(等待6秒后读取)
Thread.sleep(6000);
System.out.println(cache.getIfPresent("user:1")); // 输出:null
}
}
示例 2:自动加载缓存(缓存不存在时自动触发加载逻辑)
java
运行
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import java.util.concurrent.TimeUnit;
public class CaffeineLoadingCacheDemo {
public static void main(String[] args) {
// 1. 构建 LoadingCache(配置:滑动过期3秒,自动加载)
LoadingCache<String, String> loadingCache = Caffeine.newBuilder()
.maximumSize(50)
.expireAfterAccess(3, TimeUnit.SECONDS) // 滑动过期(3秒未访问则过期)
.build(key -> loadDataFromDB(key)); // 缓存不存在时执行加载逻辑
// 2. 首次访问:触发 loadDataFromDB
System.out.println(loadingCache.get("user:3")); // 输出:从数据库加载用户3
// 3. 再次访问:直接从缓存获取(3秒内)
System.out.println(loadingCache.get("user:3")); // 输出:从数据库加载用户3(缓存命中)
// 4. 滑动过期测试(等待4秒后访问)
try {
Thread.sleep(4000);
System.out.println(loadingCache.get("user:3")); // 输出:从数据库加载用户3(重新加载)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 模拟从数据库加载数据(实际项目中替换为DAO/Service调用)
private static String loadDataFromDB(String key) {
System.out.print("从数据库加载");
return key.split(":")[1] + "(数据库数据)";
}
}
三、Spring Boot 集成 Caffeine(实战核心)
Spring Boot 2.x+ 已原生支持 Caffeine 作为缓存管理器,以下是 生产级配置 + 使用示例:
1. 引入依赖(Spring Boot 专用)
xml
<!-- Spring Boot 缓存抽象 + Caffeine -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.1.8</version>
</dependency>
2. 配置文件(application.yml)
yaml
spring:
cache:
type: caffeine # 指定缓存类型为 Caffeine
caffeine:
spec: maximumSize=1000,expireAfterWrite=10s # 缓存规格(最大1000条,写入后10秒过期)
# 可选:指定缓存管理器名称(多缓存配置时使用)
cache-manager:
name: caffeineCacheManager
3. 缓存配置类(自定义高级策略)
如果需要多缓存策略(如不同接口用不同过期时间),可通过配置类扩展:
java
运行
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Configuration
@EnableCaching // 启用Spring缓存注解(@Cacheable、@CacheEvict等)
public class CaffeineCacheConfig {
// 定义多个缓存策略(按需扩展)
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<CaffeineCache> caches = new ArrayList<>();
// 缓存1:用户信息缓存(最大500条,5分钟过期)
caches.add(new CaffeineCache(
"userCache", // 缓存名称(@Cacheable中指定)
Caffeine.newBuilder()
.maximumSize(500)
.expireAfterWrite(5, TimeUnit.MINUTES)
.build()
));
// 缓存2:商品列表缓存(最大1000条,1分钟过期)
caches.add(new CaffeineCache(
"productCache",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build()
));
// 缓存3:热点数据缓存(无过期时间,最大200条,基于LRU淘汰)
caches.add(new CaffeineCache(
"hotDataCache",
Caffeine.newBuilder()
.maximumSize(200)
.evictionListener((key, value, cause) ->
System.out.printf("缓存淘汰:key=%s, cause=%s%n", key, cause)
) // 淘汰监听器(可选)
.build()
));
cacheManager.setCaches(caches);
return cacheManager;
}
}
4. 业务层使用缓存注解
java
运行
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
// 1. 查询用户:缓存存在则返回缓存,不存在则执行方法并缓存结果
@Cacheable(value = "userCache", key = "#userId") // value指定缓存名称,key指定缓存键
public User getUserById(Long userId) {
// 模拟数据库查询(实际项目中替换为Mapper调用)
System.out.println("从数据库查询用户:" + userId);
return new User(userId, "用户" + userId, 25);
}
// 2. 更新用户:更新数据库后同步更新缓存
@CachePut(value = "userCache", key = "#user.id") // 缓存键与查询一致
public User updateUser(User user) {
System.out.println("更新数据库用户:" + user.getId());
user.setName("更新后的" + user.getName());
return user;
}
// 3. 删除用户:删除数据库后删除缓存
@CacheEvict(value = "userCache", key = "#userId") // 清除指定key的缓存
public void deleteUser(Long userId) {
System.out.println("删除数据库用户:" + userId);
}
// 4. 清空缓存:清除整个userCache缓存
@CacheEvict(value = "userCache", allEntries = true)
public void clearUserCache() {
System.out.println("清空用户缓存");
}
// 模拟User实体类
public static class User {
private Long id;
private String name;
private Integer age;
// 构造器、getter/setter省略
}
}
5. 测试缓存效果(Controller 层)
java
运行
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@RestController
@RequestMapping("/users")
public class UserController {
@Resource
private UserService userService;
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
// 第一次访问:执行getUserById(数据库查询),后续访问:直接返回缓存
return userService.getUserById(id);
}
@PutMapping
public User updateUser(@RequestBody UserService.User user) {
return userService.updateUser(user);
}
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
return "删除成功";
}
@DeleteMapping("/cache/clear")
public String clearCache() {
userService.clearUserCache();
return "缓存清空成功";
}
}
四、高级特性与性能优化
1. 缓存策略选择(关键优化点)
| 策略 | 方法 | 适用场景 |
|---|---|---|
| 固定过期(写入后) | expireAfterWrite(time) |
数据更新频率低(如配置、静态数据) |
| 滑动过期(访问后) | expireAfterAccess(time) |
数据更新频率高,需根据访问频率保留热点数据 |
| 无过期 + 容量限制 | maximumSize(size) |
热点数据,依赖淘汰算法自动清理冷数据 |
| 缓存刷新(定时重载) | refreshAfterWrite(time) |
数据需定时更新(如实时统计数据,避免缓存穿透) |
优化建议:
- 热点查询接口用
expireAfterAccess(30s~5min)+ 合理maximumSize; - 配置类 / 静态数据用
expireAfterWrite(1h~24h); - 实时性要求高的数据用
refreshAfterWrite(10s~1min)(结合异步加载)。
2. 异步加载与线程池优化
Caffeine 支持异步加载(避免阻塞主线程),结合 Spring 线程池配置:
java
运行
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Configuration
public class AsyncCaffeineConfig {
// 配置线程池(异步加载缓存时使用)
@Bean
public ExecutorService cacheExecutor() {
return Executors.newFixedThreadPool(5); // 5个核心线程
}
// 构建异步加载缓存
@Bean
public AsyncLoadingCache<String, String> asyncHotDataCache(ExecutorService cacheExecutor) {
return Caffeine.newBuilder()
.maximumSize(200)
.expireAfterAccess(2, TimeUnit.MINUTES)
.executor(cacheExecutor) // 指定线程池
.buildAsync(key -> loadHotDataAsync(key)); // 异步加载逻辑
}
// 模拟异步加载数据(如调用远程接口、查询数据库)
private String loadHotDataAsync(String key) {
System.out.println("异步加载热点数据:" + key);
return "热点数据-" + key;
}
}
3. 缓存穿透 / 击穿 / 雪崩防护(生产必看)
(1)缓存穿透(查询不存在的数据,导致缓存失效,直击数据库)
- 解决方案:缓存空值 + 布隆过滤器
java
运行
// 方式1:缓存空值(@Cacheable中配置)
@Cacheable(value = "userCache", key = "#userId", unless = "#result == null")
public User getUserById(Long userId) {
User user = userMapper.selectById(userId);
// 若用户不存在,返回null(不会缓存),但可优化为缓存空对象
return user == null ? new User(userId, "空用户", 0) : user;
}
// 方式2:布隆过滤器(拦截不存在的key,避免查询数据库)
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.nio.charset.StandardCharsets;
@Configuration
public class BloomFilterConfig {
// 初始化布隆过滤器(假设用户ID范围1~1000000)
@Bean
public BloomFilter<Long> userBloomFilter() {
int expectedInsertions = 1000000; // 预计插入数量
double fpp = 0.01; // 误判率(越低越耗内存)
return BloomFilter.create(
Funnels.longFunnel(),
expectedInsertions,
fpp
);
}
}
// 业务层使用布隆过滤器
@Service
public class UserService {
@Resource
private BloomFilter<Long> userBloomFilter;
@Cacheable(value = "userCache", key = "#userId")
public User getUserById(Long userId) {
// 先通过布隆过滤器判断:不存在则直接返回null,不查询数据库
if (!userBloomFilter.mightContain(userId)) {
System.out.println("布隆过滤器拦截不存在的用户ID:" + userId);
return null;
}
// 存在则查询数据库
return userMapper.selectById(userId);
}
}
(2)缓存击穿(热点 key 过期瞬间,大量请求直击数据库)
- 解决方案:互斥锁 + 热点 key 永不过期
java
运行
// 方式1:互斥锁(使用Redis分布式锁,避免并发查询数据库)
@Service
public class ProductService {
@Resource
private StringRedisTemplate redisTemplate;
@Resource
private ProductMapper productMapper;
@Cacheable(value = "productCache", key = "#productId")
public Product getProductById(Long productId) {
// 1. 尝试获取分布式锁
String lockKey = "lock:product:" + productId;
boolean locked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 3, TimeUnit.SECONDS);
if (locked) {
try {
// 2. 加锁成功:查询数据库并缓存
Product product = productMapper.selectById(productId);
return product;
} finally {
// 3. 释放锁
redisTemplate.delete(lockKey);
}
} else {
// 4. 加锁失败:重试(或返回默认值)
try {
Thread.sleep(100); // 等待100ms后重试
} catch (InterruptedException e) {
e.printStackTrace();
}
return getProductById(productId); // 递归重试
}
}
}
// 方式2:热点key永不过期(定期后台刷新)
@Scheduled(fixedRate = 5 * 60 * 1000) // 每5分钟刷新一次热点商品缓存
public void refreshHotProductCache() {
List<Long> hotProductIds = Arrays.asList(1001L, 1002L, 1003L); // 热点商品ID列表
for (Long productId : hotProductIds) {
Product product = productMapper.selectById(productId);
// 手动更新缓存
redisTemplate.opsForValue().set("productCache:" + productId, product, 1, TimeUnit.HOURS);
}
}
(3)缓存雪崩(大量 key 同时过期,导致数据库压力骤增)
- 解决方案:过期时间随机化 + 多级缓存
java
运行
// 方式1:过期时间随机化(避免key集中过期)
@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
List<CaffeineCache> caches = new ArrayList<>();
caches.add(new CaffeineCache(
"productCache",
Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.MINUTES)
// 过期时间随机化(1分钟 ± 30秒)
.expireAfterWrite((long) (60 + Math.random() * 60), TimeUnit.SECONDS)
.build()
));
cacheManager.setCaches(caches);
return cacheManager;
}
// 方式2:多级缓存(本地缓存 + Redis分布式缓存)
@Service
public class MultiLevelCacheService {
@Resource
private LoadingCache<String, Product> localCache; // Caffeine本地缓存
@Resource
private StringRedisTemplate redisTemplate;
@Resource
private ProductMapper productMapper;
public Product getProductById(Long productId) {
String key = "product:" + productId;
Product product;
// 1. 先查本地缓存(Caffeine)
product = localCache.getIfPresent(key);
if (product != null) {
return product;
}
// 2. 本地缓存未命中:查Redis分布式缓存
String redisValue = redisTemplate.opsForValue().get(key);
if (redisValue != null) {
product = JSON.parseObject(redisValue, Product.class);
// 同步到本地缓存
localCache.put(key, product);
return product;
}
// 3. Redis未命中:查数据库,同步到Redis和本地缓存
product = productMapper.selectById(productId);
redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 30, TimeUnit.MINUTES);
localCache.put(key, product);
return product;
}
}
五、Windows 环境下的调试与问题排查
1. 缓存可视化监控(Windows 本地开发)
使用 caffeine-monitor 依赖查看缓存状态(开发环境专用):
xml
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine-monitor</artifactId>
<version>3.1.8</version>
<scope>test</scope>
</dependency>
调试代码:
java
运行
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import java.util.concurrent.TimeUnit;
public class CaffeineMonitorDemo {
public static void main(String[] args) {
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100)
.recordStats() // 开启统计
.expireAfterWrite(5, TimeUnit.SECONDS)
.build();
// 模拟缓存操作
cache.put("a", "1");
cache.getIfPresent("a");
cache.getIfPresent("b"); // 缓存未命中
// 打印缓存统计信息
CacheStats stats = cache.stats();
System.out.println("缓存命中率:" + stats.hitRate()); // 输出:0.5
System.out.println("未命中率:" + stats.missRate()); // 输出:0.5
System.out.println("缓存条目数:" + cache.estimatedSize()); // 输出:1
}
}
2. Windows 下常见问题与解决方案
| 问题现象 | 原因分析 | 解决方案 |
|---|---|---|
| 缓存注解不生效 | 未加 @EnableCaching 注解 |
在配置类上添加 @EnableCaching |
| 缓存键冲突 | key 生成规则重复(如参数类型不同) | 显式指定 key(如 key = "#userId.toString()") |
| 本地缓存占用内存过高 | maximumSize 配置过大 |
减小 maximumSize,添加 evictionListener 监控淘汰情况 |
| 异步加载缓存阻塞主线程 | 线程池核心线程数过少 | 增大线程池核心线程数(如 newFixedThreadPool(10)) |
六、扩展:Caffeine + Redis 多级缓存架构(生产实战)
结合你之前关注的 Redis,推荐 「本地缓存(Caffeine)+ 分布式缓存(Redis)」 架构,兼顾性能和一致性:
java
运行
@Service
public class MultiLevelCacheService {
// 1. 本地缓存(Caffeine)
private final LoadingCache<String, User> localCache = Caffeine.newBuilder()
.maximumSize(500)
.expireAfterAccess(1, TimeUnit.MINUTES)
.build(key -> loadFromRedis(key));
// 2. 分布式缓存(Redis)
@Resource
private StringRedisTemplate redisTemplate;
// 3. 数据库
@Resource
private UserMapper userMapper;
// 查询用户:本地缓存 → Redis → 数据库
public User getUserById(Long userId) {
String key = "user:" + userId;
User user;
// 步骤1:查询本地缓存
user = localCache.get(key);
if (user != null) {
return user;
}
// 步骤2:查询Redis
String redisValue = redisTemplate.opsForValue().get(key);
if (redisValue != null) {
user = JSON.parseObject(redisValue, User.class);
// 同步到本地缓存
localCache.put(key, user);
return user;
}
// 步骤3:查询数据库
user = userMapper.selectById(userId);
if (user != null) {
// 同步到Redis(30分钟过期)
redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 30, TimeUnit.MINUTES);
// 同步到本地缓存
localCache.put(key, user);
}
return user;
}
// 更新用户:数据库 → Redis → 本地缓存
public void updateUser(User user) {
String key = "user:" + user.getId();
// 步骤1:更新数据库
userMapper.updateById(user);
// 步骤2:更新Redis
redisTemplate.opsForValue().set(key, JSON.toJSONString(user), 30, TimeUnit.MINUTES);
// 步骤3:更新本地缓存
localCache.put(key, user);
}
// 从Redis加载数据(本地缓存加载逻辑)
private User loadFromRedis(String key) {
String redisValue = redisTemplate.opsForValue().get(key);
return redisValue != null ? JSON.parseObject(redisValue, User.class) : null;
}
}
架构优势:
- 本地缓存:响应时间 < 1ms,减轻 Redis 压力;
- Redis 缓存:解决分布式系统缓存一致性问题;
- 数据库:最终数据来源,避免缓存雪崩。
总结
Caffeine 是 Java 本地缓存的首选方案,结合 Spring Boot 注解可快速集成到项目中。核心要点:
- 基础使用:通过
Caffeine.newBuilder()配置缓存策略,@Cacheable等注解简化开发; - 性能优化:根据场景选择过期策略,配置合理的
maximumSize和线程池; - 生产防护:针对穿透 / 击穿 / 雪崩问题,结合布隆过滤器、互斥锁、随机过期时间;
- 架构扩展:与 Redis 搭配实现多级缓存,兼顾性能和分布式一致性。