java - 使用特定缓冲区大小的分块传输编码在 Jetty 中传输缓慢

标签 java hadoop jetty

我正在调查 Jetty 6.1.26 的性能问题。 Jetty 似乎使用了 Transfer-Encoding: chunked,并且根据所使用的缓冲区大小,这在本地传输时可能会非常慢。

我创建了一个小型 Jetty 测试应用程序,其中包含一个演示该问题的 servlet。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.mortbay.jetty.Server;
import org.mortbay.jetty.nio.SelectChannelConnector;
import org.mortbay.jetty.servlet.Context;

public class TestServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        final int bufferSize = 65536;
        resp.setBufferSize(bufferSize);
        OutputStream outStream = resp.getOutputStream();

        FileInputStream stream = null;
        try {
            stream = new FileInputStream(new File("test.data"));
            int bytesRead;
            byte[] buffer = new byte[bufferSize];
            while( (bytesRead = stream.read(buffer, 0, bufferSize)) > 0 ) {
                outStream.write(buffer, 0, bytesRead);
                outStream.flush();
            }
        } finally   {
            if( stream != null )
                stream.close();
            outStream.close();
        }
    }

    public static void main(String[] args) throws Exception {
        Server server = new Server();
        SelectChannelConnector ret = new SelectChannelConnector();
        ret.setLowResourceMaxIdleTime(10000);
        ret.setAcceptQueueSize(128);
        ret.setResolveNames(false);
        ret.setUseDirectBuffers(false);
        ret.setHost("0.0.0.0");
        ret.setPort(8080);
        server.addConnector(ret);
        Context context = new Context();
        context.setDisplayName("WebAppsContext");
        context.setContextPath("/");
        server.addHandler(context);
        context.addServlet(TestServlet.class, "/test");
        server.start();
    }

}

在我的实验中,我使用了一个 128MB 的测试文件,该文件由 servlet 返回给使用本地主机连接的客户端。使用用 Java 编写的简单测试客户端(使用 URLConnection)下载此数据需要 3.8 秒,非常慢(是的,它是 33MB/s,听起来并不慢,只是这纯粹是本地并且输入文件被缓存;它应该快得多)。

这就是它变得奇怪的地方。如果我使用 wget 下载数据,wget 是一个 HTTP/1.0 客户端,因此不支持分 block 传输编码,它只需要 0.1 秒。这是一个更好的数字。

现在,当我将 bufferSize 更改为 4096 时,Java 客户端需要 0.3 秒。

如果我完全删除对 resp.setBufferSize 的调用(似乎使用 24KB block 大小),Java 客户端现在需要 7.1 秒,而 wget 突然变得同样慢!

请注意,我不是 Jetty 方面的专家。我在使用 reduce task 改组诊断 Hadoop 0.20.203.0 中的性能问题时偶然发现了这个问题,它使用 Jetty 以与减少示例代码非常相似的方式传输文件,缓冲区大小为 64KB。

问题在我们的 Linux (Debian) 服务器和我的 Windows 机器上以及 Java 1.6 和 1.7 上都会重现,因此它似乎完全取决于 Jetty。

有没有人知道是什么原因造成的,如果我能做些什么?

最佳答案

我相信通过查看 Jetty 源代码,我自己找到了答案。它实际上是响应缓冲区大小、传递给 outStream.write 的缓冲区大小以及是否调用 outStream.flush 的复杂相互作用(在某些情况下) .问题在于 Jetty 使用其内部响应缓冲区的方式,以及您写入输出的数据如何复制到该缓冲区,以及何时以及如何刷新该缓冲区。

如果与 outStream.write 一起使用的缓冲区大小等于响应缓冲区(我认为一个倍数也可以),或者更小并且 outStream.flush 是用过,性能还行。然后每个 write 调用都直接刷新到输出,这很好。但是,当写入缓冲区较大而不是响应缓冲区的倍数时,这似乎会导致刷新处理方式有些奇怪,导致额外的刷新,从而导致性能不佳。

在分 block 传输编码的情况下,电缆中有一个额外的扭结。对于除第一个 block 之外的所有 block ,Jetty 保留 12 个字节的响应缓冲区以包含 block 大小。这意味着在我的原始示例中,有一个 64KB 的写入和响应缓冲区,适合响应缓冲区的实际数据量仅为 65524 字节,因此,部分写入缓冲区再次溢出到多次刷新中。查看此场景捕获的网络跟踪,我看到第一个 block 为 64KB,但所有后续 block 均为 65524 字节。在这种情况下,outStream.flush 没有任何区别。

当使用 4KB 缓冲区时,只有在调用 outStream.flush 时我才看到速度很快。事实证明,resp.setBufferSize 只会增加缓冲区大小,并且由于默认大小为 24KB,因此 resp.setBufferSize(4096) 是空操作。但是,我现在正在写入 4KB 的数据,即使有保留的 12 个字节,这些数据也适合 24KB 的缓冲区,然后通过 outStream.flush 调用将其作为 4KB 的 block 刷新。但是,当删除对 flush 的调用时,它将让缓冲区填满,同样有 12 个字节溢出到下一个 block 中,因为 24 是 4 的倍数。

总结

似乎要使用 Jetty 获得良好的性能,您必须:

  • 当调用 setContentLength(无分 block 传输编码)并为 write 使用与响应缓冲区大小相同的缓冲区时。
  • 使用分 block 传输编码时,使用至少比响应缓冲区大小小 12 个字节的写入缓冲区,并在每次写入后调用 flush

请注意,“慢速”场景的性能仍然如此,您可能只会在本地主机或非常快(1Gbps 或更高)的网络连接上看到差异。

我想我应该为此提交针对 Hadoop 和/或 Jetty 的问题报告。

关于java - 使用特定缓冲区大小的分块传输编码在 Jetty 中传输缓慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9031311/

相关文章:

java - 远程访问hbase

hadoop - 如何使用 UPDATE 使用 CQL 插入到只有主键的表中?

servlets - 在jetty中举办 war 时,META-INF/resources/webjars/resources是如何获取的?

memory-leaks - jetty 7 : OutOfMemoryError: PermGen space on application redeploy

java - 用okhttp区分Connect Timeout和Read Timeout

java - 使用 JNI 将指向 double 组的 C 指针传递给 Java

MVC 的 Java 包

java - 如何在android studio中的imageView上添加两个具有不同枢轴的旋转?

csv - 加载值字段包含逗号和特殊字符的 csv 文件时 Hadoop Pig "Load"问题

clojure - "SSL doesn' t have a valid keystore"尝试连接到 Datomic Cloud 时出错