1、简述
在现代应用中,缓存是提高系统性能的关键手段。Caffeine 是一个基于 Java 的高性能本地缓存库,具备优雅的设计和强大的功能,包括基于容量、时间的自动过期,以及异步刷新缓存数据等特性。
样例代码: https://gitee.com/lhdxhl/springboot-example.git
本文将详细介绍如何在 Spring Boot 中集成 Caffeine,并结合具体代码示例,展示其使用方法。
2、主要特点
Caffeine 是 Guava Cache 的替代品,性能优异,主要特点包括:
- 高性能:基于基准测试,性能优于其他主流缓存库。
- 灵活配置:支持容量限制、时间过期、异步加载等。
- 成熟设计:使用 Window TinyLFU 算法,提高缓存命中率。
在使用 Caffeine 之前,需要添加其依赖。以下是 Caffeine 的 Maven 依赖:
<!-- Caffeine Cache -->
<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>2.5.5</version>
</dependency>
3、配置 Caffeine 缓存
3.1 开启缓存支持
在主类上添加 @EnableCaching 注解:
package com.lm.caffeine;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@EnableCaching
@SpringBootApplication
public class CaffeineApplication {
public static void main(String[] args) {
SpringApplication.run(CaffeineApplication.class, args);
}
}
3.2 配置缓存属性
在 application.yml 中定义缓存相关的配置:
spring:
cache:
type: caffeine
cache-names: usersCache, productsCache # 定义缓存名称
caffeine:
spec: maximumSize=1024,refreshAfterWrite=60s
3.3 自定义缓存配置
创建一个配置类,定制化 Caffeine 缓存行为:
package com.lm.caffeine.config;
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.caffeine.CaffeineCacheManager;
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
public class CacheConfig {
/**
* SimpleCacheManager:
* 这种缓存管理器允许你在应用程序启动时通过配置多个CaffeineCache来创建多个缓存。
* 这种方式可以让你为每个方法单独配置缓存过期时间。
* @Author shdxhl
* @Method caffeineCacheManager
* @Date 9:13 2023-06-13
* @Param []
* @return void
**/
// @Bean
// public CacheManager caffeineCacheManager() {
// SimpleCacheManager cacheManager = new SimpleCacheManager();
// List<CaffeineCache> caches = new ArrayList<>();
// caches.add(new CaffeineCache("usersCache",
// Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.SECONDS).build()));
// caches.add(new CaffeineCache("productsCache",
// Caffeine.newBuilder().expireAfterWrite(10, TimeUnit.SECONDS).build()));
// cacheManager.setCaches(caches);
// return cacheManager;
// }
/**
* CaffeineCacheManager:
* 这种缓存管理器使用了一个全局的Caffeine配置来创建所有的缓存。
* 这种方式不能为每个方法单独配置缓存过期时间,但是可以在程序启动时配置全局的缓存配置,这样就可以轻松地设置所有方法的缓存过期时间。
* @Author shdxhl
* @Method cacheManager
* @Date 9:13 2023-06-13
* @Param []
* @return void
**/
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
// 初始大小
.initialCapacity(10)
// 最大容量
.maximumSize(100)
// 打开统计
.recordStats()
// 5分钟不访问自动丢弃
.expireAfterAccess(5, TimeUnit.MINUTES);
caffeineCacheManager.setCaffeine(caffeine);
// 设定缓存器名称
caffeineCacheManager.setCacheNames(getCacheNames());
// 值不可为空
caffeineCacheManager.setAllowNullValues(false);
return caffeineCacheManager;
}
private static List<String> getCacheNames() {
List<String> names = new ArrayList<>(1);
names.add("usersCache");
names.add("productsCache");
return names;
}
}
4、具体示例
4.1 在 Service 层添加缓存
使用 @Cacheable 注解实现缓存:
package com.lm.caffeine.service;
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 {
@Cacheable(value = "usersCache", key = "#id",unless = "#result==null")
public String getUserById(String id) {
System.out.println("查询数据库...");
return "User-" + id;
}
/**
* 更新缓存
*/
@CachePut(cacheNames = "usersCache", key = "#id",unless = "#result==null")
public String updateUser(String id) {
System.out.println("更新数据库...");
return "User-" + id;
}
/**
* 删除缓存
*/
@CacheEvict(cacheNames = "usersCache", key = "#id")
public String delUser(String id) {
System.out.println("删除数据库...");
return "User-" + id;
}
}
4.2 测试缓存效果
创建一个简单的 Controller 测试缓存功能:
package com.lm.caffeine.controller;
import com.lm.caffeine.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/getUserById")
public String getUserById(@RequestParam String id) {
String userId = userService.getUserById(id);
return "Order created with ID: " + userId;
}
}
5、高级运用
5.1 基于最大大小和时间的缓存清理
为一个用户数据查询接口实现缓存,要求缓存的最大条目数为 100,同时设置 5 分钟的过期时间。
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class CacheExample {
public static void main(String[] args) {
// 创建缓存实例
Cache<String, String> cache = Caffeine.newBuilder()
.maximumSize(100) // 最大条目数
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后 5 分钟过期
.build();
// 添加缓存
cache.put("user:1", "John Doe");
// 获取缓存
String value = cache.getIfPresent("user:1");
System.out.println("从缓存获取的值: " + value);
// 缓存过期后返回 null
try {
Thread.sleep(TimeUnit.MINUTES.toMillis(6));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
value = cache.getIfPresent("user:1");
System.out.println("过期后获取的值: " + value);
}
}
5.2 基于权重的缓存清理
根据缓存的对象大小(如 JSON 的字节数)进行清理,以确保缓存不会占用过多内存。
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
public class WeightedCacheExample {
public static void main(String[] args) {
// 创建缓存实例
Cache<String, String> cache = Caffeine.newBuilder()
.maximumWeight(10_000) // 最大权重(例如字节数)
.weigher((String key, String value) -> value.length()) // 权重计算
.build();
// 添加缓存
cache.put("key1", "smallValue"); // 权重较小
cache.put("key2", "thisIsALargeValueExceedingWeightLimit"); // 权重大
// 读取缓存
System.out.println("key1: " + cache.getIfPresent("key1"));
System.out.println("key2: " + cache.getIfPresent("key2"));
}
}
5.3 异步刷新缓存
缓存中的数据需要定期刷新,以确保数据的实时性,但不希望用户直接触发时等待加载。
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class AsyncCacheExample {
public static void main(String[] args) {
// 创建异步缓存实例
AsyncLoadingCache<String, String> asyncCache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES) // 写入后 5 分钟过期
.refreshAfterWrite(2, TimeUnit.MINUTES) // 写入后 2 分钟刷新
.buildAsync(key -> loadDataFromDatabase(key)); // 异步加载
// 异步获取缓存
asyncCache.get("user:1").thenAccept(value -> System.out.println("缓存值: " + value));
// 主线程等待
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private static String loadDataFromDatabase(String key) {
System.out.println("加载数据库数据 for key: " + key);
return "UserData:" + key;
}
}
5.4 统计缓存使用情况
监控缓存命中率和加载时间,便于优化缓存策略。
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.concurrent.TimeUnit;
public class CacheStatsExample {
public static void main(String[] args) {
// 创建缓存实例
Cache<String, String> cache = Caffeine.newBuilder()
.expireAfterWrite(5, TimeUnit.MINUTES)
.recordStats() // 启用统计
.build();
// 使用缓存
cache.put("key1", "value1");
cache.getIfPresent("key1");
cache.getIfPresent("key2"); // 未命中
// 打印统计信息
System.out.println("缓存统计: " + cache.stats());
}
}
6、总结
通过 Spring Boot 集成 Caffeine,我们可以轻松实现高效的本地缓存,并灵活定制缓存行为。
核心功能总结:
- 快速集成:通过 spring-boot-starter-cache 快速启用缓存功能。
- 灵活配置:支持基于时间、容量的缓存过期机制。
- 高性能:优秀的算法保障高命中率。
在实际项目中,根据业务需求合理配置缓存策略,能显著提升应用性能。
评论区