복붙노트

[HADOOP] 특정 버퍼 크기에서 청크 분할 전송 인코딩으로 느린 전송

HADOOP

특정 버퍼 크기에서 청크 분할 전송 인코딩으로 느린 전송

Jetty 6.1.26의 성능 문제를 조사하고 있습니다. Jetty는 Transfer-Encoding을 사용하는 것으로 보이는데, 사용 된 버퍼 크기에 따라 로컬로 전송할 때 속도가 느려질 수 있습니다.

문제를 나타내는 하나의 서블릿으로 작은 부두 테스트 애플리케이션을 만들었습니다.

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 테스트 파일을 사용하고 있습니다. Java로 작성된 간단한 테스트 클라이언트 (URLConnection 사용)를 사용하여이 데이터를 다운로드하는 것은 매우 느립니다 (예, 33MB / s입니다. 순전히 로컬이며 입력 파일이 캐시 된 것을 제외하고는 느려지지 않습니다. 그것은 훨씬 빨라야합니다.)

이제 이상한 곳이 있습니다. HTTP / 1.0 클라이언트이며 따라서 청크 분할 전송 인코딩을 지원하지 않는 wget을 사용하여 데이터를 다운로드하면 0.1 초 밖에 걸리지 않습니다. 그것은 훨씬 더 좋은 모습입니다.

이제 bufferSize를 4096으로 변경하면 Java 클라이언트가 0.3 초 ​​걸립니다.

resp.setBufferSize (24KB 청크 크기를 사용하는 것처럼 보임) 호출을 제거하면 Java 클라이언트는 이제 7.1 초가 걸리고 wget은 갑자기 속도가 똑같이 느려집니다!

제가 어떤 식 으로든 제티의 전문가가 아니란 점에 유의하십시오. Hadoop 0.20.203.0의 성능 문제를 진단하는 동안이 문제를 우연히 발견했습니다. 축소 된 작업 셔플 링은 64KB 버퍼 크기와 함께 축소 된 샘플 코드와 비슷한 방식으로 Jetty를 사용하여 파일을 전송합니다.

이 문제는 우리의 Linux (Debian) 서버와 Windows 머신 모두와 Java 1.6과 1.7 모두를 재현하기 때문에 Jetty에만 의존하는 것처럼 보입니다.

아무도 이것의 원인이 될 수있는 어떤 생각을 가지고 있으며, 내가 그것에 대해 할 수있는 일이 있다면?

