【问题标题】:Correct use of mutable mongodb entities in reactive (webflux) application在反应式(webflux)应用程序中正确使用可变 mongodb 实体
【发布时间】:2019-05-08 01:03:29
【问题描述】:

当前的 spring-data-mongodb 项目使用可变实体从数据库加载状态,即使在响应式应用程序中也是如此。这被认为是一种不好的做法,即使在 spring-data-mongodb 项目本身中我也可以找到一些问题。例如来自 ReactiveMongoTemplate 的代码 sn-p:

protected <T> Mono<T> doSave(String collectionName, T objectToSave, MongoWriter<Object> writer) {

    assertUpdateableIdIfNotSet(objectToSave);

    return createMono(collectionName, collection -> {

        T toSave = maybeEmitEvent(new BeforeConvertEvent<T>(objectToSave, collectionName)).getSource();

        AdaptibleEntity<T> entity = operations.forEntity(toSave, mongoConverter.getConversionService());
        Document dbDoc = entity.toMappedDocument(writer).getDocument();
        maybeEmitEvent(new BeforeSaveEvent<T>(toSave, dbDoc, collectionName));

        return saveDocument(collectionName, dbDoc, toSave.getClass()).map(id -> {

            T saved = entity.populateIdIfNecessary(id);
            return maybeEmitEvent(new AfterSaveEvent<>(saved, dbDoc, collectionName)).getSource();
        });
    });
}

如您所见,entity.populateIdIfNecessary(id) 正在改变另一个线程上的实体对象,这被认为是多线程应用程序中的一个错误,除非您使用所有设置器都同步的实体。

使用具有响应式 mongodb 接口的可变实体处理并发问题的正确和推荐做法是什么? 考虑这个例子:

reactiveMongoOperations.findById("id2", Customer.class) // calls Customer setters on thread1
  .zipWith(reactiveMongoOperations.findById("id2", Account.class)) // calls Account setters on thread2
  .map(t -> perform(t.getT1(), t.getT2())); // access objects on thread2

为了使这段代码安全,您需要有不可变的客户和帐户类(spring 数据 mongodb 不支持),或者所有的 setter 必须是同步/易失的,这会使您的代码非常具有线程感知能力并且容易出错。

【问题讨论】:

  • 是什么让您认为存在并发问题?你能举一个发生这种问题的例子吗? Reactor 算子已经在处理并发,并提供关于执行顺序和并发的保证。 Spring WebFlux 中的 HTTP 请求和响应也会发生同样的情况,但不会发生此类并发问题。
  • Reactor 不能解决数据对象中的并发内存访问问题。如果您将值设置为变量,则它可能在另一个线程中不可见,除非该字段是最终/易失性或字段访问置于同步块中。这就是在 jvm 中如何实现并发内存访问的基础知识。 Reactor 不会在同步块中放置任何内容,这意味着您的数据对象必须是最终的,或者定义为 volatile 的字段。在我的示例中,您正在压缩两个可能从两个不同线程返回结果的 Mono 对象。
  • 反应式流保证操作符按顺序应用于发出的元素。如果您决定改变该范围之外的元素,则由您自己决定,但您的代码 sn-p 不会显示任何内容。您能否改进问题并简化代码 sn-p 以强调实际问题?
  • reactiveMongoOperations 实例化 Account 和 Customer 对象,然后改变它们(调用 setter)。据我了解,这就是 spring-data-mongo 的工作方式。我担心的是,在 process() 方法事件中,如果我使用只读访问,我可能会遇到并发问题,因为 Account 和 Customer 可能在不同的线程中被实例化(和变异)。因此,即使操作符是按顺序应用的,这些操作也可能在不同的线程上执行,我们的数据对象仍然必须是线程安全的。我在第二个 sn-p 中添加了 cmets。
  • 基本上我关心的是当在两个不同的线程中修改数据时,java内存模型中的重新排序en.wikipedia.org/wiki/Java_memory_model

标签: reactive-programming spring-data-mongodb project-reactor


【解决方案1】:

我刚刚在有关项目反应器和内存模型Project Reactor and the Java memory model 的答案中发现了类似的问题。因此,如果您不使用并行运算符,则似乎使用内存屏障(易失性关键字)来保证内存一致性。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2021-09-26
  • 2018-02-08
  • 1970-01-01
  • 2021-04-12
  • 2020-12-23
  • 2021-02-26
  • 2015-09-29
  • 2021-12-12
相关资源
最近更新 更多