다음을 통해 공유


Azure Service Bus 문제 해결

이 문서에서는 오류 조사 기술, 동시성, Azure Service Bus Java 클라이언트 라이브러리의 자격 증명 유형에 대한 일반적인 오류 및 이러한 오류를 해결하기 위한 완화 단계를 설명합니다.

로깅 사용 및 구성

Java용 Azure SDK는 애플리케이션 오류 문제를 해결하고 해결을 신속하게 하는 데 도움이 되는 일관된 로깅 스토리를 제공합니다. 생성된 로그는 근본 문제를 찾는 데 도움이 되도록 터미널 상태에 도달하기 전에 애플리케이션의 흐름을 캡처합니다. 로깅에 대한 지침은 Java용 Azure SDK에서 로깅 구성문제 해결 개요를 참조하세요.

로깅을 사용하도록 설정하는 것 외에도 로그 수준을 VERBOSE 또는 DEBUG 설정하면 라이브러리의 상태에 대한 인사이트를 제공합니다. 다음 섹션에서는 상세 로깅을 사용할 때 과도한 메시지를 줄이기 위한 log4j2 및 logback의 샘플 구성을 보여 줍니다.

Log4J 2 구성

다음 단계를 사용하여 Log4J 2를 구성합니다.

  1. 로깅 샘플 pom.xml의 종속성을 사용하여 "Log4j2에 필요한 종속성" 섹션에 pom.xml 자신의 종속성을 추가합니다.
  2. src/main/resources 폴더에 log4j2.xml 추가하세요.

로그백 구성

다음 단계를 사용하여 로그백을 구성합니다.

  1. 로깅 샘플 pom.xml의 "로그백에 필요한 종속성" 섹션에 있는 종속성을 사용하여, 귀하의 pom.xml에 종속성을 추가합니다.
  2. logback.xmlsrc/main/resources 폴더에 추가합니다.

AMQP 전송 로깅을 사용

클라이언트 로깅을 사용하도록 설정해도 문제를 진단하기에 충분하지 않은 경우, 기본 AMQP 라이브러리인 Qpid Proton-J ()의 파일에 대한 로깅을 사용하도록 설정할 수 있습니다. Qpid Proton-J java.util.logging사용합니다. 다음 섹션에 표시된 내용이 포함된 구성 파일을 만들어 로깅을 사용하도록 설정할 수 있습니다. 또는 proton.trace.level=ALL 및 원하는 구성 옵션을 java.util.logging.Handler 구현에 대해 설정합니다. 구현 클래스 및 해당 옵션은 Java 8 SDK 설명서의 Package java.util.logging 참조하세요.

AMQP 전송 프레임을 추적하려면 PN_TRACE_FRM=1 환경 변수를 설정합니다.

예시 logging.properties 파일

다음 구성 파일은 Proton-J에서 생성된 TRACE 수준 출력을 파일 proton-trace.log에 기록합니다.

handlers=java.util.logging.FileHandler
.level=OFF
proton.trace.level=ALL
java.util.logging.FileHandler.level=ALL
java.util.logging.FileHandler.pattern=proton-trace.log
java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format=[%1$tF %1$tr] %3$s %4$s: %5$s %n

로그 기록 줄이기

로깅을 줄이는 한 가지 방법은 로그의 자세한 정도를 변경하는 것입니다. 또 다른 방법은 com.azure.messaging.servicebus 또는 com.azure.core.amqp같은 로거 이름 패키지에서 로그를 제외하는 필터를 추가하는 것입니다. 예를 들어 log4J 2 구성 및 로그백 구성 섹션의 XML 파일을 참조하세요.

버그를 제출할 때 다음 패키지의 클래스에서 보내는 로그 메시지는 흥미롭습니다.

  • com.azure.core.amqp.implementation
  • com.azure.core.amqp.implementation.handler
    • onDelivery에서 ReceiveLinkHandler 메시지를 무시할 수 있다는 것이 예외입니다.
  • com.azure.messaging.servicebus.implementation

ServiceBusProcessorClient의 동시성

ServiceBusProcessorClient 통해 애플리케이션은 메시지 처리기에 대한 호출 수를 동시에 구성할 수 있습니다. 이 구성을 사용하면 여러 메시지를 병렬로 처리할 수 있습니다. 비 세션 엔터티의 메시지를 사용하는 ServiceBusProcessorClient 애플리케이션은 maxConcurrentCalls API를 사용하여 원하는 동시성을 구성할 수 있습니다. 세션이 활성화된 엔터티의 경우 원하는 동시성은 maxConcurrentSessionsmaxConcurrentCalls번입니다.

애플리케이션이 구성된 동시성보다 메시지 처리기에 대한 동시 호출이 적은 것을 관찰하는 경우 스레드 풀의 크기가 적절하게 조정되지 않았기 때문일 수 있습니다.

