>Java >java지도 시간 >비동기 및 비차단 아키텍처를 통해 더 나은 성능을 위해 Java 모놀리스 현대화

비동기 및 비차단 아키텍처를 통해 더 나은 성능을 위해 Java 모놀리스 현대화

Susan Sarandon
Susan Sarandon원래의
2024-11-17 06:13:03537검색

Modernizing Java Monoliths for Better Performance with Async and Non-Blocking Architectures

최근 프로젝트에서 저는 Dropwizard로 작성된 노후화된 모놀리식 Java 웹 서비스를 현대화했습니다. 이 서비스는 AWS Lambda 기능을 통해 여러 타사(3P) 종속성을 처리했지만 아키텍처의 동기식 차단 특성으로 인해 성능이 저하되었습니다. 설정에는 20초의 P99 대기 시간이 있어서 서버리스 기능이 완료되기를 기다리는 동안 요청 스레드를 차단했습니다. 이러한 차단으로 인해 스레드 풀 포화가 발생하여 피크 트래픽 중에 요청이 자주 실패하게 되었습니다.

성능 병목 현상 식별

문제의 핵심은 Lambda 함수에 대한 각 요청이 Java 서비스의 요청 스레드를 차지한다는 것입니다. 이러한 3P 기능은 완료하는 데 상당한 시간이 걸리는 경우가 많았기 때문에 이를 처리하는 스레드는 차단된 상태로 유지되어 리소스를 소비하고 확장성을 제한했습니다. 다음은 코드에서 이러한 차단 동작이 어떻게 나타나는지에 대한 예입니다.

// Blocking code example
public String callLambdaService(String payload) {
    String response = externalLambdaService.invoke(payload);
    return response;
}

이 예에서 callLambdaService 메서드는 externalLambdaService.invoke()가 응답을 반환할 때까지 기다립니다. 그동안 다른 작업에서는 스레드를 활용할 수 없습니다.

솔루션: 비동기식, 비차단 패턴으로 마이그레이션

이러한 병목 현상을 해결하기 위해 비동기식 및 비차단 방식을 사용하여 서비스를 다시 설계했습니다. 이 변경에는 내부적으로 EventLoopGroup을 사용하여 요청을 비동기식으로 처리하는 org.asynchttpclient 라이브러리의 AsyncHttpClient를 사용하기 위해 Lambda 함수를 호출하는 HTTP 클라이언트를 사용하는 것이 포함되었습니다.

AsyncHttpClient를 사용하면 풀에서 스레드를 소비하지 않고도 차단 작업을 오프로드하는 데 도움이 되었습니다. 업데이트된 비차단 호출의 예는 다음과 같습니다.

// Non-blocking code example
public CompletableFuture<String> callLambdaServiceAsync(String payload) {
    return CompletableFuture.supplyAsync(() -> {
        return asyncHttpClient.invoke(payload);
    });
}

비동기 호출 연결을 위해 Java의 CompletableFuture 활용

개별 호출을 비차단으로 만드는 것 외에도 CompletableFuture를 사용하여 여러 종속성 호출을 연결했습니다. thenCombine 및 thenApply와 같은 방법을 사용하면 여러 소스에서 데이터를 비동기적으로 가져오고 결합하여 처리량을 크게 높일 수 있습니다.

CompletableFuture<String> future1 = callLambdaServiceAsync(payload1);
CompletableFuture<String> future2 = callLambdaServiceAsync(payload2);

CompletableFuture<String> combinedResult = future1.thenCombine(future2, (result1, result2) -> {
    return processResults(result1, result2);
});

사용자 정의 SafeAsyncResponse 클래스를 통한 유형 안전성 소개

구현 중에 Java의 기본 AsyncResponse 객체에는 유형 안전성이 부족하여 임의의 Java 객체가 전달될 수 있다는 사실을 발견했습니다. 이 문제를 해결하기 위해 지정된 응답 유형만 반환될 수 있도록 보장하는 제네릭을 사용하여 SafeAsyncResponse 클래스를 생성하여 유지 관리성을 높이고 런타임 오류 위험을 줄였습니다. 이 클래스는 응답이 두 번 이상 작성된 경우에도 오류를 기록합니다.

