Web 应用程序和 Project Loom

工程 | Mark Thomas | 2023 年 2 月 27 日 | ...

引言

Project Loom 旨在为 JRE 带来“易于使用、高吞吐量、轻量级并发”。Project Loom 引入的一项功能是虚拟线程。在这篇博文中,我们将通过一些部署在 Apache Tomcat 上的简单 Web 应用程序,探讨虚拟线程对 Web 应用程序意味着什么。

高吞吐量/轻量级

第一个实验是比较使用Tomcat标准线程池和使用基于虚拟线程(Loom)的执行器所带来的开销。本文末尾详细介绍了所使用的测试环境。我们通过平均每秒请求数来检查不同响应大小和请求并发情况下的性能。结果显示在下图中。

loom-results-01

结果表明,通常情况下,创建新虚拟线程来处理请求的开销小于从线程池中获取平台线程的开销。

在线程池测试中出现的一个意外结果是,对于较小的响应体,2个并发用户导致的平均每秒请求数比单个用户更少,这一点更为明显。调查发现,额外的延迟发生在任务被传递给执行器和执行器调用任务的run()方法之间。这种差异在4个并发用户时有所减小,在8个并发用户时几乎消失。

在高并发级别,当并发任务数超过可用的处理器核心数时,虚拟线程执行器再次显示出更高的性能。这在使用较小响应体的测试中更为明显。

易于使用

第二个实验比较了使用Servlet异步I/O与标准线程池的性能,以及使用简单阻塞I/O与基于虚拟线程的执行器的性能。虚拟线程在这里的潜在优势是简化。阻塞读写比等效的Servlet异步读写要简单得多——尤其是在考虑错误处理时。

Servlet异步I/O通常用于访问外部服务,其中响应存在明显的延迟。测试Web应用程序在Service类中模拟了这种情况。使用基于虚拟线程的执行器的Servlet以阻塞方式访问服务,而使用标准线程池的Servlet则使用Servlet异步API访问服务。尽管没有涉及网络I/O,但这应该不会影响结果。

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

loom-results-02

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

分析

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

Tomcat标准线程池与基于虚拟线程的执行器之间性能差异的主要驱动因素是向线程池队列添加和移除任务时的争用。通过优化Tomcat当前使用的实现,有可能减少标准线程池队列中的争用,从而提高吞吐量。

影响相对性能的次要因素是上下文切换。这可能是第二个实验中,一旦并发度超过可用处理器核心数后,性能差异的合理解释,因为虚拟线程的上下文切换成本低于标准线程池中的线程。

结论

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

在Tomcat上遇到阻塞(例如经典Spring MVC)且尚未切换到Servlet异步API、响应式编程或其他异步API的Web应用程序,通过切换到基于虚拟线程的执行器,应该会看到一些可伸缩性改进。根据Web应用程序的不同,这些改进可能无需更改Web应用程序代码即可实现。

已切换到使用Servlet异步API、响应式编程或其他异步API的Web应用程序,切换到基于虚拟线程的执行器后,不太可能观察到可测量的差异(无论是正面的还是负面的)。

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

最后,Project Loom仍处于预览模式。现在考虑在生产环境中使用虚拟线程还为时过早,但现在是时候将Project Loom和虚拟线程纳入您的规划中,以便在JRE中普遍提供虚拟线程时做好准备。

测试环境

测试环境包括以下内容:

测试是在一台完全更新的Ubuntu 22.04.1 LTS机器上进行的,该机器配备Intel i7-6950X处理器和32 GB RAM。

为了最大限度地显示测试之间的差异,对默认配置进行了以下更改,以最大限度地减少共同开销:

  • 在单机上使用环回接口运行测试,以最大限度地减少网络开销
  • 禁用访问日志,因为在高请求量下它是大量磁盘I/O的来源
  • 将maxKeepAliveRequests设置为-1,以减少建立和拆除TCP连接的时间

测试Web应用程序也旨在最大限度地减少共同开销并突出测试之间的差异。

使用的server.xml文件是

<?xml version="1.0" encoding="UTF-8"?>
<Server port="8005" shutdown="SHUTDOWN">
  <Listener className="org.apache.catalina.startup.VersionLoggerListener" />

  <Service name="Catalina">

    <Executor
        className="org.apache.catalina.core.LoomExecutor"
        name="loomExecutor"
        />

    <Connector 
        protocol="org.apache.coyote.http11.Http11NioProtocol"
        port="8080"
        maxKeepAliveRequests="-1"
        />

    <Connector
        executor="loomExecutor"
        protocol="org.apache.coyote.http11.Http11NioProtocol"
        port="8081"
        maxKeepAliveRequests="-1"
        />

    <Engine name="Catalina" defaultHost="localhost">
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true">
      </Host>
    </Engine>
  </Service>
</Server>

使用的setenv.sh文件是

#!/bin/sh
JAVA_OPTS=--enable-preview

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有