ServiceBusProcessorClient Reactor 전역 boundedElastic 스레드 풀의 디먼 스레드를 사용하여 메시지 처리기를 호출합니다. 이 풀의 최대 동시 스레드 수는 한도로 제한됩니다. 기본적으로 이 한도는 사용 가능한 CPU 코어 수의 10배입니다. ServiceBusProcessorClient 애플리케이션의 원하는 동시성(maxConcurrentCalls 또는 maxConcurrentSessionsmaxConcurrentCalls)을 효과적으로 지원하려면 원하는 동시성보다 높은 boundedElastic 풀 캡 값이 있어야 합니다. 시스템 속성 reactor.schedulers.defaultBoundedElasticSize설정하여 기본 상한을 재정의할 수 있습니다.

개별적인 상황에 따라 스레드 풀 및 CPU 할당을 조정해야 합니다. 그러나 풀 캡을 재정의하는 경우 시작점으로 동시 스레드를 CPU 코어당 약 20-30으로 제한합니다. ServiceBusProcessorClient 인스턴스당 원하는 동시성을 약 20-30으로 제한하는 것이 좋습니다. 특정 사용 사례를 프로파일 및 측정하고 그에 따라 동시성 측면을 조정합니다. 부하가 높은 시나리오의 경우 각 인스턴스가 새 ServiceBusProcessorClient 인스턴스에서 빌드되는 여러 ServiceBusClientBuilder 인스턴스를 실행하는 것이 좋습니다. 또한 컨테이너 또는 VM과 같은 전용 호스트에서 각 ServiceBusProcessorClient 실행하여 한 호스트의 가동 중지 시간이 전체 메시지 처리에 영향을 주지 않도록 하는 것이 좋습니다.

CPU 코어가 적은 호스트에서 풀 캡에 대한 높은 값을 설정하면 부정적인 영향을 미칠 수 있습니다. CPU 리소스가 부족하거나 CPU 수에 비해 스레드가 너무 많은 풀의 경우 나타날 수 있는 징후로는 빈번한 시간 초과, 잠금 손실, 교착 상태 또는 낮은 처리량 등이 있습니다. 컨테이너에서 Java 애플리케이션을 실행하는 경우 두 개 이상의 vCPU 코어를 사용하는 것이 좋습니다. 컨테이너화된 환경에서 Java 애플리케이션을 실행할 때 1개 미만의 vCPU 코어를 선택하지 않는 것이 좋습니다. 자원 조달에 대한 자세한 권장 사항은 Java 애플리케이션을 컨테이너화하려면 을 참조하세요.

연결 공유 병목 현상

공유 ServiceBusClientBuilder 인스턴스에서 만든 모든 클라이언트는 Service Bus 네임스페이스에 대한 동일한 연결을 공유합니다.

공유 연결을 사용하면 한 연결에서 클라이언트 간에 멀티플렉싱 작업을 수행할 수 있지만 클라이언트가 많거나 클라이언트가 함께 높은 부하를 생성하는 경우 공유가 병목 상태가 될 수도 있습니다. 각 연결에는 연결된 I/O 스레드가 있습니다. 연결을 공유할 때 클라이언트는 이 공유 I/O 스레드의 작업 큐에 작업을 배치하고 각 클라이언트의 진행률은 큐에서 해당 작업이 적시에 완료되는 경우에 따라 달라집니다. I/O 스레드는 큐에 저장된 작업을 직렬로 처리합니다. 즉, 공유 연결의 I/O 스레드 작업 큐가 처리해야 할 보류 중인 작업이 많은 경우 증상은 CPU가 낮은 것과 유사합니다. 이 조건은 동시성에 대한 이전 섹션에서 설명되어 있습니다. 예를 들어, 클라이언트 중단 및 시간 초과, 잠금 손실 또는 복구 경로에 속도 저하 등이 있습니다.

Service Bus SDK는 연결 I/O 스레드에 reactor-executor-* 명명 패턴을 사용합니다. 애플리케이션에서 공유 연결 병목 현상이 발생하면 I/O 스레드의 CPU 사용량에 반영될 수 있습니다. 또한 힙 덤프나 라이브 메모리에서 ReactorDispatcher$workQueue 객체는 I/O 스레드의 작업 큐입니다. 병목 상태 기간 동안 메모리 스냅샷의 긴 작업 큐는 공유 I/O 스레드가 보류 중인 작업으로 오버로드되었음을 나타낼 수 있습니다.

