WebFlux栈中MyBatis如何处理
How to deal with MyBatis in WebFlux stack
我有一个庞大的企业 java 应用程序,其中包含“你老了”的技术堆栈:Spring Web MVC、PostgreSQL、MyBatis。让我们假设确实需要将应用程序迁移到反应式堆栈。这意味着旧的 Postrges JDBC 驱动程序和 MyBatis 将不再适用,因为它们具有阻塞特性。这反过来意味着整个 DB 层(MyBatis 映射器)必须使用 r2dbc 实现之一进行重构。这真的很重要!
所以问题是:是否有任何 hack 可以使用现有的映射器以及反应式 Flux 和 Monos?也许一些类似回调的解决方案或其他什么?
更新
Mono
有一个 fromFuture
方法。以下代码有效并且按预期工作。但它真的被它灵反应了吗?
public Mono<User> getUserById(Long id) {
return Mono.fromFuture(
CompletableFuture.supplyAsync(() -> userMapper.getUser(id)));
}
恐怕答案很长 - 这是一个 大 主题,通常需要解压。但是 tl;dr 是你可以,但你可能不应该。
你当然可以...
So the question is: is there any hack to use existing mappers along with reactive Fluxes and Monos? Maybe some callback-like solution or something?
许多反应式框架,包括反应堆,都有设计用于执行此操作的机制。您将传统的阻塞库保留在反应链中,但要确保它们在传统的线程池中执行,这样它们就不会占用事件循环线程(这些必须 never 阻塞,这很关键。)在 Webflux / reactor 中,你会使用 Schedulers.boundedElastic()
这就是为了这个目的 - 它将维护一个弹性的工作池,该池将根据需要扩大和缩小,并在项目出现时处理它们出现。
顺便说一句,这几乎就是您的示例所做的事情 - 回想一下 supplyAsync()
uses the common ForkJoin pool,所以它所做的只是将您的任务委托给线程池支持的执行程序。然而,与反应堆 boundedElastic()
不同的是,fork 连接池不会根据您的要求扩大和缩小规模,因此它不适合这项任务。
...但你不应该这样做。
可能当然并不意味着你应该。通过以这种方式执行您的任务,您将失去 所有 响应式框架对这段特定代码的好处 - 最重要的是,您保留了您试图避免的上下文切换(首先使用反应堆的关键驱动因素。)
如果您将整个数据库层都放在阻塞线程池上,那么一开始使用反应式框架几乎没有任何好处。
你会吗?
但这并不意味着您应该永远不要使用它。对于大型应用程序关键组件(例如您的数据库层),这在我的书中是完全禁止的。但是,如果您的应用程序有一种不经常使用的辅助服务并且性能不是很关键,那么上述方法可以很好地工作 - 假设您有一个旧的阻塞数据库连接,它只用于一些很少使用的错误例如报告功能,然后使用弹性调度程序(恕我直言)是完全可以接受的。
那你该怎么办?
您需要转到 R2DBC - 我很清楚这一点。但是你实际上仍然可以在阻塞环境的规范/安全范围内这样做,至少在最初是这样。
在迁移较旧的应用程序时(假设您已经完成了尽职调查并且值得这样做)我的偏好实际上是采用“自下而上”的方法,将所有组件迁移到响应式等效项,然后调用 block()
在将反应式库(在本例中为 R2DBC)连接到旧的阻塞代码的地方。这给你带来了一些优势:
- 然后您可以 运行 使用新库进行全面测试,但所有测试都在旧的、阻塞的环境中 - 无需更改任何其他内容;
- 它允许您在出现问题时轻松恢复,而无需更改其他代码堆;
- 您可以采用增量方法,以这种方式一个接一个地迁移所有阻塞库,直到您拥有一整套“准备就绪”的响应式库,并经过尝试和测试;
- 当您进行“最后一次尝试”以切换到完全响应式堆栈时,您根本没有做太多更改 - 您只是切换环境并删除一些阻塞调用。然后对所有单独的部件进行试验和测试。
以上并不是说这项任务当然会很快或很容易——恰恰相反,尤其是如果您使用的是一些相对较新的库,可能会有问题。但它确实为您提供了一种明智的迁移方法,您可以在其中一点一点地测试系统,并希望在每一步都获得信心,事情正在按预期发生。
我有一个庞大的企业 java 应用程序,其中包含“你老了”的技术堆栈:Spring Web MVC、PostgreSQL、MyBatis。让我们假设确实需要将应用程序迁移到反应式堆栈。这意味着旧的 Postrges JDBC 驱动程序和 MyBatis 将不再适用,因为它们具有阻塞特性。这反过来意味着整个 DB 层(MyBatis 映射器)必须使用 r2dbc 实现之一进行重构。这真的很重要!
所以问题是:是否有任何 hack 可以使用现有的映射器以及反应式 Flux 和 Monos?也许一些类似回调的解决方案或其他什么?
更新
Mono
有一个 fromFuture
方法。以下代码有效并且按预期工作。但它真的被它灵反应了吗?
public Mono<User> getUserById(Long id) {
return Mono.fromFuture(
CompletableFuture.supplyAsync(() -> userMapper.getUser(id)));
}
恐怕答案很长 - 这是一个 大 主题,通常需要解压。但是 tl;dr 是你可以,但你可能不应该。
你当然可以...
So the question is: is there any hack to use existing mappers along with reactive Fluxes and Monos? Maybe some callback-like solution or something?
许多反应式框架,包括反应堆,都有设计用于执行此操作的机制。您将传统的阻塞库保留在反应链中,但要确保它们在传统的线程池中执行,这样它们就不会占用事件循环线程(这些必须 never 阻塞,这很关键。)在 Webflux / reactor 中,你会使用 Schedulers.boundedElastic()
这就是为了这个目的 - 它将维护一个弹性的工作池,该池将根据需要扩大和缩小,并在项目出现时处理它们出现。
顺便说一句,这几乎就是您的示例所做的事情 - 回想一下 supplyAsync()
uses the common ForkJoin pool,所以它所做的只是将您的任务委托给线程池支持的执行程序。然而,与反应堆 boundedElastic()
不同的是,fork 连接池不会根据您的要求扩大和缩小规模,因此它不适合这项任务。
...但你不应该这样做。
可能当然并不意味着你应该。通过以这种方式执行您的任务,您将失去 所有 响应式框架对这段特定代码的好处 - 最重要的是,您保留了您试图避免的上下文切换(首先使用反应堆的关键驱动因素。)
如果您将整个数据库层都放在阻塞线程池上,那么一开始使用反应式框架几乎没有任何好处。
你会吗?
但这并不意味着您应该永远不要使用它。对于大型应用程序关键组件(例如您的数据库层),这在我的书中是完全禁止的。但是,如果您的应用程序有一种不经常使用的辅助服务并且性能不是很关键,那么上述方法可以很好地工作 - 假设您有一个旧的阻塞数据库连接,它只用于一些很少使用的错误例如报告功能,然后使用弹性调度程序(恕我直言)是完全可以接受的。
那你该怎么办?
您需要转到 R2DBC - 我很清楚这一点。但是你实际上仍然可以在阻塞环境的规范/安全范围内这样做,至少在最初是这样。
在迁移较旧的应用程序时(假设您已经完成了尽职调查并且值得这样做)我的偏好实际上是采用“自下而上”的方法,将所有组件迁移到响应式等效项,然后调用 block()
在将反应式库(在本例中为 R2DBC)连接到旧的阻塞代码的地方。这给你带来了一些优势:
- 然后您可以 运行 使用新库进行全面测试,但所有测试都在旧的、阻塞的环境中 - 无需更改任何其他内容;
- 它允许您在出现问题时轻松恢复,而无需更改其他代码堆;
- 您可以采用增量方法,以这种方式一个接一个地迁移所有阻塞库,直到您拥有一整套“准备就绪”的响应式库,并经过尝试和测试;
- 当您进行“最后一次尝试”以切换到完全响应式堆栈时,您根本没有做太多更改 - 您只是切换环境并删除一些阻塞调用。然后对所有单独的部件进行试验和测试。
以上并不是说这项任务当然会很快或很容易——恰恰相反,尤其是如果您使用的是一些相对较新的库,可能会有问题。但它确实为您提供了一种明智的迁移方法,您可以在其中一点一点地测试系统,并希望在每一步都获得信心,事情正在按预期发生。