当使用经典的Tomcat方法时,您可以为服务器提供处理来自用户的web请求的最大线程数。使用反应式编程范式,以及Spring5中的Reactor,我们能够更好地垂直扩展,确保我们被阻塞得最少。
在我看来,这使得这比经典的Tomcat方法更难管理,在经典的Tomcat方法中,您只需定义并发请求的最大数量。当并发请求的数量达到最大值时,就更容易估计应用程序需要的最大内存并相应地进行扩展。当您使用Spring5的反应式编程时,这似乎更像是一个麻烦。
当我向sysadmin的朋友谈论这些新技术时,他们的回答是担心应用程序会耗尽RAM,甚至是操作系统级别的线程。那么我们怎样才能更好地处理这个问题呢?
发布于 2018-01-04 04:37:23
完全没有阻塞I/O
首先,如果你没有任何阻塞操作,那么你根本不应该担心,我应该提供多少线程来管理并发。在这种情况下,我们只有一个worker,它以异步和非阻塞的方式处理所有连接。在这种情况下,我们可以很容易地扩展连接服务工作者,这些工作者处理所有连接而没有争用和一致性(每个工作者都有自己的接收连接队列,每个工作者在自己的CPU上工作),在这种情况下,我们可以更好地扩展应用程序(无共享设计)。
Servlet摘要:在这种情况下,您可以像以前一样管理Web线程的最大数量,通过配置应用程序容器(Tomcat,WebSphere等)或类似的非Servlet服务器,如Netty,或混合Undertow。好处--你可以处理更多的用户请求,但消耗的资源是一样的。
阻塞数据库和非阻塞Web (如Netty上的WebFlux )。
如果我们应该以某种方式处理阻塞I/O,为了通过阻塞JDBC与DB进行即时通信,尽可能保持您的应用程序可伸缩和高效的最合适的方法,我们应该为I/O使用专用的线程池。
线程池需求
首先,我们应该创建线程池,线程池的工作线程数与JDBC连接池中可用连接的工作线程数完全相同。因此,我们将拥有完全相同数量的线程,这些线程将阻塞地等待响应,并且我们尽可能有效地利用我们的资源,因此不会有更多的内存被消耗给实际需要的线程堆栈(换句话说,每个连接模型都有线程)。
如何根据连接池大小配置线程池
由于对属性的访问对于特定的数据库和JDBC驱动程序是不同的,因此我们可以始终将该配置外部化到特定属性上,这反过来意味着它可以由devops或sysadmin进行配置。线程池的配置(在我们的示例中是配置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());
}
...
正如可能注意到的,使用该技术,我们可以将共享线程池配置委托给外部团队(实例为sysadmin),并允许他们管理用于创建的Java线程的内存消耗。
只为I/O工作保留阻塞I/O线程池
这句话意味着I/O线程应该只用于阻塞等待的操作。反过来,这意味着在线程完成等待响应之后,您应该将结果处理转移到另一个线程。
这就是为什么在上面的代码片段中,我把.publishOn
放在.subscribeOn
后面的原因。
因此,总结一下,使用该技术,我们可以允许外部团队通过相应地控制线程池大小来管理应用程序大小。所有的结果处理都将在一个线程中执行,因此不会有多余的、不受控制的内存消耗。
最后,阻塞API (Spring MVC)和阻塞I/O (数据库访问)
在这种情况下,根本不需要反应式范式,因为你不会从中获得任何好处。首先,反应式编程需要特别的思想转变,特别是在理解功能技术与反应式库(如RxJava或项目反应器)的使用方面。反过来,对于没有准备好的用户,它会带来更多的复杂性,并导致更多的“这里发生了什么?”。因此,在两端阻塞操作的情况下,您应该三思而后行,您是否真的需要在这里进行反应式编程。
而且,没有免费的魔法。反应式扩展带来了很多内部复杂性,使用所有这些神奇的.map
、.flatMap
等,你可能会损失整体性能和内存消耗,而不是像端到端的非阻塞、异步通信那样获胜。
这意味着旧的好的命令式编程将更适合这里,并且使用旧的好的Tomcat配置管理在内存中控制应用程序的大小将会容易得多。
发布于 2017-08-03 17:13:41
你能试试这个吗:
public class AsyncConfig implements AsyncConfigurer {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(15);
taskExecutor.setMaxPoolSize(100);
taskExecutor.setQueueCapacity(100);
taskExecutor.initialize();
return taskExecutor;
}
}
这在spring 4中适用于async,但我不确定它在spring 5中是否适用于reactive。
https://stackoverflow.com/questions/45416290
复制相似问题