Java Spring缓存刷新过时值
在基于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) {
索引的计算<代码>索引
计算起来相对昂贵(比如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()
方法没有参数,因此它存储在keySimpleKey.EMPTY的缓存中
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();
}