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 访问服务。没有涉及任何网络 IO,但这不应该影响结果。

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

loom-results-02

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

分析

基于虚拟线程的执行器与 Tomcat 的标准线程池之间的差异并不像从上图中最初看起来那样明显。测试旨在检查每种方法相关的开销,并不代表真实世界的应用。在真实世界的应用中,测试中显示的差异可能相对于完成请求所需的时间可以忽略不计。

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

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

结论

使用基于虚拟线程的执行器是 Tomcat 标准线程池的可行替代方案。从切换到虚拟线程执行器获得的益处在容器开销方面是微不足道的。

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

已切换到使用 Servlet 异步 API、响应式编程或其他异步 API 的 Web 应用不太可能通过切换到基于虚拟线程的执行器观察到可衡量的差异(正面或负面)。

从长远来看,虚拟线程的最大好处似乎是更简单的应用代码。一些当前需要使用 Servlet 异步 API、响应式编程或其他异步 API 的用例将能够使用阻塞 IO 和虚拟线程来满足。但这有一个需要注意的地方,即应用通常需要对不同的外部服务进行多次调用。这最有效地以并行方式完成,而像 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 应用 test web application 也旨在最大程度地减少共同开销并突出测试之间的差异。

使用的 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 Newsletter

订阅

抢先一步

VMware 提供培训和认证,助您加速发展。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举办的活动

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

查看全部