介绍

Loom项目的目标是为JRE带来易于使用、高吞吐、轻量级并发。Loom的一个特性是虚拟线程。在本文中,我们将探索在Tomcat上部署的简单web应用上使用虚拟线程更意味着什么。

高吞吐/轻量级

第一个实验是比较使用Tomcat标准线程池的开销和使用虚拟线程的执行器的开销。测试环境在本文最后给出。使用每秒平均请求数检查了不同响应大小和请求并发性的性能。结果如下图所示。

结果表明,通常,创建新的虚拟线程来处理请求的开销小于从线程池获取平台线程的开销。在线程池测试中看到的一个意外结果是,对于较小的响应主体,2个并发用户每秒产生的平均请求数比单个用户少。调查发现,在传递给Executor的任务和Executor调用任务的run()方法之间发生了额外的延迟。这一差异在4个并发用户中减少,在8个并发用户几乎消失。

当并发任务多于可用的处理器核时,在高并发级别,虚拟线程执行器再次显示出更高的性能。这在使用较小响应体的测试中更为明显。根据图中的测试,(译者注:响应体小于4k时,并发越大越明显。小于16K是会有一定量的提高。再大一些的话和标准线程池差别不大了。

易于使用

第二个实验将使用标准线程池的Servlet异步I/O获得的性能与使用基于虚拟线程的执行器的简单阻塞I/O获得的效果进行了比较。虚拟线程的潜在好处是简单。与等效的Servlet异步读写相比,阻塞读写要简单得多,特别是在考虑错误处理时。

Servlet异步I/O通常用于访问响应有明显延迟的某些外部服务。测试web应用程序在Service类中对此进行了模拟。与基于虚拟线程的执行器一起使用的Servlet以阻塞方式访问服务,而与标准线程池一起使用的Servlet使用Servlet异步API访问服务。没有涉及任何网络IO,但这不应该影响结果。

不出所料,最初的测试显示,阻塞方法和异步方法之间没有可测量的差异,因为计时主要由5秒延迟控制。为了在没有延迟影响的情况下探索差异,将延迟减少为零,并执行了一组与吞吐量测试类似的测试。结果如下图所示:

我们再次看到,虚拟线程通常性能更高,在低并发和并发超过可用于测试的处理器内核数量时,差异最为明显。

分析

基于虚拟线程的执行器和Tomcat的标准线程池之间的差异并不像从上图中首次看到的那样明显。这些测试旨在检查与每种方法相关的开销,并不代表实际应用程序。在实际应用中,与完成请求所需的时间相比,测试中显示的差异可能微不足道。

第一个因素:Tomcat的标准线程池和基于虚拟线程的执行器之间性能差异的主要因素是线程池队列添加和删除任务的竞争。通过优化Tomcat使用的当前实现,可以减少标准线程池队列中的锁竞争,并提高吞吐量。

第二个因素:上下文切换。这可能是第二个实验中出现的性能差异的一种解释,因为虚拟线程的上下文切换比标准线程池中的线程的成本更低,因此并发性超过了可用的处理器内核数。

结论

使用基于虚拟线程的执行器是Tomcat标准线程池的可行替代方案。就容器开销而言,切换到虚拟线程执行器的好处微乎其微。

经历阻塞诸的Web应用程序(比如Tomcat上经典的Spring MVC应用),并且尚未切换到Servlet异步API,响应式编程或其他异步API,应该通过切换到基于虚拟线程的执行器来看到一些可扩展性的改进。根据Web应用程序,可以实现这些改进,而不会更改Web应用程序代码。切换到使用Servlet异步API、响应式编程或其他异步API的Web应用程序不太可能通过切换到基于虚拟线程的执行器来观察到可测量的差异(正面或负面),除非单机需承担的QPS特别大。

从长远来看,虚拟线程的最大好处似乎是更简单的应用程序代码。当前需要使用Servlet异步API、反应式编程或其他异步API的一些用例将能够使用阻塞IO和虚拟线程来满足。对此需要注意的是,应用程序通常需要对不同的外部服务进行多次调用。这是虚拟线程最有效的并行实现,虽然Project Reactor等框架为此提供了一流的支持,但JRE的同类解决方案(结构化并发)仍处于孵化器阶段,仅旨在协调多个Future,而不是以最方便的方式相互声明或组合它们。

最后,Loom项目仍处于预览模式。现在考虑在生产环境中使用虚拟线程还为时过早,但现在是将Loom项目和虚拟线程纳入规划的时候了,这样当JRE中正式有虚拟线程时,您就可以做好准备了。

测试环境

未译,原文地址: https://spring.io/blog/2023/0...


疯狂小兵
193 声望9 粉丝

专注做后端,用java和go做工具,编写世界