Spring 在后台使用 Netty 与 Tomcat 时 webFlux 的差异

Spring webFlux differrences when Netty vs Tomcat is used under the hood

我正在学习spring webflux,我已经阅读了以下系列文章(first, second, third)

在第三篇文章中,我遇到了以下文字:

Remember the same application code runs on Tomcat, Jetty or Netty. Currently, the Tomcat and Jetty support is provided on top of Servlet 3.1 asynchronous processing, so it is limited to one request per thread. When the same code runs on the Netty server platform that constraint is lifted, and the server can dispatch requests sympathetically to the web client. As long as the client doesn’t block, everyone is happy. Performance metrics for the netty server and client probably show similar characteristics, but the Netty server is not restricted to processing a single request per thread, so it doesn’t use a large thread pool and we might expect to see some differences in resource utilization. We will come back to that later in another article in this series.

首先,我没有看到该系列的更新文章,尽管它是在 2016 年写的。我很清楚 tomcat 默认有 100 个线程处理请求,一个线程处理一个请求在同一时间,但我不明白短语 it is limited to one request per thread 这是什么意思?

我还想知道 Netty 如何处理那个具体案例(我想了解与 Tomcat 的区别)。每个线程可以处理 2 个请求吗?

目前有 2 个基本概念来处理对 web-server 的并行访问,各有优缺点:

  1. 阻塞
  2. Non-Blocking

阻塞Web-Servers

阻塞的第一个概念,multi-threaded 服务器在池中有有限数量的线程。每个请求都将分配给特定的线程,并且该线程将被分配直到请求被完全服务。这与超市结账队列的工作方式基本相同,一次一个顾客,可能有平行线。在大多数情况下,Web 服务器中的请求在处理请求的大部分时间里都是 cpu-idle。这是因为它必须等待 I/O:读取套接字,写入数据库(基本上也是 IO)并读取结果并写入套接字。另外 using/creating 一堆线程很慢(上下文切换)并且需要大量内存。因此,这个概念通常不会非常有效地使用它拥有的硬件资源,并且对可以并行服务的客户端数量有硬性限制。 属性 被滥用于所谓的饥饿攻击,例如slow loris,一种通常单个客户端可以毫不费力地 DOS 大 multi-threaded web-server 的攻击。

总结

  • (+) 更简单的代码
  • (-) 并行客户端的硬限制
  • (-) 需要更多内存
  • (-) 日常 web-server 工作的硬件使用效率低下
  • (-) 易于DOS

大多数 "conventional" 网络服务器都是这样工作的,例如较旧 tomcat、Apache Webserver 以及所有 Servlet 比 3 或 3.1 等旧的

Non-Blocking Web-Servers

相比之下,non-blocking web-server 可以只用一个线程来服务多个客户端。那是因为它使用了non-blocking kernel I/O features。这些只是内核调用,它们会立即 return 并在可以写入或读取某些内容时回调,从而使 cpu 可以自由地做其他工作。重用我们的超市比喻,这就像,当收银员需要他的主管解决问题时,他不会等待并阻塞整个通道,而是开始结账下一位顾客,直到主管到达并解决第一位顾客的问题客户。

这通常在事件循环或更高级的抽象中完成,如 green-threads or fibers. In essence such servers can't really process anything concurrently (of course you can have multiple non-blocking threads), but they are able to serve thousands of clients in parallel because the memory consumption will not scale as drastically as with the multi-thread concept (read: there is no hard limit on max parallel clients). Also there is no thread context-switching. The downside is, that non-blocking code is often more complex to read and write (e.g. callback-hell),并且在请求执行大量 cpu-expensive 工作的情况下效果不佳。

总结

  • (-) 更复杂的代码
  • (-) cpu 密集型任务的性能更差
  • (+) 比网络服务器更有效地使用资源
  • (+) 更多没有 hard-limit 的并行客户端(最大内存除外)

最现代的 "fast" web-servers 和框架促进了 non-blocking 概念:Netty、Vert.x、Webflux、nginx、servlet 3.1+、Node、Go Webservers。

附带说明一下,查看此基准页面,您会发现大多数最快的 web-server 通常是 non-blocking:https://www.techempower.com/benchmarks/


另见

使用 Servlet 2.5 时,Servlet 容器会将请求分配给线程,直到该请求被完全处理。

使用 Servlet 3.0 异步处理时,服务器可以在应用程序处理请求时在单独的线程池中分派请求处理。然而,当谈到 I/O 时,工作总是发生在服务器线程上,并且总是阻塞。这意味着 "slow client" 可以独占服务器线程,因为服务器被阻塞,而 reading/writing 到网络连接不佳的客户端。

对于 Servlet 3.1,异步 I/O 是允许的,在这种情况下,"one request/thread" 模型不再存在。在任何时候,位请求处理都可以安排在服务器管理的不同线程上。

Servlet 3.1+ 容器通过 Servlet API 提供了所有这些可能性。利用异步处理或 non-blocking I/O 取决于应用程序。在non-blockingI/O的情况下,范式的改变很重要,使用起来真的很有挑战性。

使用 Spring WebFlux - Tomcat,Jetty 和 Netty 没有完全相同的运行时模型,但它们都支持反应式背压和 non-blocking I/O。