Jetty和其他容器如何在坚持Servlet规范的同时利用NIO?

2022-09-01 18:57:07

我是NIO的新手,我正试图弄清楚Jetty如何利用NIO。

我对使用阻塞 IO 服务请求的传统 Servlet 容器的理解如下:

  1. 请求到达
  2. 分配一个线程来处理请求,并调用 servlet 方法( etc)doGet
  3. Servlet 方法被赋予一个和InputStreamOutputStream
  4. servlet 方法从 读取并写入InputStreamOutputStream
  5. 和 基本上与底层的相应流相关联InputStreamOutputStreamSocket

使用 NIO 连接器时有何不同?我的猜测大致如下:

  1. 请求到达
  2. Jetty 使用 NIO 连接器并异步缓冲整个请求
  3. 读取请求后,将缓冲区完全包装在InputStream
  4. 创建一个空的响应缓冲区(包装在OutputStream)
  5. 分配一个线程并调用 servlet 方法 ( etc) 来处理上述包装流doGet
  6. Servlet 方法写入包装(缓冲)响应流,并从 servlet 方法返回
  7. Jetty 使用 NIO 将响应缓冲区内容写入底层SocketChannel

从Jetty文档中,我发现了以下内容:

选择通道连接器 - 此连接器使用具有非阻塞线程模型的高效 NIO 缓冲区。Jetty 使用 Direct NIO 缓冲区,并且仅将线程分配给具有请求的连接。同步模拟 servlet API 的阻塞,请求处理结束时的任何未刷新内容都是异步编写的。

我不确定我是否理解同步模拟 servlet API 的阻塞意味着什么?


答案 1

你没有完全正确。当 jetty 使用 NIO 连接器(9 仅支持 NIO)时,其工作原理如下:

  1. 空闲状态为几个线程(1-4 个,具体取决于内核数)调用选择器,查找 IO 活动。这已经扩展到码头上超过1,000,000个连接。
  2. 当选择器看到 IO 活动时,它会在连接上调用一个 handle 方法,该方法之一是:

    • 其他内容已注册它被阻止等待 IO 进行此连接,因此在这种情况下,选择器只会唤醒被阻止的用户。
    • 否则,将调度一个线程来处理连接。
  3. 如果调度了一个线程,那么它将尝试读取连接并解析它。现在发生的事情取决于连接是http,spdy,http2还是websocket。

    • 对于http,如果请求标头已完成,线程将继续调用请求的处理(最终到达servlet),而无需等待任何内容。
    • 对于 http2/spdy,需要另一个调度,但请参阅列表中有关 Eat-What-You-Kill 策略的讨论:http://dev.eclipse.org/mhonarc/lists/jetty-dev/msg02166.html
    • 对于 websocket,调用消息处理。
  4. 一旦一个线程被调度到一个 servlet,它看起来就像 servlet IO 正在阻塞,但在 HttpInputStream 和 HttpOutputStream 的级别下,所有的 IO 都与回调同步。阻塞 API 使用特殊的阻塞回调来实现阻塞。这意味着,如果 servlet 选择使用异步 IO,那么它只是绕过阻塞回调,或多或少地直接使用异步 API。

  5. Servlet 可以使用 request.startAsync 挂起,在这种情况下,调度的线程将返回到线程池,但关联的连接不会被标记为对 IO 感兴趣。可以执行异步 IO,但需要 AsyncContext 事件来重新分配线程或在异步周期完成后为 IO 活动重新注册连接。

这个视图因http2和spdy而稍微复杂,它们是多路复用的,因此它们可以涉及额外的调度。

任何不调度的HTTP框架都可以在基准代码中非常快地运行,但是当面对一个真正的应用程序时,它可以做一些愚蠢的事情,比如数据库,文件系统,REST服务等......那么缺少调度只是意味着一个连接可以阻止系统上的所有其他连接。

有关码头如何处理异步和调度的更多信息,请参阅:


答案 2

推荐