1. 概述
本文将介绍 Infinispan——一个功能强大的内存键值存储库。相比同类工具,它提供了更丰富的特性集。我们将通过构建一个实际项目来演示其核心功能,帮助大家理解其工作原理。
2. 项目配置
首先在 pom.xml 中添加依赖(最新版本可在 Maven Central 查询):
<dependency>
<groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId>
<version>9.1.5.Final</version>
</dependency>
后续所有底层基础设施都将通过编程方式处理。
3. CacheManager 配置
CacheManager 是 Infinispan 的核心组件,负责:
- 托管所有声明的缓存
- 控制缓存生命周期
- 管理全局配置
创建方式非常简单:
public DefaultCacheManager cacheManager() {
return new DefaultCacheManager();
}
现在可以用它来构建缓存了。
4. 缓存配置
缓存由名称和配置定义,使用 ConfigurationBuilder 构建配置。先准备一个模拟耗时查询的测试类:
public class HelloWorldRepository {
public String getHelloWorld() {
try {
System.out.println("执行耗时查询");
Thread.sleep(1000);
} catch (InterruptedException e) {
// ...
e.printStackTrace();
}
return "Hello World!";
}
}
Infinispan 通过 @Listener 注解提供事件监听能力。定义监听器后,缓存事件会自动触发通知:
@Listener
public class CacheListener {
@CacheEntryCreated
public void entryCreated(CacheEntryCreatedEvent<String, String> event) {
this.printLog("添加键 '" + event.getKey() + "' 到缓存", event);
}
@CacheEntryExpired
public void entryExpired(CacheEntryExpiredEvent<String, String> event) {
this.printLog("键 '" + event.getKey() + "' 已过期", event);
}
@CacheEntryVisited
public void entryVisited(CacheEntryVisitedEvent<String, String> event) {
this.printLog("键 '" + event.getKey() + "' 被访问", event);
}
@CacheEntryActivated
public void entryActivated(CacheEntryActivatedEvent<String, String> event) {
this.printLog("激活键 '" + event.getKey() + "'", event);
}
@CacheEntryPassivated
public void entryPassivated(CacheEntryPassivatedEvent<String, String> event) {
this.printLog钝化键 '" + event.getKey() + "'", event);
}
@CacheEntryLoaded
public void entryLoaded(CacheEntryLoadedEvent<String, String> event) {
this.printLog("加载键 '" + event.getKey() + "'", event);
}
@CacheEntriesEvicted
public void entriesEvicted(CacheEntriesEvictedEvent<String, String> event) {
StringBuilder builder = new StringBuilder();
event.getEntries().forEach(
(key, value) -> builder.append(key).append(", "));
System.out.println("从缓存驱逐以下条目: " + builder.toString());
}
private void printLog(String log, CacheEntryEvent event) {
if (!event.isPre()) {
System.out.println(log);
}
}
}
⚠️ 注意:打印日志前需检查事件是否已发生,因为某些事件类型会发送两次通知(处理前和处理后)。
缓存创建方法:
private <K, V> Cache<K, V> buildCache(
String cacheName,
DefaultCacheManager cacheManager,
CacheListener listener,
Configuration configuration) {
cacheManager.defineConfiguration(cacheName, configuration);
Cache<K, V> cache = cacheManager.getCache(cacheName);
cache.addListener(listener);
return cache;
}
接下来演示五种典型缓存配置:
4.1. 基础缓存
最简单的缓存配置只需一行代码:
public Cache<String, String> simpleHelloWorldCache(
DefaultCacheManager cacheManager,
CacheListener listener) {
return this.buildCache(SIMPLE_HELLO_WORLD_CACHE,
cacheManager, listener, new ConfigurationBuilder().build());
}
使用方式:
public String findSimpleHelloWorld() {
String cacheKey = "simple-hello";
return simpleHelloWorldCache
.computeIfAbsent(cacheKey, k -> repository.getHelloWorld());
}
测试方法(统计执行时间):
protected <T> long timeThis(Supplier<T> supplier) {
long millis = System.currentTimeMillis();
supplier.get();
return System.currentTimeMillis() - millis;
}
验证缓存效果:
@Test
public void whenGetIsCalledTwoTimes_thenTheSecondShouldHitTheCache() {
assertThat(timeThis(() -> helloWorldService.findSimpleHelloWorld()))
.isGreaterThanOrEqualTo(1000);
assertThat(timeThis(() -> helloWorldService.findSimpleHelloWorld()))
.isLessThan(100);
}
4.2. 过期缓存
为所有条目设置生命周期:
private Configuration expiringConfiguration() {
return new ConfigurationBuilder().expiration()
.lifespan(1, TimeUnit.SECONDS)
.build();
}
缓存创建:
public Cache<String, String> expiringHelloWorldCache(
DefaultCacheManager cacheManager,
CacheListener listener) {
return this.buildCache(EXPIRING_HELLO_WORLD_CACHE,
cacheManager, listener, expiringConfiguration());
}
使用示例:
public String findSimpleHelloWorldInExpiringCache() {
String cacheKey = "simple-hello";
String helloWorld = expiringHelloWorldCache.get(cacheKey);
if (helloWorld == null) {
helloWorld = repository.getHelloWorld();
expiringHelloWorldCache.put(cacheKey, helloWorld);
}
return helloWorld;
}
快速调用测试:
@Test
public void whenGetIsCalledTwoTimesQuickly_thenTheSecondShouldHitTheCache() {
assertThat(timeThis(() -> helloWorldService.findExpiringHelloWorld()))
.isGreaterThanOrEqualTo(1000);
assertThat(timeThis(() -> helloWorldService.findExpiringHelloWorld()))
.isLessThan(100);
}
间隔调用测试(验证过期):
@Test
public void whenGetIsCalledTwiceSparsely_thenNeitherHitsTheCache()
throws InterruptedException {
assertThat(timeThis(() -> helloWorldService.findExpiringHelloWorld()))
.isGreaterThanOrEqualTo(1000);
Thread.sleep(1100);
assertThat(timeThis(() -> helloWorldService.findExpiringHelloWorld()))
.isGreaterThanOrEqualTo(1000);
}
监听器输出示例:
执行耗时查询
添加键 'simple-hello' 到缓存
键 'simple-hello' 已过期
执行耗时查询
添加键 'simple-hello' 到缓存
✅ 过期触发时机:访问条目或清理线程扫描时。
灵活过期控制:
// 单独设置过期时间
simpleHelloWorldCache.put(cacheKey, helloWorld, 10, TimeUnit.SECONDS);
// 设置最大空闲时间
simpleHelloWorldCache.put(cacheKey, helloWorld, -1, TimeUnit.SECONDS, 10, TimeUnit.SECONDS);
4.3. 缓存驱逐
限制缓存条目数量:
private Configuration evictingConfiguration() {
return new ConfigurationBuilder()
.memory().evictionType(EvictionType.COUNT).size(1)
.build();
}
使用方式:
public String findEvictingHelloWorld(String key) {
String value = evictingHelloWorldCache.get(key);
if(value == null) {
value = repository.getHelloWorld();
evictingHelloWorldCache.put(key, value);
}
return value;
}
测试驱逐效果:
@Test
public void whenTwoAreAdded_thenFirstShouldntBeAvailable() {
assertThat(timeThis(
() -> helloWorldService.findEvictingHelloWorld("key 1")))
.isGreaterThanOrEqualTo(1000);
assertThat(timeThis(
() -> helloWorldService.findEvictingHelloWorld("key 2")))
.isGreaterThanOrEqualTo(1000);
assertThat(timeThis(
() -> helloWorldService.findEvictingHelloWorld("key 1")))
.isGreaterThanOrEqualTo(1000);
}
监听器输出:
执行耗时查询
添加键 'key 1' 到缓存
执行耗时查询
从缓存驱逐以下条目: key 1,
添加键 'key 2' 到缓存
执行耗时查询
从缓存驱逐以下条目: key 2,
添加键 'key 1' 到缓存
4.4. 钝化缓存
结合驱逐和钝化(Passivation)实现内存优化:
private Configuration passivatingConfiguration() {
return new ConfigurationBuilder()
.memory().evictionType(EvictionType.COUNT).size(1)
.persistence()
.passivation(true) // 启用钝化
.addSingleFileStore() // 存储到文件
.purgeOnStartup(true) // 启动时清理
.location(System.getProperty("java.io.tmpdir"))
.build();
}
使用方式:
public String findPassivatingHelloWorld(String key) {
return passivatingHelloWorldCache.computeIfAbsent(key, k ->
repository.getHelloWorld());
}
测试钝化效果:
@Test
public void whenTwoAreAdded_thenTheFirstShouldBeAvailable() {
assertThat(timeThis(
() -> helloWorldService.findPassivatingHelloWorld("key 1")))
.isGreaterThanOrEqualTo(1000);
assertThat(timeThis(
() -> helloWorldService.findPassivatingHelloWorld("key 2")))
.isGreaterThanOrEqualTo(1000);
assertThat(timeThis(
() -> helloWorldService.findPassivatingHelloWorld("key 1")))
.isLessThan(100);
}
监听器输出:
执行耗时查询
添加键 'key 1' 到缓存
执行耗时查询
钝化键 'key 1'
从缓存驱逐以下条目: key 1,
添加键 'key 2' 到缓存
钝化键 'key 2'
从缓存驱逐以下条目: key 2,
加载键 'key 1'
激活键 'key 1'
键 'key 1' 被访问
关键流程说明:
- 钝化:条目被移出内存存储到外部
- 驱逐:从内存移除以释放空间
- 加载:从外部存储重新加载到内存
- 激活:条目在内存中重新可用
4.5. 事务缓存
实现类似数据库的事务控制:
private Configuration transactionalConfiguration() {
return new ConfigurationBuilder()
.transaction().transactionMode(TransactionMode.TRANSACTIONAL)
.lockingMode(LockingMode.PESSIMISTIC)
.build();
}
测试方法(快速提交 vs 慢速回滚):
public Integer getQuickHowManyVisits() {
TransactionManager tm = transactionalCache
.getAdvancedCache().getTransactionManager();
tm.begin();
Integer howManyVisits = transactionalCache.get(KEY);
howManyVisits++;
System.out.println("尝试设置 HowManyVisits = " + howManyVisits);
StopWatch watch = new StopWatch();
watch.start();
transactionalCache.put(KEY, howManyVisits);
watch.stop();
System.out.println("成功设置 HowManyVisits = " + howManyVisits +
" 耗时 " + watch.getTotalTimeSeconds() + " 秒");
tm.commit();
return howManyVisits;
}
public void startBackgroundBatch() {
TransactionManager tm = transactionalCache
.getAdvancedCache().getTransactionManager();
tm.begin();
transactionalCache.put(KEY, 1000);
System.out.println("HowManyVisits 应为 1000,但事务未提交");
Thread.sleep(1000L);
tm.rollback();
System.out.println("慢速事务已回滚");
}
并发测试:
@Test
public void whenLockingAnEntry_thenItShouldBeInaccessible() throws InterruptedException {
Runnable backGroundJob = () -> transactionalService.startBackgroundBatch();
Thread backgroundThread = new Thread(backGroundJob);
transactionalService.getQuickHowManyVisits();
backgroundThread.start();
Thread.sleep(100); // 等待后台线程启动
assertThat(timeThis(() -> transactionalService.getQuickHowManyVisits()))
.isGreaterThan(500).isLessThan(1000);
}
控制台输出:
添加键 'key' 到缓存
键 'key' 被访问
尝试设置 HowManyVisits = 1
成功设置 HowManyVisits = 1 耗时 0.001 秒
HowManyVisits 应为 1000,但事务未提交
键 'key' 被访问
尝试设置 HowManyVisits = 2
成功设置 HowManyVisits = 2 耗时 0.902 秒
慢速事务已回滚
❌ 注意:主线程因慢速事务持有锁而等待近1秒。
5. 总结
本文介绍了 Infinispan 的核心特性及实际应用场景,包括:
- 基础缓存操作
- 条目过期控制
- 内存驱逐策略
- 钝化机制
- 事务管理
完整代码示例可在 GitHub 获取。