避免等待Servlet流

时间:2017-07-06 11:38:05

标签: java servlets asynchronous

我的Servler花了很多时间阅读request.getInputStream()并写信给response.getOutputStream()。从长远来看,这可能是一个问题,因为它阻止一个线程,除了每秒几个字节读/写字面。 (*)

我从不对部分请求数据感兴趣,处理不应在请求完全可用之前启动。同样的回应。

我想,异步IO会解决它,但我想知道什么是正确的方法。也许servlet FilterServletInputStream替换ByteArrayInputStream,使用request.startAsync并在收集整个输入后调用链式servlet?

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

请注意,我的意思是避免在慢速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默认执行此缓冲,甚至在two years ago之前无法关闭。

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

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

  

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

实际上完全支持:从3.1开始,ServletInputStream.html#setReadListener似乎就是为了这个。分配用于处理Servlet#Service的线程最初调用request.startAsync(),附加侦听器并通过从service返回返回池。监听器实现onDataAvailable(),当它可以读取而不阻塞时会被调用,添加一段数据并返回。在onAllDataRead()中,我可以对收集的数据进行整个处理。

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

(*)在日志文件中,我可以看到花费在读取输入(100字节标题+ 100字节数据)上花费8秒的请求。这种情况很少见,但它们确实发生了,尽管服务器大多处于闲置状态。所以我猜,它是一个连接非常糟糕的移动客户端(我们的一些用户从连接不良的地方连接)。

3 个答案:

答案 0 :(得分:12)

HttpServletRequest#startAsync()对此无用。这对于推动诸如网络套接字和优秀的SSE之类的东西非常有用。此外,JSR356 Web Socket API建立在它之上。

您的具体问题已被理解,但这绝对无法通过servlet解决。由于容器已经将当前线程专用于servlet请求,直到请求主体被完全读取到最后一位,即使它已经完成,你最终只会浪费更多线程。最终由新生成的异步线程读取。

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

几乎所有现代servletcontainers都支持它:Undertow(WildFly),Grizzly(GlassFish / Payara),TomcatJetty等等。有些人默认启用它,其他人需要额外配置。只需使用关键字" NIO"。

推荐他们的文档

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

答案 1 :(得分:6)

  1. 你不能。 Servlet容器将线程分配给请求,并且它的结束是它的分配。那就是模特。如果您不喜欢,则必须停止使用Servlet。
  2. 即使您可以解决(1),也无法在输入流上启动异步I / O.
  3. 处理慢速请求的方法是通过为您正在使用的任何容器设置适当的设置来将它们计时... 如果您实际上遇到了问题,那么&#39你真的很清楚自己做的很多,而且服务器空闲,这种情况很少发生。
  4. 你的阅读循环有所区别。只需读取请求输入流到它的结尾。 servlet容器已确保流的末尾发生在内容长度(如果提供)。

答案 2 :(得分:2)

有一个名为org.apache.catalina.connector.CoyoteAdapter的类,它是从TCP工作线程接收封送请求的类。它有一个名为" service"这是繁重的大部分工作。此方法由另一个类调用:org.apache.coyote.http11.Http11Processor,它也有一个同名的方法。

我觉得有趣的是,我在代码中看到如此多的钩子来处理异步io,这让我想知道这不是容器的内置功能吗?无论如何,凭借我有限的知识,我能想到实现你所讨论的功能的最佳方式是创建一个类:

public class MyAsyncReqHandlingAdapter extends CoyoteAdapter@Override service()方法并推出自己的方法......我现在没有时间专心做这件事,但我将来可能会再次访问。

在这种方法中,您需要一种方法来识别请求并处理它们,方法是将它们交给单线程nio处理器并完成"完成"在该级别的请求,给定源代码:

https://github.com/apache/tomcat/blob/075920d486ca37e0286586a9f017b4159ac63d65/java/org/apache/coyote/http11/Http11Processor.java

https://github.com/apache/tomcat/blob/3361b1321201431e65d59d168254cff4f8f8dc55/java/org/apache/catalina/connector/CoyoteAdapter.java

你应该能够弄明白该怎么做。有趣的问题,是的,它可以做到。我没有在规范中看到它说它不能......