public Index getIndex() {
return calculateIndex();
}
public Index calculateIndex() {
// 1 second or more
}
public boolean isIndexActual(Index index) {
// 20ms or less
}
我正在使用Spring Cache通过@Cacheable
注解来缓存计算出的索引:
@Cacheable(cacheNames = CacheConfiguration.INDEX_CACHE_NAME)
public Index getIndex() {
return calculateIndex();
}
我们目前将
GuavaCache
配置为缓存实现:@Bean
public Cache indexCache() {
return new GuavaCache(INDEX_CACHE_NAME, CacheBuilder.newBuilder()
.expireAfterWrite(indexCacheExpireAfterWriteSeconds, TimeUnit.SECONDS)
.build());
}
@Bean
public CacheManager indexCacheManager(List<Cache> caches) {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(caches);
return cacheManager;
}
我还需要检查缓存的值是否仍然有效,如果无效,则最好异步地刷新它。理想情况下,应该按照以下方式进行:
- 调用
getIndex()
时,Spring会检查缓存中是否有值。- 如果没有,则通过
calculateIndex()
加载新值并将其存储在缓存中 - 如果是,则通过
isIndexActual(...)
检查现有值是否实际。- 如果旧值是实际的,则返回旧值。
- 如果旧值不是实际的,则返回旧值,但从缓存中删除,并触发加载新值。
- 如果没有,则通过
基本上,我想尽快从缓存中提供值(即使它已过时),同时立即触发更新。
目前为止,我已经做到了检查实际性和清除过期值:
@Cacheable(cacheNames = INDEX_CACHE_NAME)
@CacheEvict(cacheNames = INDEX_CACHE_NAME, condition = "target.isObsolete(#result)")
public Index getIndex() {
return calculateIndex();
}
如果结果已经过时,此操作会触发逐出并立即返回旧的值,即使情况确实如此。但是,这不会在缓存中刷新值。
是否有一种方法可以配置Spring Cache,在逐出后积极刷新过时的值?
更新
这是一个MCVE示例。
public static class Index {
private final long timestamp;
public Index(long timestamp) {
this.timestamp = timestamp;
}
public long getTimestamp() {
return timestamp;
}
}
public interface IndexCalculator {
public Index calculateIndex();
public long getCurrentTimestamp();
}
@Service
public static class IndexService {
@Autowired
private IndexCalculator indexCalculator;
@Cacheable(cacheNames = "index")
@CacheEvict(cacheNames = "index", condition = "target.isObsolete(#result)")
public Index getIndex() {
return indexCalculator.calculateIndex();
}
public boolean isObsolete(Index index) {
long indexTimestamp = index.getTimestamp();
long currentTimestamp = indexCalculator.getCurrentTimestamp();
if (index == null || indexTimestamp < currentTimestamp) {
return true;
} else {
return false;
}
}
}
现在开始测试:
@Test
public void test() {
final Index index100 = new Index(100);
final Index index200 = new Index(200);
when(indexCalculator.calculateIndex()).thenReturn(index100);
when(indexCalculator.getCurrentTimestamp()).thenReturn(100L);
assertThat(indexService.getIndex()).isSameAs(index100);
verify(indexCalculator).calculateIndex();
verify(indexCalculator).getCurrentTimestamp();
when(indexCalculator.getCurrentTimestamp()).thenReturn(200L);
when(indexCalculator.calculateIndex()).thenReturn(index200);
assertThat(indexService.getIndex()).isSameAs(index100);
verify(indexCalculator, times(2)).getCurrentTimestamp();
// I'd like to see indexCalculator.calculateIndex() called after
// indexService.getIndex() returns the old value but it does not happen
// verify(indexCalculator, times(2)).calculateIndex();
assertThat(indexService.getIndex()).isSameAs(index200);
// Instead, indexCalculator.calculateIndex() os called on
// the next call to indexService.getIndex()
// I'd like to have it earlier
verify(indexCalculator, times(2)).calculateIndex();
verify(indexCalculator, times(3)).getCurrentTimestamp();
verifyNoMoreInteractions(indexCalculator);
}
我希望在缓存中的值被驱逐后不久就能得到更新。目前情况是在下一次调用 getIndex()
之前刷新。如果驱逐后立即刷新,这将节省我1秒钟。
我尝试过使用 @CachePut
,但它也不能带来我想要的效果。值会被刷新,但方法总是会执行,无论 condition
或 unless
是什么。
目前我唯一看到的方法是调用 getIndex()
两次(第二次是异步/非阻塞)。但那有点傻。
getIndex()
上添加一个额外的注解@CachePut(cacheNames = INDEX_CACHE_NAME, condition = "target.isObsolete(#result)")
,这应该能为您解决问题。 - Bond - Java Bond@CachePut
无论如何都会执行该方法,只是不会缓存过时的结果。我想在且仅在结果过时时执行该方法。 - lexicore