10

我需要缓存一些异步计算的结果。具体来说,为了克服这个问题,我正在尝试使用 Spring 4.3 缓存和异步计算功能。

作为一个例子,让我们看下面的代码:

@Service
class AsyncService {
    @Async
    @Cacheable("users")
    CompletableFuture<User> findById(String usedId) {
        // Some code that retrieves the user relative to id userId
        return CompletableFuture.completedFuture(user);
    }
}

可能吗?我的意思是,Spring 的缓存抽象会正确处理 type 的对象CompletableFuture<User>吗?我知道Caffeine Cache有类似的东西,但如果配置正确,我不明白 Spring 是否使用它。

编辑:我对User对象本身不感兴趣,但对CompletableFuture代表计算的对象不感兴趣。

4

5 回答 5

12

社区要求我做一些实验,所以我做了它们。我发现我的问题的答案很简单:如果将它们放在同一方法之上@Cacheable@Async则不要一起工作。

需要明确的是,我并不是在寻求一种直接使缓存返回 a 拥有的对象的方法CompletableFuture。这是不可能的,如果不是这样,它将打破类异步计算的契约CompletableFuture

正如我所说,这两个注释不能在同一个方法上一起工作。如果你仔细想想,这很明显。标记@Async@Cacheable意味着将整个缓存管理委托给不同的异步线程。如果值的计算CompletableFuture需要很长时间才能完成,那么缓存中的值将在此时间之后由 Spring Proxy 放置。

显然,有一种解决方法。变通方法使用事实 the CompletableFutureis a promise。让我们看一下下面的代码。

@Component
public class CachedService {
    /* Dependecies resolution code */
    private final AsyncService service;

    @Cacheable(cacheNames = "ints")
    public CompletableFuture<Integer> randomIntUsingSpringAsync() throws InterruptedException {
        final CompletableFuture<Integer> promise = new CompletableFuture<>();
        // Letting an asynchronous method to complete the promise in the future
        service.performTask(promise);
        // Returning the promise immediately
        return promise;
    }
}

@Component
public class AsyncService {
    @Async
    void performTask(CompletableFuture<Integer> promise) throws InterruptedException {
        Thread.sleep(2000);
        // Completing the promise asynchronously
        promise.complete(random.nextInt(1000));
    }
}

诀窍是创建一个不完整的承诺并立即从标有@Cacheable注释的方法中返回它。Promise 将由另一个拥有该@Async注解标记的方法的 bean 异步完成。

作为奖励,我还实现了一个不使用 Spring@Async注解的解决方案,而是直接使用类中可用的工厂方法CompletableFuture

@Cacheable(cacheNames = "ints1")
public CompletableFuture<Integer> randomIntNativelyAsync() throws
        InterruptedException {
    return CompletableFuture.supplyAsync(this::getAsyncInteger, executor);
}

private Integer getAsyncInteger() {
    logger.info("Entering performTask");
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return random.nextInt(1000);
}

无论如何,我分享了我的 GitHub 问题spring-cacheable-async的完整解决方案。

最后,以上是对 Jira SPR-12967所指内容的详细说明。

我希望它有所帮助。干杯。

于 2017-11-09T06:49:18.033 回答
4

根据SPR-12967,不支持ListenableFuture( )。CompletableFuture

于 2017-11-07T15:17:27.243 回答
2

在一个类的方法上添加@Async注释,在另一个类@Cacheable的方法级别添加注释。

@Async然后从服务或任何不同的层调用方法。

它对我有用,无论是 Redis 缓存还是异步,都极大地提高了性能。

于 2018-09-05T12:19:54.183 回答
0

理论上,只要

  • CacheManager 背后的实现@Cacheable不是序列化缓存的对象(如 Hazelcast 支持的缓存)

  • 由于CompletableFuture持有一个状态,可以通过调用例如cancel()方法来修改它,重要的是 API 的所有用户都不会乱用缓存的对象。否则,可能存在Future无法再检索到其中的缓存对象的风险,因此需要进行缓存逐出

  • 值得验证注释后面的代理被调用的顺序。即@Cacheable代理总是在代理之前调用@Async吗?还是反过来?还是视情况而定?例如,如果@Async之前调用了 a ,它将在 a 中触发 a CallableForkJoinPool然后从缓存中检索另一个对象。

于 2017-11-08T21:45:59.963 回答
0

我尝试了以下方法,它似乎有效。

  • 创建一个@Cachable执行实际业务逻辑的方法
  • 创建一个方法@Async来调用上述@Cachable方法并返回一个CompletableFuture
  • @Async在主执行流程中调用方法

例子:

public class Main {
    public void cachedAsyncData() {
        try {
            asyncFetcher.getData().get();
        } catch(Exception e){}
    }
}

public class AsyncFetcher {
    @Async
    public CompletableFuture<String> getData() {
        return CompletableFuture.completedFuture(cacheFetcher.getData());
    }
}

public class CacheFetcher {
    @Cacheable
    public String getData() {
        return "DATA";
    }
}
于 2021-03-09T11:57:43.300 回答