Java Spring缓存刷新过时值

Java Spring缓存刷新过时值,java,spring,caching,Java,Spring,Caching,在基于Spring的应用程序中,我有一个服务,它执行一些索引的计算索引计算起来相对昂贵(比如1s),但检查实际情况相对便宜(比如20ms)。实际代码并不重要,它遵循以下几行: public Index getIndex() { return calculateIndex(); } public Index calculateIndex() { // 1 second or more } public boolean isIndexActual(Index index) {

在基于Spring的应用程序中,我有一个服务,它执行一些
索引的计算<代码>索引
计算起来相对昂贵(比如1s),但检查实际情况相对便宜(比如20ms)。实际代码并不重要,它遵循以下几行:

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;
}
如果结果已过时,则检查触发逐出,并立即返回旧值,即使是这样。但这不会刷新缓存中的值

有没有一种方法可以配置Spring缓存,以便在逐出后主动刷新过时的值

更新

这是一个例子

我希望该值在从缓存中移出后不久刷新。此时,它将在下次调用
getIndex()
时首先刷新。如果该值在逐出后立即刷新,那么这将在以后为我节省1秒

我尝试了
@CachePut
,但也没有达到预期效果。刷新该值,但始终会执行该方法,无论是
条件
还是
,除非
被刷新

目前我看到的唯一方法是调用
getIndex()
两次(第二次异步/非阻塞)。但这有点愚蠢。

EDIT1:

在这种情况下,基于
@Cacheable
@cacheexecute
的缓存抽象将不起作用。这些行为如下:在
@Cacheable
调用期间,如果值在缓存中-从缓存返回值,否则计算并放入缓存,然后返回;在
@cacheexecute
期间,该值将从缓存中删除,因此从此时起,缓存中没有任何值,因此对
@Cacheable
的第一个传入调用将强制重新计算并放入缓存。使用
@cacheexecute(condition=“”)
只会在基于此条件的调用期间检查是否要从缓存中删除值。因此,每次失效后,
@Cacheable
方法将运行此重量级例程来填充缓存

为了将值存储在缓存管理器中并进行异步更新,我建议重用以下例程:

@Inject
@Qualifier("my-configured-caching")
private Cache cache; 
private ReentrantLock lock = new ReentrantLock();

public Index getIndex() {
    synchronized (this) {
        Index storedCache = cache.get("singleKey_Or_AnythingYouWant", Index.class); 
        if (storedCache == null ) {
             this.lock.lock();
             storedCache = indexCalculator.calculateIndex();
             this.cache.put("singleKey_Or_AnythingYouWant",  storedCache);
             this.lock.unlock();
         }
    }
    if (isObsolete(storedCache)) {
         if (!lock.isLocked()) {
              lock.lock();
              this.asyncUpgrade()
         }
    }
    return storedCache;
}
第一个构造是同步化的,只是为了阻止所有即将到来的调用,以等待第一个调用填充缓存

然后系统检查是否应重新生成缓存。如果是,则调用异步更新值的单个调用,并且当前线程返回缓存的值。一旦缓存处于重新计算状态,接下来的调用将只返回缓存中最近的值。等等

有了这样的解决方案,您将能够重用大量内存(比如hazelcast cache manager),以及基于多个键的缓存存储,并保持缓存实现和逐出的复杂逻辑

或者,如果您喜欢
@Cacheable
注释,可以通过以下方式执行此操作:

@Cacheable(cacheNames = "index", sync = true)
public Index getCachedIndex() {
    return new Index();
}

@CachePut(cacheNames = "index")
public Index putIntoCache() {
    return new Index();
}

public Index getIndex() {
    Index latestIndex = getCachedIndex();

    if (isObsolete(latestIndex)) {
        recalculateCache();
    }

    return latestIndex;
}

private ReentrantLock lock = new ReentrantLock();

@Async
public void recalculateCache() {
    if (!lock.isLocked()) {
        lock.lock();
        putIntoCache();
        lock.unlock();
    }
}
这与上面的几乎相同,但重用了spring的缓存注释抽象

原件: 为什么要尝试通过缓存解决此问题?如果这是简单值(不基于键,您可以以更简单的方式组织代码,请记住spring服务在默认情况下是单例的)

诸如此类:

@Service
public static class IndexService {
    @Autowired
    private IndexCalculator indexCalculator;

    private Index storedCache; 
    private ReentrantLock lock = new ReentrantLock();

    public Index getIndex() {
        if (storedCache == null ) {
             synchronized (this) {
                 this.lock.lock();
                 Index result = indexCalculator.calculateIndex();
                 this.storedCache = result;
                 this.lock.unlock();
             }
        }
        if (isObsolete()) {
             if (!lock.isLocked()) {
                  lock.lock();
                  this.asyncUpgrade()
             }
        }
        return storedCache;
    }

    @Async
    public void asyncUpgrade() {
        Index result = indexCalculator.calculateIndex();
        synchronized (this) {
             this.storedCache = result;
        }
        this.lock.unlock();
    }