// Blocking code example
public String callLambdaService(String payload) {
    String response = externalLambdaService.invoke(payload);
    return response;
}

SafeAsyncResponse의 샘플 사용

// Non-blocking code example
public CompletableFuture<String> callLambdaServiceAsync(String payload) {
    return CompletableFuture.supplyAsync(() -> {
        return asyncHttpClient.invoke(payload);
    });
}

테스트 및 성능 향상

이러한 변경 사항의 효과를 확인하기 위해 가상 스레드를 사용하여 단일 시스템의 최대 처리량을 시뮬레이션하는 로드 테스트를 작성했습니다. 저는 다양한 수준의 서버리스 함수 실행 시간(1~20초 범위)을 생성했으며 새로운 비동기 비차단 구현이 실행 시간이 짧을수록 처리량이 8배, 실행 시간이 길수록 약 4배 증가한다는 사실을 발견했습니다.

이러한 로드 테스트를 설정할 때 클라이언트 수준 연결 제한을 조정하여 처리량을 극대화했습니다. 이는 비동기식 시스템에서 병목 현상을 방지하는 데 필수적입니다.

HTTP 클라이언트에서 숨겨진 버그 발견

이러한 스트레스 테스트를 실행하는 동안 저는 맞춤형 HTTP 클라이언트에서 숨겨진 버그를 발견했습니다. 클라이언트는 연결 시간 초과가 Integer.MAX_VALUE로 설정된 세마포어를 사용했습니다. 즉, 클라이언트에 사용 가능한 연결이 부족하면 스레드가 무기한 차단됩니다. 고부하 시나리오에서 잠재적인 교착 상태를 방지하려면 이 버그를 해결하는 것이 중요했습니다.

가상 스레드와 기존 비동기 코드 사이의 선택

왜 우리가 상당한 리소스 비용 없이 스레드를 차단할 수 있도록 하여 비동기 코드의 필요성을 줄일 수 있는 가상 스레드로 전환하지 않았는지 궁금해하실 수도 있습니다. 그러나 현재 가상 스레드에는 제한 사항이 있습니다. 즉, 동기화된 작업 중에 고정됩니다. 이는 가상 스레드가 동기화된 블록에 들어갈 때 마운트 해제할 수 없으며 작업이 완료될 때까지 OS 리소스를 차단할 수 있음을 의미합니다.

예:

CompletableFuture<String> future1 = callLambdaServiceAsync(payload1);
CompletableFuture<String> future2 = callLambdaServiceAsync(payload2);

CompletableFuture<String> combinedResult = future1.thenCombine(future2, (result1, result2) -> {
    return processResults(result1, result2);
});

이 코드에서 사용 가능한 데이터가 없어 읽기가 차단되면 가상 스레드가 OS 스레드에 고정되어 OS 스레드가 마운트 해제되거나 차단되는 것을 방지합니다.

다행히도 JEP 491이 곧 출시됨에 따라 Java 개발자는 플랫폼 스레드를 소모하지 않고 동기화된 코드의 차단 작업을 보다 효율적으로 처리할 수 있는 가상 스레드의 향상된 동작을 기대할 수 있습니다.

결론

비동기 비차단 아키텍처로 서비스를 리팩터링하여 성능이 크게 향상되었습니다. AsyncHttpClient를 구현하고, 유형 안전성을 위해 SafeAsyncResponse를 도입하고, 로드 테스트를 수행함으로써 Java 서비스를 최적화하고 처리량을 크게 향상시킬 수 있었습니다. 이 프로젝트는 모놀리식 애플리케이션을 현대화하는 귀중한 작업이었으며 확장성을 위한 적절한 비동기 방식의 중요성을 보여주었습니다.

Java가 발전함에 따라 미래에는 가상 스레드를 더욱 효과적으로 활용할 수 있지만 현재로서는 비동기 및 비차단 아키텍처가 대기 시간이 긴 타사 종속 서비스의 성능 최적화를 위한 필수 접근 방식으로 남아 있습니다.

위 내용은 비동기 및 비차단 아키텍처를 통해 더 나은 성능을 위해 Java 모놀리스 현대화의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.