避免等待 Servlet 流实际上是一个受支持的案例???

2022-09-02 10:22:40

我的Servler花了相当多的时间阅读和写作。从长远来看,这可能是一个问题,因为它阻塞线程只是为了每秒读取/写入几个字节。(*)request.getInputStream()response.getOutputStream()

我对部分请求数据不感兴趣,在请求完全可用之前,处理不应开始。响应也是如此。

我想,异步IO可以解决这个问题,但我想知道什么是正确的方法。也许一个 servlet 在收集了整个输入后,用一个包装的、使用和调用链式的 servlet 替换了 ?FilterServletInputStreamByteArrayInputStreamrequest.startAsync

  • 有没有这样的过滤器?
  • 我应该写一个还是应该使用不同的方法?

请注意,我的意思是避免在慢速 servlet 流上浪费线程。这与避免浪费线程只是等待某些事件是不一样的。startAsync

是的,目前这将是一个过早的优化。

我的读取循环,如请求

在我目前的输入流读取方法中没有什么有趣的,但在这里你是:

private byte[] getInputBytes() throws IOException {
    ServletInputStream inputStream = request.getInputStream();
    final int len = request.getContentLength();
    if (len >= 0) {
        final byte[] result = new byte[len];
        ByteStreams.readFully(inputStream, result);
        return result;
    } else {
        return ByteStreams.toByteArray(inputStream);
    }
}

仅此而已,当数据不可用时,它会阻止; 来自番石榴。ByteStreams

我迄今为止的理解总结

正如答案清楚地表明的那样,使用servlet流而不浪费线程是不可能的。Servlet架构和通用实现都没有公开任何允许说“缓冲整个数据,只有在你收集了所有内容时才打电话给我”的东西,尽管他们使用NIO并且可以做到这一点。

原因可能是通常使用像nginx这样的反向代理,这可以做到这一点。nginx默认进行这种缓冲,直到两年前它才被关闭。

实际上是一个受支持的案例???

鉴于许多否定的答案,我不确定,但看起来我的目标

以避免在慢速 servlet 流上浪费线程

实际上是完全支持的:从3.1开始,有ServletInputStream.html#setReadListener,它似乎正是为此而生的。分配给处理最初调用的线程附加侦听器,并通过简单地从 返回返回到池。侦听器实现 ,当可以读取而不阻塞时调用,添加一段数据并返回。在 中,我可以对收集的数据进行整个处理。Servlet#Servicerequest.startAsync()serviceonDataAvailable()onAllDataRead()

有一个例子,如何使用Jetty来完成它。它似乎也涵盖了非阻塞输出。


(*)在日志文件中,我可以看到请求花费长达八秒的时间,这些请求花费在读取输入(100字节标头+ 100字节数据)上。这种情况很少见,但它们确实会发生,尽管服务器大部分处于空闲状态。所以我想,这是一个非常糟糕的连接上的移动客户端(我们的一些用户从连接性如此差的地方连接)。


答案 1

HttpServletRequest#startAsync() 对此没有用处。这只对推动诸如Web套接字和良好的'ol SSE之类的东西有用。此外,JSR356 Web Socket API就是建立在它之上的。

您的具体问题已被理解,但这绝对无法从服务端解决。你最终只会浪费更多的线程,原因很简单,因为容器已经将当前线程专用于servlet请求,直到请求正文完全读取到最后一位,即使它最终被新生成的异步线程读取。

为了保存线程,您实际上需要一个支持NIO的servlet容器,并在必要时打开该功能。使用NIO,单个线程可以处理可用堆内存允许的任意数量的TCP连接,而不是为每个TCP连接分配单个线程。然后,在您的 servlet 中,您根本不需要担心这个微妙的 I/O 任务。

几乎所有的现代服务容器都支持它:Undertow(WildFly),Grizzly(GlassFish/Payara),TomcatJetty等。有些默认启用它,有些则需要额外的配置。只需使用关键字“NIO”参考他们的文档即可。

如果你实际上还想保存 servlet 请求线程本身,那么你基本上需要退后一步,删除 servlet 并在现有的 NIO 连接器(Undertow、Grizzly、Jetty 等)之上实现基于 NIO 的自定义服务。


答案 2
  1. 你不能。Servlet 容器将线程分配给请求,这就是它的结束,它被分配了。这就是模型。如果你不喜欢这样,你将不得不停止使用Servlets。
  2. 即使可以解决 (1),也无法在输入流上启动异步 I/O。
  3. 处理慢速请求的方法是通过为您正在使用的任何容器设置适当的设置来使它们超时...如果你真的有问题,而且还不清楚你真的有问题,有一个大部分空闲的服务器,这种情况很少发生。
  4. 您的读取循环在没有差异的情况下进行区分。只需将请求输入流读取到其末尾即可。servlet 容器已经确保流结束发生在内容长度(如果提供)处。

推荐