따라서 Service Bus 엔드포인트에 대한 애플리케이션 로드가 전체 수신 메시지 수 또는 페이로드 크기 측면에서 상당히 높은 경우 빌드하는 각 클라이언트에 대해 별도의 작성기 인스턴스를 사용해야 합니다. 예를 들어 큐 또는 토픽과 같은 각 엔터티에 대해 새 ServiceBusClientBuilder 만들고 해당 엔터티에서 클라이언트를 빌드할 수 있습니다. 특정 엔터티에 대한 부하가 매우 높은 경우 해당 엔터티에 대한 여러 클라이언트 인스턴스를 만들거나 여러 호스트(예: 컨테이너 또는 VM)에서 클라이언트를 실행하여 부하를 분산하는 것이 좋습니다.

Application Gateway 사용자 지정 엔드포인트를 사용할 때 클라이언트가 중지됩니다.

사용자 지정 엔드포인트 주소는 Service Bus로 확인 가능하거나 Service Bus로 트래픽을 라우팅하도록 구성된 애플리케이션 제공 HTTPS 엔드포인트 주소를 나타냅니다. Azure Application Gateway를 사용하면 트래픽을 Service Bus로 전달하는 HTTPS 프런트 엔드를 쉽게 만들 수 있습니다. Application Gateway 프런트 엔드 IP 주소를 Service Bus에 연결하는 사용자 지정 엔드포인트로 사용하도록 애플리케이션에 대해 Service Bus SDK를 구성할 수 있습니다.

Application Gateway는 다양한 TLS 프로토콜 버전을 지원하는 여러 보안 정책을 제공합니다. TLSv1.2를 최소 버전으로 적용하는 미리 정의된 정책이 있으며, TLSv1.0을 최소 버전으로 사용하는 이전 정책도 있습니다. HTTPS 프런트 엔드에는 TLS 정책이 적용됩니다.

현재 Service Bus SDK는 TLSv1.0을 최소 버전으로 사용하는 Application Gateway 프런트 엔드에서 특정 원격 TCP 종료를 인식하지 못합니다. 예를 들어 프런트 엔드가 TCP FIN, ACK 패킷을 전송하여 속성이 업데이트될 때 연결을 닫는 경우 SDK는 이를 검색할 수 없으므로 다시 연결되지 않으며 클라이언트는 더 이상 메시지를 보내거나 받을 수 없습니다. 이러한 중지는 TLSv1.0을 최소 버전으로 사용하는 경우에만 발생합니다. 완화하려면 TLSv1.2 이상의 보안 정책을 Application Gateway 프런트 엔드의 최소 버전으로 사용합니다.

모든 Azure 서비스에서 TLSv1.0 및 1.1에 대한 지원은 이미 2024년 10월 31일까지 종료될 발표되었으므로 TLSv1.2로 전환하는 것이 좋습니다.

메시지 또는 세션 잠금이 손실됨

Service Bus 큐 또는 토픽 구독에는 리소스 수준에서 잠금 기간이 설정됩니다. 수신자 클라이언트가 리소스에서 메시지를 가져오면 Service Bus Broker는 메시지에 초기 잠금을 적용합니다. 초기 잠금은 리소스 수준에서 설정된 잠금 기간 동안 지속됩니다. 메시지 잠금이 만료되기 전에 갱신되지 않으면 Service Bus 브로커는 메시지를 해제하여 다른 수신기에서 사용할 수 있도록 합니다. 애플리케이션이 잠금 만료 후 메시지를 완료하거나 중단하려고 하면 오류 com.azure.messaging.servicebus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queueAPI 호출이 실패합니다.

Service Bus 클라이언트는 만료되기 전에 매번 메시지 잠금을 지속적으로 갱신하는 백그라운드 잠금 갱신 작업 실행을 지원합니다. 기본적으로 잠금 갱신 작업은 5분 동안 실행됩니다. ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration)사용하여 잠금 갱신 기간을 조정할 수 있습니다. Duration.ZERO 값을 전달하면 잠금 갱신 작업이 비활성화됩니다.

