0

赶超Netty:基于Java19虚拟线程的Nima发布

 1 year ago
source link: https://www.jdon.com/62478
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

赶超Netty:基于Java19虚拟线程的Nima发布


Níma 是一个基于 Java 19(目前是早期访问)的服务器实现,专为 Java 虚拟线程(Project Loom 的产品)而设计。

Helidon 4.0.0-ALPHA1 现在与我们全新的 Helidon Níma 一起发布,提供基于虚拟线程的 Web 服务器。对于那些对最新 Java 技术感兴趣的人来说,这是一个早期访问版本,但它还不适合生产使用!
要试用可用于生产的 Helidon,请查看我们的最新版本Helidon 3.0.0

在 Alpha 版本中,我们提供了以下协议的实现:

  • 带有流水线的 HTTP/1.1 — 服务器和客户端
  • HTTP/2 服务器(原型,已知问题)
  • gRPC 服务器(原型,已知问题)
  • WebSocket 服务器(原型)

线程
该实现使用虚拟线程,其设计和实现是为了提供一个恒定的、低开销的、高并发的服务器,同时保持一个阻塞线程模型。这让你写的代码不会因为反应式编程中经常遇到的问题而变得复杂,比如说。

套接字监听器

  • 套接字监听器是平台线程(数量非常少,每个打开的服务器套接字都有一个)。

HTTP/1.1

  • 1个虚拟线程来处理连接(包括路由)
  • 1个虚拟线程用于在该连接上进行写操作(可以被禁用,因此写操作发生在连接处理线程上)
  • 单个连接的所有请求都由连接处理程序来处理

HTTP/2.2:

  • 1个虚拟线程来处理连接
  • 1个虚拟线程处理该连接的写操作(可以禁用,以便写操作发生在连接处理程序线程上)
  • 每个HTTP/2流有一个虚拟线程(包括路由)。

虚拟线程执行器服务使用无边界的执行器。

协议
以下协议在我们的Alpha版本中已经实现。

  • 具有可扩展升级机制的HTTP/1.1
  • HTTP/1.1 WebSocket的升级实现
  • HTTP/1.1到HTTP/2明文(h2c)的升级实现
  • 具有可扩展的 "子协议 "机制的HTTP/2
  • HTTP/2 gRPC子协议的实现
  • 对其他TCP协议(包括非HTTP)的可扩展性
  • 对任何协议的服务器端TLS支持
  • 对任何协议的相互TLS支持
  • 可扩展的应用层协议协商(ALPN),由HTTP/2(h2)使用

路由
同一个 Web 服务器可用于路由到多个协议(例如,您可以拥有一个服务于 HTTP/1.1、HTTP/2、WebSocket 和 gRPC 的端口)。默认情况下实现 HTTP/1.1 路由(因为其他协议从它升级);其他协议是可以添加的单独模块。

  • HTTP 路由与版本无关——相同的路由可用于 HTTP/1.1 和 HTTP/2
  • 支持特定协议的路由——只服务于 HTTP/2 的路由是可能的
  • gRPC 路由(一元、服务器流式传输、客户端流式传输、双向)
  • WebSocket 路由(目前不实现子协议)

特征
实现了以下功能,可以尝试:

  • 跟踪支持——使用现有的 Helidon 跟踪实现,例如 Jaeger 或 Zipkin
  • 静态内容支持——来自类路径或文件系统
  • CORS 支持
  • 访问日志支持
  • 可观察性端点(健康、应用程序信息、配置)
  • 容错(隔板、断路器、重试和超时功能)
  • HTTP/1.1 客户端

阻塞式与反应式
让我们比较一下Níma(阻塞式)和Helidon SE(反应式)在同一任务中的实现。

我们需要建立每个框架的基本规则。

  • 反应式--你不能阻塞请求的线程,这是通过反应式流API支持的,比如Helidon的Multiand Single
  • 阻塞式--你不能异步地完成响应(例如,响应必须从发出请求的同一线程发出)

注意:在这两种情况下,你都不应该 "阻塞 "线程。阻碍是对线程的长期、充分的利用。

  • 在一个反应式框架中,这将消耗一个事件循环线程,有效地停止了服务器。
  • 在阻塞式(Níma)中,这可能会导致 "钉子线程 "的问题。

在这两种情况下,可以通过使用平台线程将重载卸载到专门的执行器服务来解决。

堵塞:

private void one(ServerRequest req, ServerResponse res) {
  String response = callRemote(client());
  res.send(response);
}

反应式:

