-
Notifications
You must be signed in to change notification settings - Fork 0
[Study] Java Http 다운로드 라이브러리 조사 및 다운로드 방식에 따른 성능테스트
참고 : https://www.wiremock.io/post/java-http-client-comparison
우선 서드파티 라이브러리를 제외하고, 자바가 제공해주는 HTTP 클라이언트를 비교 분석하고자하였다.
JDK 1.1 부터 존재한 클라이언트로 아직도 많이 사용되는 클라이언트라고 볼 수 있다.
- URL 객체의
openConnection()
메서드를 통해서 커넥션을 맺는다.- 내부적으로는 기본적으로
URLStreamHandler
를 사용하여 HTTP 혹은 HTTPS에 따라서 invoke가 수행된다.
- 내부적으로는 기본적으로
// java.net.URL
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}
// sun.net.www.protocol.https.Handler (HTTP일 경우 다른 핸들러를 사용한다)
protected java.net.URLConnection openConnection(URL u, Proxy p)
throws IOException {
return new HttpsURLConnectionImpl(u, p, this); // 이 메서드를 호출하여 java.net.URLConnection을 리턴한다.
}
- 이후 전달받은
HttpURLConnection
에서 요청된 URL의 데이터를 읽어오기 위해서getInputStream()
메서드를 호출한다.- 이때, chunk-lenght, fixed-length 등이 존재한다면 스트리밍 모드로 동작한다.
// sun.net.www.protocol.http.HttpURLConnection
@Override
public synchronized InputStream getInputStream() throws IOException {
connecting = true;
SocketPermission p = URLtoSocketPermission(this.url);
if (p != null) {
try {
return AccessController.doPrivilegedWithCombiner(
new PrivilegedExceptionAction<InputStream>() {
public InputStream run() throws IOException {
return getInputStream0();
}
}, null, p
);
} catch (PrivilegedActionException e) {
throw (IOException) e.getException();
}
} else {
return getInputStream0(); // 별도의 SocketPermission이 부여안되어있다면 이 메서드를 통해서 스트림을 가져온다.
}
}
private synchronized InputStream getInputStream0() throws IOException {
... (중략) ...
}
이렇게 커넥션을 가져오면서 처리된다고 볼 수 있다.
- 더미데이터 파일 크기 : 800MB
- 네트워크 지연을 최소화하기 위해서 CloudFront (No-Cache) 요청으로 수행
- 병렬 처리 알고리즘
- 메인쓰레드에서
InputStream
을 100메가씩 버퍼로 읽어들임. - 후에
executor.excute()
메서드를 통해서 쓰레드에 던져서 병렬 처리하도록 작업 (쓰레드 내부에서는 50메가의 버퍼를 사용)
- 메인쓰레드에서
테스트 변인 | 설정 값 | 비고 |
---|---|---|
GC | G1GC | 가비지컬렉터 알고리즘 |
XMS | 2g | 최소 힙 영역 |
XMX | 2g | 최대 힙 영역 |
CPU Core | 10 | M1 Macbook Pro(H/W) |
RAM | 32G | M1 Macbook Pro(H/W) |
쓰레드 수 | 평균 다운로드 시간(S) | 평균 다운로드 시간(MS) |
---|---|---|
3 | 약 52초 | 52874 |
4 | 약 40초 | 40764.66666667 |
6 | 약 32초 | 32649.66666667 |
8 | 약 31초 | 31512.33333333 |
10 | 약 29초 | 29888.33333333 |
CPU 코어 수와 같은 쓰레드 수인 10개를 활용할 때 제일 약 29초 로 제일 빨랐던 점을 확인할 수 있다.
JDK11 이후부터 추가된 클라이언트로 내부적으로 셀렉터로 구현되어있다.
즉, 이 클라이언트는 NIO 방식으로 구현되어있다고 보면된다.
Selector
를 사용함으로써 멀티쓰레드가 아니여도 쓰레드 1개만으로도 논블로킹 처리를 하면서 빠르게 설정이 가능하다.
- 동기 다운로드 시
버퍼 크기 | 평균 다운로드 시간(S) | 평균 다운로드 시간(MS) |
---|---|---|
0 | 약 30초 | 30994 |
10MB | 약 25초 | 25263.33333333 |
50MB | 약 17초 | 17434.66666667 |
100MB | 약 31초 | 31605.33333333 |
왜? 버퍼 크기를 늘렸음에도 평균 다운로드 시간을 줄어들게 되었는가? 이 부분은 고민할 껀덕지가 많으나 현재 아래와 같은 가설을 세우게 되었다.
네트워크 속도의 영향으로 큰 버퍼의 사이즈만큼 읽기를 위해서 대기하는 것보다 작은 크기의 버퍼를 채우는 속도가 빠르고 그만큼 I/O가 이뤄질 수 있어서지않을까?
이 부분에 대한 가설은 나중에 검증해보도록한다.
- 비동기 다운로드 시
버퍼 크기 | 평균 다운로드 시간(S) | 평균 다운로드 시간(MS) |
---|---|---|
10MB | 약 15초 | 15014 |
50MB | 약 14초 | 14911.33333333 |
100MB | 약 15초 | 15004.66666667 |
HttpClent.sendAsync()
메서드를 활용하여 테스트를 하였을 경우에는 버퍼 크기에 유의미한 차이를 나타나지는 않았다. (버퍼 크기가 0일때 제외하고)
유의미한 점은 Async
로 처리하는 방식이 Sync
로 처리하는 부분보다 빠르다는 점이다.
아마도 Non-Blocking
이기 때문에 좀 더 빠르다고 추측을 하고 있다.