다음 목록에서는 잠금 손실 오류로 이어질 수 있는 일부 사용 패턴 또는 호스트 환경에 대해 설명합니다.

  • 잠금 갱신 작업이 비활성화되고 애플리케이션의 메시지 처리 시간이 리소스 수준에서 설정된 잠금 기간을 초과합니다.

  • 애플리케이션의 메시지 처리 시간이 구성된 잠금 갱신 작업 기간을 초과합니다. 잠금 갱신 기간이 명시적으로 설정되지 않은 경우 기본값은 5분입니다.

  • 애플리케이션은 ServiceBusReceiverClientBuilder.prefetchCount(prefetch)사용하여 프리페치 값을 양의 정수로 설정하여 프리페치 기능을 설정했습니다. 프리페치 기능을 사용하도록 설정하면 클라이언트는 Service Bus 엔터티(큐 또는 토픽)에서 프리페치와 동일한 메시지 수를 검색하고 메모리 내 프리페치 버퍼에 저장합니다. 메시지는 애플리케이션에 수신될 때까지 프리페치 버퍼에 유지됩니다. 클라이언트는 프리페치 버퍼에 있는 동안 메시지의 잠금을 확장하지 않습니다. 애플리케이션 처리 시간이 너무 오래 걸려서 프리페치 버퍼에 있는 동안 메시지 잠금이 만료되면, 애플리케이션이 만료된 잠금 상태로 메시지를 가져올 수 있습니다. 자세한 내용은 프리페치가 기본 옵션이 아닌 이유는 무엇인가요?

  • 호스트 환경에는 잠금 갱신 작업이 시간에 따라 잠금을 갱신하지 못하도록 하는 일시적인 네트워크 오류 또는 중단과 같은 네트워크 문제가 가끔 있습니다.

  • 호스트 환경에 충분한 CPU가 없거나 일시적으로 CPU 주기가 부족하여 잠금 갱신 작업이 정시에 실행되는 것을 지연합니다.

  • 호스트 시스템 시간이 정확하지 않습니다(예: 시계가 왜곡됨) 잠금 갱신 작업을 지연시키고 정시에 실행되지 않도록 합니다.

  • 연결 I/O 스레드가 오버로드되어 시간에 잠금 갱신 네트워크 호출을 실행하는 기능에 영향을 미칩니다. 다음 두 가지 시나리오로 인해 이 문제가 발생할 수 있습니다.

    • 애플리케이션이 동일한 연결을 공유하는 수신기 클라이언트가 너무 많이 실행되고 있습니다. 자세한 내용은 연결 공유 병목 현상 항목을 참조하세요.
    • 애플리케이션은 ServiceBusReceiverClient.receiveMessages 또는 ServiceBusProcessorClient을 큰 maxMessages 또는 maxConcurrentCalls 값을 갖도록 설정했습니다. 자세한 내용은 동시성에 대한 ServiceBusProcessorClient 섹션을 참조하세요.
  • 잠금 손실 오류의 가능성을 높이는 일반적인 애플리케이션 패턴에는 장기 실행 잠금 갱신 작업(예: 몇 시간 동안 지속된 작업)을 예약하는 작업이 포함됩니다. 앞에서 설명한 것처럼 Service Bus 클라이언트를 제어하지 않는 다양한 요인은 성공적인 잠금 갱신을 방해할 수 있으므로 애플리케이션 디자인은 연장된 기간 동안 보장된 갱신을 가정하지 않아야 합니다. 장기 실행 작업을 다시 처리할 필요가 없도록 하려면 작업을 더 작은 단위로 나누거나 동일한 결과를 보장하는 검사점 논리를 구현하는 것이 좋습니다.

클라이언트에서 잠금 갱신 작업의 수는 maxMessages 또는 maxConcurrentCalls에 설정된 ServiceBusProcessorClient 또는 ServiceBusReceiverClient.receiveMessages 매개 변수 값과 동일합니다. 여러 네트워크 호출을 수행하는 잠금 갱신 작업의 수가 많으면 Service Bus 네임스페이스 제한에 부정적인 영향을 미칠 수도 있습니다.

호스트에 충분한 리소스가 없는 경우 잠금 갱신 작업이 몇 개만 실행되더라도 잠금이 손실될 수 있습니다. 컨테이너에서 Java 애플리케이션을 실행하는 경우 두 개 이상의 vCPU 코어를 사용하는 것이 좋습니다. 컨테이너화된 환경에서 Java 애플리케이션을 실행하는 경우 1개 미만의 vCPU 코어를 선택하지 않는 것이 좋습니다. 자원 조달에 대한 자세한 권장 사항은 Java 애플리케이션을 컨테이너화하려면 을 참조하세요.

잠금에 대한 설명은 세션이 활성화된 Service Bus 큐나 토픽 구독에도 해당됩니다. 수신기 클라이언트가 자원의 세션에 연결되면 브로커가 세션에 초기 잠금 설정을 적용합니다. 세션에 대한 잠금을 유지하려면 클라이언트의 잠금 갱신 작업이 만료되기 전에 세션 잠금을 계속 갱신해야 합니다. 세션 사용 리소스의 경우 기본 파티션은 때때로 Service Bus 노드 간에 부하 분산을 달성하기 위해 이동합니다(예: 부하를 공유하기 위해 새 노드가 추가될 때). 이 경우 세션 잠금이 손실될 수 있습니다. 세션 잠금이 손실된 후 애플리케이션이 메시지를 완료하거나 중단하려고 하면 오류 com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiverAPI 호출이 실패합니다.

다음 단계

이 문서의 문제 해결 지침이 Java용 Azure SDK 클라이언트 라이브러리를 사용할 때 문제를 해결하는 데 도움이 되지 않는 경우 Java GitHub 리포지토리Azure SDK에 문제를 것이 좋습니다.