해결법

  1. ==============================

    1.Jetty 소스 코드를 살펴보고 대답을 찾은 것 같습니다. 실제로 응답 버퍼 크기, outStream.write에 전달 된 버퍼 크기 및 outStream.flush 호출 여부는 복잡한 상호 작용입니다 (일부 상황에서). 이 문제는 Jetty가 내부 응답 버퍼를 사용하는 방법과 출력에 쓰는 데이터를 해당 버퍼에 복사하는 방법과 버퍼가 플러시되는시기 및 방법을 설명합니다.

    Jetty 소스 코드를 살펴보고 대답을 찾은 것 같습니다. 실제로 응답 버퍼 크기, outStream.write에 전달 된 버퍼 크기 및 outStream.flush 호출 여부는 복잡한 상호 작용입니다 (일부 상황에서). 이 문제는 Jetty가 내부 응답 버퍼를 사용하는 방법과 출력에 쓰는 데이터를 해당 버퍼에 복사하는 방법과 버퍼가 플러시되는시기 및 방법을 설명합니다.

    outStream.write와 함께 사용되는 버퍼의 크기가 응답 버퍼와 같으면 (여러개도 작동한다고 생각합니다.) 또는 더 적은 수의 outStream.flush가 사용되면 성능이 좋습니다. 각 쓰기 호출은 출력으로 곧바로 플러시됩니다. 그러나 쓰기 버퍼가 크고 응답 버퍼의 배수가 아닌 경우 플러시 처리 방식이 이상하게 들릴 수 있으므로 플러시가 추가되어 성능이 저하 될 수 있습니다.

    청크 분할 전송 인코딩의 경우 케이블에 여분의 꼬임이 있습니다. 첫 번째 청크를 제외한 모든 경우에 대해 청크는 청크 크기를 포함하기 위해 12 바이트의 응답 버퍼를 예약합니다. 즉, 원래의 64KB 쓰기 및 응답 버퍼가있는 예에서 응답 버퍼에 들어있는 실제 데이터 양은 65524 바이트 였으므로 다시 쓰기 버퍼의 일부가 여러 번의 플러시로 유출되었습니다. 이 시나리오의 캡처 된 네트워크 추적을 보면 첫 번째 청크는 64KB이지만 이후 청크는 모두 65524 바이트입니다. 이 경우 outStream.flush는 아무런 차이가 없습니다.

    4KB 버퍼를 사용할 때 outStream.flush가 호출되었을 때만 빠른 속도를 보았습니다. resp.setBufferSize는 버퍼 크기 만 증가시키고 기본 크기는 24KB이므로 resp.setBufferSize (4096)는 no-op입니다. 그러나 이제는 4KB의 데이터를 쓰고 있었는데 예약 된 12 바이트가 있더라도 24KB 버퍼에 맞춰졌고 outStream.flush 호출에 의해 4KB 청크로 플러시됩니다. 그러나 flush에 대한 호출이 제거되면 24가 4의 배수이므로 12 바이트가 다음 청크로 다시 채워지면서 버퍼가 채워집니다.

    Jetty에서 좋은 성과를 얻으려면 다음 중 하나를 수행해야합니다.

    "느린"시나리오의 성능은 여전히 ​​로컬 호스트 또는 매우 빠른 (1Gbps 이상) 네트워크 연결에서만 차이점을 볼 가능성이 높습니다.

    나는 이것을 위해 Hadoop 및 / 또는 Jetty에 대한 문제 보고서를 제출해야한다고 생각합니다.

  2. ==============================

    2.예, 응답 크기를 결정할 수없는 경우 부두가 Transfer-Encoding : Chunked로 기본 설정됩니다.

    예, 응답 크기를 결정할 수없는 경우 부두가 Transfer-Encoding : Chunked로 기본 설정됩니다.

    응답의 크기를 알면 그것이 무엇이 될 것인지. resp.setContentLength (135 * 1000 * 1000 * 1000)를 호출해야합니다. 이 경우에는

    resp.setBufferSize ();

    실제로 resp.setBufferSize를 설정하는 것은 중요하지 않습니다.

    OutputStream을 열기 전에, 그것은 다음 행 앞에 있습니다. OutputStream outStream = resp.getOutputStream (); 너는 전화해야 해. resp.setContentLength (135 * 1000 * 1000 * 1000);

    (위의 줄)

    회전 시키십시오. 그게 효과가 있는지보십시오. 그것들은 이론적으로 내 추측입니다.

  3. ==============================

    3.이것은 순수한 추측이지만, 가비지 수집기 문제 일종의 것 같습니다. JVM을 실행할 때 더 많은 힙을 사용하면 Java 클라이언트의 성능이 향상됩니까?     자바 -Xmx 128m

    이것은 순수한 추측이지만, 가비지 수집기 문제 일종의 것 같습니다. JVM을 실행할 때 더 많은 힙을 사용하면 Java 클라이언트의 성능이 향상됩니까?     자바 -Xmx 128m

    GC 로깅을 켜기 위해 JVM 스위치를 생각해 보지는 않지만, doGet에 들어가는 것처럼 GC가 실행되는지 확인하십시오.

    내 2 센트.

  4. from https://stackoverflow.com/questions/9031311/slow-transfers-in-jetty-with-chunked-transfer-encoding-at-certain-buffer-size by cc-by-sa and MIT license