spring - 您如何实际使用Spring 5的响应式(Reactive)编程来“管理”最大数量的Web线程?

标签 spring tomcat reactive-programming project-reactor

使用经典的Tomcat方法时,可以为服务器提供最大数量的线程,以用于处理来自用户的Web请求。使用Reactive Programming范例和Spring 5中的Reactor,我们能够在垂直方向上更好地扩展,从而确保最小限度地阻塞了我们。

在我看来,这比传统的Tomcat方法(仅定义并发请求的最大数量)要容易管理。当并发请求的数量最大时,可以更轻松地估计应用程序所需的最大内存并相应地扩展。当您使用Spring 5的Reactive Programming时,这似乎很麻烦。

当我向sysadmin朋友谈论这些新技术时,他们回答时担心应用程序的RAM不足,甚至操作系统级别的线程不足。那么我们如何更好地应对呢?

最佳答案

完全没有阻塞的I / O

首先,如果您没有任何阻塞操作,那么您完全不必担心我应该为管理并发提供多少线程。在这种情况下,我们只有一个工作线程,可以异步且非阻塞地处理所有连接。在这种情况下,我们可以轻松地扩展连接服务方工作程序,以处理所有连接而不会发生争用和一致性(每个工作人员都有自己的接收连接队列,每个工作人员都在自己的CPU上工作),并且在这种情况下,我们可以更好地扩展应用程序(没有共享设计)。

简介:在这种情况下,您可以通过配置应用程序容器(Tomcat,WebSphere等)来管理与以前相同的最大Web线程数,或者在非Servlet服务器(例如Netty或混合Undertow)的情况下,通过类似的方法来管理。好处-您可以处理更多的用户请求,但消耗的资源相同。

阻止数据库和非阻止Web API(例如,基于Netty的WebFlux)。

万一我们应该以某种方式处理阻塞I / O,以便通过阻塞JDBC与DB进行即时通信,这是使应用程序尽可能保持可伸缩性和效率的最合适方法,我们应该为I / O使用专用线程池。

线程池要求

首先,我们应该使用与JDBC connections-pool中可用连接数量完全相同的工作线程创建线程池。因此,我们将拥有完全相同数量的线程,这些线程将阻塞性地等待响应,并且我们将尽可能高效地利用我们的资源,因此不会像实际需要的那样为线程堆栈消耗更多的内存(换句话说,连接模型)。

如何根据连接池的大小配置线程池

由于对特定数据库和JDBC驱动程序的属性访问有所不同,因此我们可以始终在特定属性上外部化该配置,这又意味着可以由devops或sysadmin对其进行配置。
Threadpool的配置(在我们的示例中为Project Reactor 3的Scheduler的配置)可能如下所示:

@Configuration
public class ReactorJdbcSchedulerConfig {
    @Value("my.awasome.scheduler-size")
    int schedulerSize;

    @Bean
    public Scheduler jdbcScheduler() {
        return Schedulers.fromExecutor(new ForkJoinPool(schedulerSize));
        // similarly 
        // ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        // taskExecutor.setCorePoolSize(schedulerSize);
        // taskExecutor.setMaxPoolSize(schedulerSize);
        // taskExecutor.setQueueCapacity(schedulerSize);
        // taskExecutor.initialize();
        // return Schedulres.fromExecutor(taskExecutor);
    }
}
...

    @Autowire
    Scheduler jdbcScheduler;


    public Mono myJdbcInteractionIsolated(String id) {
         return Mono.fromCallable(() -> jpaRepo.findById(id))
                    .subscribeOn(jdbcScheduler)
                    .publishOn(Schedulers.single());
    }
...


可能会注意到,使用这种技术,我们可以将共享线程池配置委派给外部团队(实例的sysadmins),并允许他们管理用于创建的Java线程的内存消耗。

保留阻塞的I / O线程池仅用于I / O工作

该语句意味着I / O线程应仅用于阻塞等待的操作。反过来,这意味着在线程完成等待响应之后,您应该将结果处理移到另一个线程。

这就是为什么在上面的代码片段中,我将.publishOn放在.subscribeOn之后。

因此,总而言之,使用该技术,我们可以允许外部团队通过相应地控制线程池大小到连接池大小来管理应用程序的大小。所有结果处理将在一个线程内执行,因此不会有多余的,不受控制的内存消耗。

最后,阻止API(Spring MVC)和阻止I / O(数据库访问)

在那种情况下,根本不需要反应式范例,因为您不会从中获得任何收益。首先,反应式编程需要特别的思维转变,尤其是在理解功能性技术与反应性库(例如RxJava或Project Reactor)的用法时。反过来,对于没有准备好的用户,它会带来更多的复杂性并导致更多的“ ********正在发生什么??”。因此,在两端阻止操作的情况下,您应该三思而后行,这里是否真的需要反应式编程。

此外,没有免费的魔术。 Reactive Extensions具有很多内部复杂性,并且使用所有神奇的.map.flatMap等,您可能会损失整体性能和内存消耗,而不是像端到端非阻塞情况那样赢钱,异步通信。

这意味着旧的,好的命令式编程将在这里更合适,并且使用旧的,好的Tomcat配置管理,将更容易控制内存中应用程序的大小。

关于spring - 您如何实际使用Spring 5的响应式(Reactive)编程来“管理”最大数量的Web线程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45416290/

相关文章:

java - 多个 QueueConnectionFactory 上的异常

java - 从 Spring MVC 端点流式传输动态图像而不将其保存在内存中

java - 管理员和用户的访问权限

tomcat - 如何将 SHA256RSA 签名的服务器证书转换为 SHA1RSA?

php - Windows 中的 Apache Web 服务器和 Tomcat

c# - 如何在不使用 Subjects 或 .NET 事件的情况下创建在方法调用时发出的 IObservable?

c# - 发布可观察对象时异步创建挂起

java - 为什么在独立 Tomcat 上部署时我的 RequestMapping 没有被拾取?它适用于 Eclipse WTP

java - 从属 jar 不会部署到 tomcat,其中带有此 jar 的项目在工作区中打开

java - 带有 Spring 5 的响应式(Reactive) WebSockets - 如何获取初始消息