在基于 Spring 的应用程序中,我有一个服务来执行一些 Index
的计算。 Index
计算起来相对昂贵(比如 1 秒),但检查实际情况相对便宜(比如 20 毫秒)。实际代码无关紧要,它遵循以下几行:
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()
两次(第二次异步/非阻塞)。但这有点愚蠢。
最佳答案
我想说,做你需要的最简单的方法是创建一个自定义的 Aspect,它可以透明地完成所有的魔法,并且可以在更多地方重复使用。
所以假设你的类路径上有 spring-aop
和 aspectj
依赖项,下面的方面就可以解决问题。
@Aspect
@Component
public class IndexEvictorAspect {
@Autowired
private Cache cache;
@Autowired
private IndexService indexService;
private final ReentrantLock lock = new ReentrantLock();
@AfterReturning(pointcut="hello.IndexService.getIndex()", returning="index")
public void afterGetIndex(Object index) {
if(indexService.isObsolete((Index) index) && lock.tryLock()){
try {
Index newIndex = indexService.calculateIndex();
cache.put(SimpleKey.EMPTY, newIndex);
} finally {
lock.unlock();
}
}
}
}
注意事项
- 由于您的
getIndex()
方法没有参数,因此它存储在SimpleKey.EMPTY
键的缓存中
- 代码假定 IndexService 在
hello
包中。
关于java - Spring Cache 刷新过时的值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42342810/