Spring Webflux和@Cacheable - Mono/Flux类型的缓存结果的正确方法

Tom*_*mba 10 spring-boot spring-cache spring-webflux

我正在学习Spring WebFlux,在编写示例应用程序时,我发现了与Reactive类型(Mono/Flux)结合Spring Cache的问题.

考虑以下代码片段(在Kotlin中):

@Repository
interface TaskRepository : ReactiveMongoRepository<Task, String>

@Service
class TaskService(val taskRepository: TaskRepository) {

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

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

以下代码使用SimpleCacheResolver,但默认情况下使用Redis失败,因为Mono不是Serializable.为了使它们工作,例如需要使用Kryo序列化器.

Ole*_*uka 22

哈克方式

目前,没有@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()
}
Run Code Online (Sandbox Code Playgroud)

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

Reactor Addons Way

有一个除了对反应器3,允许与现代的内存缓存一样流利整合咖啡因,JCACHE等.使用这种技术,你将能够轻松地缓存数据:

@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));
} 
Run Code Online (Sandbox Code Playgroud)

注意:Reactor插件缓存自己的抽象Signal<T>,因此,不要担心并遵循该约定

  • 上面的"reactor addons方式"需要在将来的某个时候集成到`@ Cacheable`来缓存`Mono`所持有的结果.缓存一个`Mono`实例本身没有意义,只不过试图缓存一个简单的`Runnable`或`Future` (3认同)
  • @SoulCub一次在调用者之间没有其他同步,因此可能发生两个对DB的调用。因此,您必须添加其他呼叫多路复用以避免竞争。我将样本添加到答案中 (2认同)