private void one(ServerRequest req, ServerResponse res) {
  Single<String> response = client.get()
          .request(String.class);

  response.forSingle(res::send)
          .exceptionally(res::send);
}

我们可以看到,使用阻塞代码,我们可以获取响应并发送它。如果发生任何事情,默认异常处理程序将正确处理异常,或发送内部服务器错误。

另一方面,对于反应、响应式,我们必须使用响应式流并处理异常,否则异常会丢失。
阻塞代码的优点:

  • 简单的异常处理
  • 有意义的堆栈跟踪(包括线程转储)
  • 更少的脚手架

复杂案例
在此示例中,我们将并行调用另一个服务,将结果组合成一个响应。
我们期望从查询参数中获取并行请求的数量;我们将把它放在变量“count”中。此外,此处未显示对 InterruptedException 和 ExecutionException 的处理,即使必须这样做(在下一个 Alpha 版本中,处理程序将允许抛出已检查的异常)。
阻塞:

List<String> responses = new LinkedList<>();

// list of tasks to be executed in parallel
List<Callable<String>> callables = new LinkedList<>();
for (int i = 0; i < count; i++) {
  callables.add(() -> client.get().request(String.class));
}

// execute all tasks (blocking operation)
for (var future : EXECUTOR.invokeAll(callables)) {
  responses.add(future.get());
}

// send it
res.send("Combined results: " + responses);

反应性:

// create a dummy stream from numbers 0 to count
Multi.range(0, count)
  // for each number, call the task on a different thread
  .flatMap(i -> Single.create(CompletableFuture.supplyAsync(() -> 
        client().get().request(String.class), EXECUTOR))
  // flat map from Single<Single<String>> to Single<String>
  .flatMap(Function.identity()))
  .collectList()
  .map(it -> "Combined results: " + it)
  .onError(res::send)
  .forSingle(res::send);

尽管我们用响应式代码实现了相同的效果,但代码的可读性要差得多(并且很难正确编写)。

性能
我们主要关注的是性能。下面我们展示了与纯Netty(4.1.36.Final版--没有额外的功能,只有HTTP)上的非阻塞实现相比的当前数据(ALPHA-1版)。

这些是在一台机器上使用环回接口进行的相当简单的基准测试(例如,有一个已知的客户端和服务器进程的干扰,因为它们共享CPU)。然而,它为我们提供了一个快速的性能比较,看看我们是否可以与非阻塞的实现相比较。与任何性能测试一样,结果会因很多因素而不同(特别是对Linux环境如何进行性能优化)。

测试结果:

Nima可以实现与极简 Netty 服务器相当的性能,同时保持简单、易于使用的编程模型。

2dbbd7338d0843fd85abc3553fe0fe31~noop.image?_iz=58558&from=article.pc_detail&x-expires=1664063720&x-signature=pFydDnYPRVK9EaKKoDyWrY6nRGA%3D

Performance results (requests/second)

6ac3750717344b4ba9362833bc7e3794~noop.image?_iz=58558&from=article.pc_detail&x-expires=1664063720&x-signature=FeCl0zY0M6UKupWZaJ2xrAToWjY%3D

Performance results (requests/second), HTTP/1.1 with pipelining

重复这个实验工具:
HTTP/1.1 using wrk:HTTP/2 using h2load:

h2load -n 50000000 -t 5 -c 5 -m 100 https://localhost:8081/plaintext

  • Netty: customized TechEmpower benchmark (as mentioned above) with HTTP/2

入门
有几个先决条件:

运行:

git clone [url]https://github.com/tomas-langer/helidon-nima-example[/url] cd helidon-nima-example
  • 2、从存储库的根目录运行 Maven
mvn clean package
  • 3、 运行 Níma 服务(需要预览,因为虚拟线程是 Java 19 中的预览功能)
java --enable-preview -jar nima/target/example-nima-blocking.jar
  • 4、端点:单一响应
curl -i [url]http://localhost:8080/one[/url]
  • 多个端点的顺序执行
curl -i [url=http://localhost:8080/sequence]http://localhost:8080/sequence [/url] curl -i [url]http://localhost:8080/sequence?count=5[/url]
  • 多个端点的并行执行

curl -i http://localhost:8080/parallel curl -i http://localhost:8080/parallel?count=5
该示例的来源可以在模块中的签出项目中找到nima:

  • NimaMain— 配置路由和启动服务器的主类
  • BlockingService— 具有上述端点的 HTTP 服务
  • resources/application.yaml— 服务器的配置

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK