【问题标题】:Spring Webflux and @Cacheable - proper way of caching result of Mono / Flux typeSpring Webflux 和 @Cacheable - Mono / Flux 类型缓存结果的正确方法
【发布时间】:2018-06-17 19:02:50
【问题描述】:

我正在学习 Spring WebFlux,在编写示例应用程序期间,我发现了与响应式类型(Mono/Flux)结合 Spring Cache 相关的问题。

考虑以下代码-sn-p(在 Kotlin 中):

@Repository
interface TaskRepository : ReactiveMongoRepository<Task, String>

@Service
class TaskService(val taskRepository: TaskRepository) {

    @Cacheable("tasks")
    fun get(id: String): Mono<Task> = taskRepository.findById(id)
}

这种缓存返回 Mono 或 Flux 的方法调用是否有效且安全?也许还有其他一些原则可以做到这一点?

以下代码正在使用 SimpleCacheResolver,但默认情况下,由于 Mono 不可序列化,Redis 会失败。为了使它们工作,例如需要使用 Kryo 序列化器。

【问题讨论】:

    标签: spring-boot spring-cache spring-webflux


    【解决方案1】:

    // 在门面中:

    public Mono<HybrisResponse> getProducts(HybrisRequest request) {
        return Mono.just(HybrisResponse.builder().build());
    }
    

    // 在服务层中:

    @Cacheable(cacheNames = "embarkations")
    public HybrisResponse cacheable(HybrisRequest request) {
        LOGGER.info("executing cacheable");
        return null;
    }
    
    @CachePut(cacheNames = "embarkations")
    public HybrisResponse cachePut(HybrisRequest request) {
        LOGGER.info("executing cachePut");
        return hybrisFacade.getProducts(request).block();
    }
    

    // 在控制器中:

    HybrisResponse hybrisResponse = null;
    
    try {
       // get from cache
       hybrisResponse = productFeederService.cacheable(request);
    
    } catch (Throwable e) {
       // if not in cache then cache it
       hybrisResponse = productFeederService.cachePut(request);
    }
    
    return Mono.just(hybrisResponse)
        .map(result -> ResponseBody.<HybrisResponse>builder()
            .payload(result).build())
        .map(ResponseEntity::ok);
    

    【讨论】:

    • Hybris 现在可以响应了吗?
    【解决方案2】:

    我使用了 Oleh Dokuka 的 hacky 解决方案,效果很好,但有一个问题。您必须在 Flux 缓存中使用比 Cachable 缓存 timetolive 值更大的 Duration。如果您不使用 Flux 缓存的持续时间,它不会使其无效(Flux 文档说“将此 Flux 转换为热源并缓存最后发出的信号以供进一步的订阅者使用。”)。 因此,将 Flux 缓存设置为 2 分钟,将生存时间设置为 30 秒可以是有效的配置。如果 ehcahce 超时首先发生,则会生成一个新的 Flux 缓存引用并将其使用。

    【讨论】:

    • 你是说如果我使用@Cacheable.cache() 会泄漏内存吗?我是否需要显式调用 .cache(ttl) 且 ttl ≥缓存配置?
    【解决方案3】:

    破解方式

    目前,@Cacheable 与 Reactor 3 没有流畅的集成。 但是,您可以通过将 .cache() 运算符添加到返回的 Mono 来绕过该操作

    @Repository
    interface TaskRepository : ReactiveMongoRepository<Task, String>
    
    @Service
    class TaskService(val taskRepository: TaskRepository) {
    
        @Cacheable("tasks")
        fun get(id: String): Mono<Task> = taskRepository.findById(id).cache()
    }
    

    hack 缓存和共享从 taskRepository 数据返回。反过来,spring cacheable 将缓存返回的Mono 的引用,然后返回该引用。换句话说,它是一个保存缓存的单声道缓存:)。

    Reactor 插件方式

    Reactor 3 有一个 addition,它允许与现代内存缓存(如 caffeinejcache 等)流畅集成。使用该技术,您将能够轻松缓存数据:

    @Repository
    interface TaskRepository : ReactiveMongoRepository<Task, String>
    
    @Service
    class TaskService(val taskRepository: TaskRepository) {
    
        @Autowire
        CacheManager manager;
    
    
        fun get(id: String): Mono<Task> = CacheMono.lookup(reader(), id)
                                                   .onCacheMissResume(() -> taskRepository.findById(id))
                                                   .andWriteWith(writer());
    
        fun reader(): CacheMono.MonoCacheReader<String, Task> = key -> Mono.<Signal<Task>>justOrEmpty((Signal) manager.getCache("tasks").get(key).get())
        fun writer(): CacheMono.MonoCacheWriter<String, Task> = (key, value) -> Mono.fromRunnable(() -> manager.getCache("tasks").put(key, value));
    } 
    

    注意:Reactor 插件会缓存自己的抽象,即 Signal&lt;T&gt;,因此,不必担心并遵循该约定

    【讨论】:

    • 感谢您提供宝贵的提示,但问题仍然存在:序列化和缓存 Mono 对象本身是否存在风险或被视为不好的做法?我想将 @Cacheable 与 Redis 结合使用,将缓存移到应用程序内存之外。
    • 不幸的是,更好的方法是手动与 Redis 集成,而不是第二种方法与组合,在你的情况下,使用 Spring Data Redis
    • 上面的“reactor addons way”需要在将来的某个时候集成到@Cacheable,以缓存Mono持有的结果。缓存Mono 实例本身没有意义,只不过是尝试缓存普通的RunnableFuture
    • @SoulCub 一次调用者之间没有额外的同步,因此可能会出现两次对 DB 的调用。因此,您必须添加额外的呼叫多路复用以避免竞争。我将在答案中添加示例
    • 您知道@Cacheable.cache() 解决方案是否会泄漏内存吗?如果我理解正确,下面的@Ilker 建议将.cache(ttl) 与 ttl ≥ 缓存配置一起使用。你知道这是否需要吗?
    猜你喜欢
    • 2019-05-18
    • 2018-12-12
    • 2021-09-30
    • 2019-01-20
    • 2020-03-20
    • 1970-01-01
    • 2019-03-26
    • 2020-11-14
    • 1970-01-01
    相关资源
    最近更新 更多