    public boolean isObsolete() {
        long currentTimestamp = indexCalculator.getCurrentTimestamp();
        if (storedCache == null || storedCache.getTimestamp() < currentTimestamp) {
            return true;
        } else {
            return false;
        }
    }
}
@服务
公共静态类索引服务{
@自动连线
私有索引计算程序索引计算程序;
私有索引存储缓存;
private ReentrantLock lock=new ReentrantLock();
公共索引getIndex(){
if(storedCache==null){
已同步(此){
this.lock.lock();
索引结果=indexCalculator.calculateIndex();
this.storedCache=结果;
这个.lock.unlock();
}
}
if(isObsolete()){
如果(!lock.isLocked()){
lock.lock();
此.asyncUpgrade()
}
}
返回存储缓存;
}
@异步的
public-void异步升级(){
索引结果=indexCalculator.calculateIndex();
已同步(此){
this.storedCache=结果;
}
这个.lock.unlock();
}
公共布尔同素体(){
长currentTimestamp=indexCalculator.getCurrentTimestamp();
if(storedCache==null | | storedCache.getTimestamp()
i、 e.第一次调用是同步的,您必须等待结果被填充。然后,如果存储的值已过时,系统将对该值执行异步更新,但当前线程将接收存储的“缓存”值


我还引入了可重入锁来限制存储索引的一次性升级

我想说,做你需要的事情最简单的方法是创建一个自定义方面,它可以透明地完成所有的工作,并且可以在更多的地方重用

因此,假设您的类路径上有
springaop
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()
    方法没有参数,因此它存储在key
    SimpleKey.EMPTY的缓存中
  • 代码假定IndexService在
    hello
    包中

  • 我会在索引服务中使用Guava LoadingCache,如下面的代码示例所示:

    LoadingCache-graphs=CacheBuilder.newBuilder()
    .最大尺寸(1000)
    .refreshAfterWrite(1,时间单位:分钟)
    .建造(
    新缓存加载程序(){
    公共图加载(键){//无已检查异常
    返回GetGraphFromDatabase
    
    @Cacheable(cacheNames = "index", sync = true)
    public Index getCachedIndex() {
        return new Index();
    }
    
    @CachePut(cacheNames = "index")
    public Index putIntoCache() {
        return new Index();
    }
    
    public Index getIndex() {
        Index latestIndex = getCachedIndex();
    
        if (isObsolete(latestIndex)) {
            recalculateCache();
        }
    
        return latestIndex;
    }
    
    private ReentrantLock lock = new ReentrantLock();
    
    @Async
    public void recalculateCache() {
        if (!lock.isLocked()) {
            lock.lock();
            putIntoCache();
            lock.unlock();
        }
    }
    
    @Service
    public static class IndexService {
        @Autowired
        private IndexCalculator indexCalculator;
    
        private Index storedCache; 
        private ReentrantLock lock = new ReentrantLock();
    
        public Index getIndex() {
            if (storedCache == null ) {
                 synchronized (this) {
                     this.lock.lock();
                     Index result = indexCalculator.calculateIndex();
                     this.storedCache = result;
                     this.lock.unlock();
                 }
            }
            if (isObsolete()) {
                 if (!lock.isLocked()) {
                      lock.lock();
                      this.asyncUpgrade()
                 }
            }
            return storedCache;
        }
    
        @Async
        public void asyncUpgrade() {
            Index result = indexCalculator.calculateIndex();
            synchronized (this) {
                 this.storedCache = result;
            }
            this.lock.unlock();
        }
    
        public boolean isObsolete() {
            long currentTimestamp = indexCalculator.getCurrentTimestamp();
            if (storedCache == null || storedCache.getTimestamp() < currentTimestamp) {
                return true;
            } else {
                return false;
            }
        }
    }
    
    @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();
                }
            }
        }
    }
    
    @Service
    public static class IndexService {
        @Autowired
        private IndexCalculator indexCalculator;
    
        public Index getIndex() {
            Index cachedIndex = getCachedIndex();
    
            if (isObsolete(cachedIndex)) {
                evictCache();
                asyncRefreshCache();
            }
    
            return cachedIndex;
        }
    
        @Cacheable(cacheNames = "index")
        public Index getCachedIndex() {
            return indexCalculator.calculateIndex();
        }
    
        public void asyncRefreshCache() {
            CompletableFuture.runAsync(this::getCachedIndex);
        }
    
        @CacheEvict(cacheNames = "index")
        public void evictCache() { }
    
        public boolean isObsolete(Index index) {
            long indexTimestamp = index.getTimestamp();
            long currentTimestamp = indexCalculator.getCurrentTimestamp();
    
            if (index == null || indexTimestamp < currentTimestamp) {
                return true;
            } else {
                return false;
            }
        }
    }
    
    @Autowired
    IndexService indexService; // self injection
    
    @Cacheable(cacheNames = INDEX_CACHE_NAME)
    @CacheEvict(cacheNames = INDEX_CACHE_NAME, condition = "target.isObsolete(#result) && @indexService.calculateIndexAsync()")
    public Index getIndex() {
        return calculateIndex();
    }
    
    public boolean calculateIndexAsync() {
        someAsyncService.run(new Runable() {
            public void run() {
                indexService.updateIndex(); // require self reference to use Spring caching proxy
            }
        });
        return true;
    }
    
    @CachePut(cacheNames = INDEX_CACHE_NAME)
    public Index updateIndex() {
        return calculateIndex();
    }
    
    @Autowired
    IndexService indexService; // self injection
    
    @Cacheable(cacheNames = INDEX_CACHE_NAME, condition = "!(target.isObsolete(#result) && @indexService.calculateIndexAsync())")
    public Index getIndex() {
        return calculateIndex();
    }
    
    public boolean calculateIndexAsync() {
        if (!someThreadSafeService.isIndexBeingUpdated()) {
            someAsyncService.run(new Runable() {
                public void run() {
                    indexService.updateIndex(); // require self reference to use Spring caching proxy
                }
            });
        }
        return false;
    }
    
    @CachePut(cacheNames = INDEX_CACHE_NAME)
    public Index updateIndex() {
        return calculateIndex();
    }