이 문서에서는 Spring JMS를 사용할 때 알려진 문제 및 일반적인 오류를 해결하는 방법을 설명합니다. 또한 이 문서에서는 spring-cloud-azure-starter-servicebus-jms대한 몇 가지 질문과 대답을 제공합니다.
연결 문제
복구할 수 없는 오류로 인해 MessageProducer가 닫혔습니다.
문제 설명
JmsTemplate 사용하여 메시지를 보내는 경우 10~15분 사이의 유휴 간격 동안 JmsTemplate 사용할 수 없게 됩니다. 해당 간격으로 메시지를 보내면 다음 예제 출력에 표시된 예외를 가져올 수 있습니다.
2022-11-06 11:12:05.762 INFO 25944 --- [ scheduling-1] c.e.demo.ServiceBusJMSMessageProducer : Sending message: 2022-11-06T11:12:05.762072 message 1
2022-11-06 11:12:05.772 ERROR 25944 --- [ scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in scheduled task
org.springframework.jms.IllegalStateException: The MessageProducer was closed due to an unrecoverable error.; nested exception is javax.jms.IllegalStateException: The MessageProducer was closed due to an unrecoverable error.
at org.springframework.jms.support.JmsUtils.convertJmsAccessException(JmsUtils.java:274) ~[spring-jms-5.3.23.jar:5.3.23]
...
Caused by: org.apache.qpid.jms.provider.ProviderException: The link 'G0:36906660:qpid-jms:sender:azure:5caf3ef4-9602-413c-964d-cf1292d6e1f5:1:1:1:t4' is force detached. Code: publisher(link376). Details: AmqpMessagePublisher.IdleTimerExpired: Idle timeout: 00:10:00. [condition = amqp:link:detach-forced]
at org.apache.qpid.jms.provider.amqp.AmqpSupport.convertToNonFatalException(AmqpSupport.java:181) ~[qpid-jms-client-0.53.0.jar:na]
...
원인 분석
AMQP 연결 및 링크가 활성 상태이지만 호출(예: 통화 보내기 또는 받기)이 10분 동안 링크를 사용하여 수행되지 않는 경우 Azure Service Bus 예외가 발생합니다. 이 경우 링크가 닫힙니다. 또한 활동(유휴)이 없고 5분 후에 새 링크가 만들어지지 않아 연결의 모든 링크가 닫혀 있으면 연결이 닫힙니다.
Service Bus JMS 스타터의 경우 세션, 생산자 및 소비자를 캐시하는 CachingConnectionFactory 기본적으로 사용됩니다.
JmsProducer 10분 이상 유휴 상태이지만 15분 미만이면 캐시된 생산자가 차지하는 링크가 닫힙니다. 이 간격 동안에는 메시지를 보낼 수 없습니다. 그런 다음, 5분 후에 유휴 상태가 되면 전체 연결이 닫힙니다. 따라서 15분 유휴 간격 이후의 모든 전송 작업으로 인해 CachingConnectionFactory 새 연결을 만듭니다. 전송 작업은 15분 후에 사용할 수 있게 됩니다.
해결 방법
현재 시작은 JmsPoolConnectionFactory풀링, Connection및 Session풀링된 인스턴스의 수명 주기를 관리하는 MessageProducer적용하여 링크 분리 문제에 대한 해결 방법을 제공합니다. 이 해결 방법을 사용하면 사용할 수 없게 된 후 생산자가 제거되므로 모든 전송 작업이 활성 생산자에서 수행됩니다.
이 해결 방법을 사용하려면 다음 구성을 추가합니다.
spring:
jms:
servicebus:
pool:
enabled: true
max-connections: ${your-expected-max-connection-value}
spring.jms.servicebus.idle-timeout 사용
유휴 시간 제한 속성은 AMQP 연결의 유휴 시간 제한 구성합니다. AMQP 사양은 다음 설명을 제공합니다.
연결에는 유휴 시간 제한 임계값이 적용됩니다. 임계값을 초과한 후 프레임이 수신되지 않는 경우 로컬 피어에 의해 시간 제한이 트리거됩니다. 유휴 시간 제한은 밀리초 단위로 측정되며 마지막 프레임이 수신된 시간부터 시작됩니다. 임계값을 초과하는 경우 피어는 이유를 설명하는 오류와 함께 닫기 프레임을 사용하여 연결을 정상적으로 닫아야 합니다. 원격 피어가 임계값 내에서 정상적으로 응답하지 않으면 피어가 TCP 소켓을 닫을 수 있습니다.
JMS 클라이언트의 경우 이 속성을 구성할 때 서버 쪽에서 메시지가 배달되지 않을 때 연결 상태를 유지하기 위해 서버가 빈 프레임을 보낼 것으로 예상하는 기간을 제어합니다. 이 속성은 원격 피어의 동작을 제어하며 각 피어는 격리된 고유한 값을 가질 수 있습니다.
JmsTemplate 문제
예약된 메시지
Azure Service Bus는 지연된 메시지 처리를 지원합니다. 자세한 내용은 메시지 시퀀싱 및 타임스탬프예약된 메시지 섹션을 참조하세요. JMS의 경우 메시지를 예약하려면 메시지 주석 헤더 ScheduledEnqueueTimeUtc사용하여 x-opt-scheduled-enqueue-time 속성을 설정합니다.
JmsListener 문제
서버에 메시지가 없더라도 Service Bus에 너무 많은 요청이 전송됨
문제 설명
@JmsListener API를 사용하는 경우 수신할 메시지가 서버에 없더라도 Azure Portal에서 큐 또는 토픽으로 전송되는 들어오는 요청에 대한 지속적인 값이 있음을 확인할 수 있습니다.
원인 분석
@JmsListener 반복된 폴링 시도를 위해 빌드된 폴링 수신기입니다.
수신기는 진행 중인 폴링 루프에 있습니다. 각 루프는 JMS MessageConsumer.receive() 메서드를 호출하여 로컬 소비자가 사용할 메시지를 폴링합니다. 기본적으로 각 폴링 작업에 대해 로컬 소비자는 메시지 브로커에 끌어오기 요청을 보내 메시지를 요청한 다음 특정 기간 동안 차단합니다. 구체적인 폴링 프로세스는 receiveTimeout, prefetchSize, receiveLocalOnly 또는 receiveNoWaitLocalOnly포함한 여러 속성에 의해 결정됩니다.
receiveNoWaitLocalOnly 메서드는 receiveTimeout 음수 값으로 설정한 경우에만 사용됩니다.
애플리케이션에서 이 문제가 발생하면 다음 구성 설정을 확인합니다.
프리페치 정책이 기본 옵션인 0인지 확인합니다. 0 프리페치는 각 설문 조사에 대해 Service Bus에 끌어오기 요청을 보내는 끌어오기 소비자를 의미합니다.
0이 아닌 프리페치를 구성한 경우
receiveLocalOnly또는receiveNoWaitLocalOnly속성이 기본 옵션인false설정되었는지 확인합니다. 여기서false값은 로컬 소비자만 폴링하지 않기 때문에 여전히 끌어오기 요청을 서버로 보냅니다.receiveTimeout구성은 각 끌어오기 요청에 대해 차단되는 기간을 결정하므로 서버로 보내는 끌어오기 요청의 빈도에 영향을 줄 수 있습니다. 기본값은 1초입니다.
전체 분석은 GitHub 문제설명을 참조하세요.
솔루션
다음 섹션에서는 이 문제를 처리하기 위한 두 가지 솔루션에 대해 설명합니다.
솔루션 1. 소비자 푸시 및 로컬 확인 전용으로 변경
모드를 push변경하면 소비자는 브로커에서 메시지를 가져오지 않고 대상 링크 크레딧을 유지하는 비동기 알림 소비자가 됩니다. 금액은 프리페치 속성에 의해 결정됩니다. Service Bus(보낸 사람)가 메시지를 푸시하면 보낸 사람의 링크 크레딧이 감소하고 발신자의 링크 크레딧이 임계값 아래로 떨어지면 클라이언트(수신자)는 서버에 요청을 보내 발신자의 링크 크레딧을 원하는 대상 금액으로 다시 늘입니다.
이 솔루션을 수행하려면 다음 구성을 추가합니다.
먼저 prefetch 번호를 0이 아닌 숫자로 구성하여 소비자를 끌어오지 않는 것으로 구성합니다. 다음 표에서는 여러 프리페치 속성을 보여 줍니다. 각 속성은 서로 다른 Service Bus 엔터티를 제어합니다. 사례에 적용되는 속성을 설정합니다.
| 재산 | 묘사 |
|---|---|
spring.jms.servicebus.prefetch.all |
이 Service Bus 네임스페이스의 프리페치 옵션에 대한 대체 값 |
spring.jms.servicebus.prefetch.queue-prefetch |
큐의 프리페치 번호입니다. |
spring.jms.servicebus.prefetch.queue-browser-prefetch |
큐 브라우저의 프리페치 번호입니다. |
spring.jms.servicebus.prefetch.topic-prefetch |
토픽의 프리페치 번호입니다. |
spring.jms.servicebus.prefetch.durable-topic-prefetch |
지속성 토픽의 프리페치 번호입니다. |
둘째, 다음 예제와 같이 팩터리 사용자 지정자에 대한 구성 클래스를 추가하여 non-local-check 구성합니다.
@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {
@Bean
ServiceBusJmsConnectionFactoryCustomizer customizer() {
return factory -> {
factory.setReceiveLocalOnly(true);
// Configure the below ReceiveNoWaitLocalOnly instead if you have specified the property
// spring.jms.listener.receive-timeout with negative value. Otherwise, configure the above `ReceiveLocalOnly`.
//factory.setReceiveNoWaitLocalOnly(true);
};
}
}
프리페치 값은 메시지가 소비자의 로컬 버퍼로 디스패치되는 속도에 영향을 줄 수 있습니다. 소비 성능 및 메시지 볼륨에 따라 값을 조정해야 합니다. 적절한 값은 소비 프로세스의 속도를 높일 수 있지만 너무 큰 값으로 인해 로컬로 버퍼링된 메시지가 오래되고 다시 디스패치될 수 있습니다. 각 메시지를 처리하는 데 시간이 오래 걸리는 낮은 메시지 볼륨의 경우 프리페치를 1로 설정합니다. 이 값은 소비자가 한 번에 하나의 메시지만 처리하도록 합니다.
솔루션 2. 끌어오기 빈도를 줄이기 위해 수신 시간 제한을 늘립니다.
수신 시간 제한 속성은 소비자가 끌어오기 결과를 기다리는 데 걸리는 시간에 대한 전략을 결정합니다. 따라서 시간 제한을 연장하여 끌어오기 빈도를 줄인 다음 끌어오기 모드를 선택할 때 끌어오기 요청 수를 줄일 수 있습니다. 극단적인 경우 메시지가 도착할 때까지 무기한 대기하는 전략을 설정할 수 있습니다. 즉, 소비자가 메시지를 사용한 후에만 끌어오기만 합니다. 이 경우 서버에 메시지가 없으면 대기가 차단됩니다.
이 솔루션을 수행하려면 spring.jms.listener.receive-timeout 속성을 구성합니다. 이 속성은 java.time.Duration 형식이며 기본값은 1초입니다. 다음 목록에서는 다양한 값의 효과를 설명합니다.
- 수신 시간 제한을 0으로 설정하면 메시지가 디스패치될 때까지 끌어오기 블록이 무기한으로 차단됩니다.
- 수신 시간 제한을 양수 값으로 설정하면 끌어오기에서 시간 제한 시간까지 차단됩니다.
- 수신 시간 제한을 음수 값으로 설정하면 끌어오기 수신이 대기하지 않음을 의미합니다. 즉, 메시지를 즉시 반환하거나 사용할 수 있는 메시지가 없는 경우
null.
메모
시간 제한 값이 높을 경우 몇 가지 부작용이 발생합니다. 예를 들어 시간 제한 값이 높으면 주 스레드가 블록 상태에 있는 시간도 연장됩니다. 이 상태는 컨테이너가 stop() 호출에 대한 응답성이 낮으며 receive() 호출 사이에만 중지할 수 있습니다.
또한 컨테이너는 receive-timeout 간격이 경과한 후에만 요청을 보낼 수 있습니다. 간격이 10분보다 긴 경우 Service Bus는 링크를 닫고 수신기가 보내거나 받지 못하도록 합니다. 자세한 내용은 Azure Service BusAMQP 오류
높은 수신 시간 제한이 필요한 경우 JmsPoolConnectionFactory사용해야 합니다.
링크 닫기 문제 및
프리페치 문제
문제 설명
적합하지 않은 프리페치 정책으로 인해 다음과 같은 문제가 발생할 수 있습니다.
- 동일한 메시지가 반복적으로 소비됩니다.
- 메시지는 오류 또는 예외 없이 처리되는 경우에도
MaxDeliveryCountExceeded후 배달 못한 편지 큐에 배치됩니다.
원인 분석
이 문제는 일반적으로 프리페치 값이 실제 소비 용량보다 높고 너무 많은 메시지가 사용되기를 기다리는 로컬 버퍼에 프리페치되는 효과가 있는 경우에 발생합니다. 그러나 프리페치된 메시지는 Service Bus 쪽에서 피킹 잠금 모드로 디스패치된 것으로 간주됩니다. 디스패치된 각 메시지에는 max-delivery-count 및 잠금 기간 특성이 있습니다. 피킹 잠금 수신 모드에서 프리페치 버퍼로 가져온 메시지는 잠금 똑딱에 대한 시간 제한 시계와 함께 잠긴 상태의 버퍼로 획득됩니다. 프리페치 버퍼가 크고 처리가 너무 오래 걸리면 프리페치 버퍼에 머무는 동안 메시지 잠금이 만료되는 경우 메시지는 중단된 것으로 처리되고 큐에서 검색할 수 있게 됩니다.
이 문제로 인해 메시지가 프리페치 버퍼로 가져와서 끝에 배치될 수 있습니다. 메시지 만료 전에 프리페치 버퍼가 처리되지 않으면 메시지가 반복적으로 프리페치되지만 사용 가능한(유효하게 잠긴) 상태로 효과적으로 전달되지는 않습니다. 그런 다음 오래된 복사본이 큐에서 제거되면 애플리케이션은 동일한 메시지를 반복적으로 사용하고 완료할 수 없습니다. 또 다른 경우 반복된 메시지는 모두 버퍼에서 만료된 후 소비됩니다. 이 경우 최대 배달 횟수를 초과하면 Service Bus의 메시지가 배달 못 한 편지 큐로 이동됩니다.
자세한 내용은 프리페치가 기본 옵션이 아닌 이유는 참조하세요. Azure Service Bus 메시지프리페치의 섹션입니다.
용액
프리페치의 구성에 주의하여 사용 기능에 맞는지 확인합니다. 잠금 시간 제한이 프리페치 버퍼의 최대 크기에 대한 누적 예상 메시지 처리 시간을 초과하도록 큐 또는 구독에 구성된 최대 프리페치 수와 잠금 기간의 균형을 유지해야 합니다. 동시에 잠금 시간 제한이 너무 길어서 메시지가 실수로 삭제될 때 최대 사용 시간을 초과할 수 없으므로 다시 배달되기 전에 잠금이 만료되어야 합니다.
기본값이 0인 프리페치 특성을 구성하려면 다음 속성 중 하나를 사용합니다.
| 재산 | 묘사 |
|---|---|
spring.jms.servicebus.prefetch.all |
이 Service Bus 네임스페이스의 프리페치 옵션에 대한 대체 값입니다. |
spring.jms.servicebus.prefetch.queue-prefetch |
큐의 프리페치 번호입니다. |
spring.jms.servicebus.prefetch.queue-browser-prefetch |
큐 브라우저의 프리페치 번호입니다. |
spring.jms.servicebus.prefetch.topic-prefetch |
토픽의 프리페치 번호입니다. |
spring.jms.servicebus.prefetch.durable-topic-prefetch |
지속성 토픽의 프리페치 번호입니다. |
Service Bus에 AMQP 처리를 수행하는 방법
JMS는 메시징 브로커에 메시지를 승인할 때 5가지 AMQP 처리 유형을 지원합니다. 지원되는 값은 ACCEPTED, REJECTED, RELEASED, MODIFIED_FAILED및 MODIFIED_FAILED_UNDELIVERABLE. 자세한 내용은 Azure Service Bus 표준 및 AMQP 1.0Java Message Service 1.1 사용
따라서 JmsListener사용하여 수동으로 메시지를 완료, 중단, 배달 못 한 편지, 연기 또는 해제하려면 다음 단계를 사용합니다.
세션 트랜잭션을 사용하지 않도록 설정하고 클라이언트 ack 모드를 사용합니다.
이 작업을 수행하려면 고유한 JmsListenerContainerFactory bean을 선언한 다음 속성을 설정하거나
JmsListenerContainerFactory정의된 후 처리합니다. 다음 예제에서는 다른 콩을 선언하는 방법을 사용합니다.@Configuration(proxyBeanMethods = false) public class CustomJmsConfiguration { @Bean public JmsListenerContainerFactory<?> customQueueJmsListenerContainerFactory( DefaultJmsListenerContainerFactoryConfigurer configurer, ConnectionFactory connectionFactory) { DefaultJmsListenerContainerFactory jmsListenerContainerFactory = new DefaultJmsListenerContainerFactory(); configurer.configure(jmsListenerContainerFactory, connectionFactory); jmsListenerContainerFactory.setPubSubDomain(Boolean.FALSE); jmsListenerContainerFactory.setSessionTransacted(Boolean.FALSE); jmsListenerContainerFactory.setSessionAcknowledgeMode(Session.CLIENT_ACKNOWLEDGE); return jmsListenerContainerFactory; } }메시지 처리기에서 메시지를 명시적으로 완료하거나 중단합니다.
@JmsListener(destination = "QUEUE_NAME", containerFactory = "customQueueJmsListenerContainerFactory") public void receiveMessage(JmsTextMessage message) throws Exception { String event = message.getBody(String.class); try { logger.info("Received event: {}", event); logger.info("Received message: {}", message); // by default complete the message message.acknowledge(); } catch (Exception e) { logger.error("Exception while processing re-source event: " + event, e); JmsAcknowledgeCallback acknowledgeCallback = message.getAcknowledgeCallback(); // explicitly abandon the message acknowledgeCallback.setAckType(MODIFIED_FAILED); message.setAcknowledgeCallback(acknowledgeCallback); message.acknowledge(); throw e; } }
구성 문제
Service Bus JMS 자동 구성 사용 안 함
문제 설명
일부 사용자는 Service Bus JMS 이외의 Azure 서비스의 자동 구성을 위해 일부 Spring Cloud Azure Starter를 가져옵니다. 또한 Service Bus JMS 없이 Spring JMS 프레임워크를 사용합니다. 그런 다음 애플리케이션이 시작하려고 하면 다음 예외가 throw됩니다.
Caused by: java.lang.IllegalArgumentException: 'spring.jms.servicebus.connection-string' should be provided
at com.azure.spring.cloud.autoconfigure.jms.properties.AzureServiceBusJmsProperties.afterPropertiesSet(AzureServiceBusJmsProperties.java:210)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800)
... 98 more
원인 분석
이 문제는 모든 Spring Cloud Azure 자동 구성 클래스가 동일한 모듈에 배치되므로 모든 Spring Cloud Azure Starter가 실제로 Service Bus JMS를 포함하는 모든 자동 구성을 가져오기 때문에 발생합니다. 그런 다음 애플리케이션이 Spring JMS API를 사용하는 경우 Service Bus JMS 자동 구성 조건을 충족하고 트리거합니다. 그런 다음 spring-cloud-azure-starter-servicebus-jms사용하지 않으려는 사용자의 경우 JMS용 Service Bus를 구성할 이유가 없으므로 속성 조건이 충족되지 않습니다. 이 경우 예외가 throw됩니다.
용액
Spring Cloud Azure for Service Bus JMS는 자동 구성을 켜거나 끄는 속성을 제공합니다. 다음 속성 설정을 사용하여 필요에 따라 이 기능을 사용하지 않도록 선택할 수 있습니다.
spring.jms.servicebus.enabled=false
메시지 특성 구성
아웃바운드 메시지의 콘텐츠 형식을 설정하는 방법
콘텐츠 형식을 구성하려면 메시지를 변환할 때 콘텐츠 형식 특성을 수정하도록 메시지 변환기를 사용자 지정합니다. 다음 코드는 바이트 메시지를 예로 사용합니다.
먼저 다음 예제와 같이 JmsTemplate사용할 메시지 변환기를 사용자 지정합니다.
public class CustomMappingJackson2MessageConverter extends MappingJackson2MessageConverter {
public static final String CONTENT_TYPE = "application/json";
public CustomMappingJackson2MessageConverter() {
this.setTargetType(MessageType.BYTES);
}
@Override
protected BytesMessage mapToBytesMessage(Object object, Session session, ObjectWriter objectWriter)
throws JMSException, IOException {
final BytesMessage message = super.mapToBytesMessage(object, session, objectWriter);
JmsBytesMessage msg = (JmsBytesMessage) message;
AmqpJmsMessageFacade facade = (AmqpJmsMessageFacade) msg.getFacade();
facade.setContentType(Symbol.valueOf(CONTENT_TYPE));
return msg;
}
}
그런 다음, 다음 예제와 같이 사용자 지정된 메시지 변환기 빈을 선언합니다.
@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {
@Bean
public MessageConverter messageConverter() {
return new CustomMappingJackson2MessageConverter();
}
}
MappingJackson2MessageConverter에 대한 형식 ID 속성 이름을 설정하는 방법
type-id-property-name 특성을 사용하면 MappingJackson2MessageConverter 메시지 페이로드를 역직렬화하는 데 사용할 클래스를 결정할 수 있습니다. 각 Java 개체를 Spring Message 페이로드로 직렬화할 때 변환기는 페이로드 형식을 type-id-property-name기록된 속성 이름을 사용하여 메시지 속성에 저장합니다. 그런 다음, 메시지를 역직렬화할 때 변환기는 메시지에서 형식 ID를 읽고 역직렬화를 수행합니다.
type-id-property-name설정하려면 다음 예제와 같이 고유한 MappingJackson2MessageConverter bean을 선언하고 해당 속성을 구성합니다.
@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {
@Bean
public MessageConverter jacksonJmsMessageConverter()
{
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTypeIdPropertyName("your-custom-type-id-property-name");
return converter;
}
}
중복 검색
Azure Service Bus는 속성을 적용하여 메시지를 고유하게 식별하고 Service Bus로 전송된 중복 항목을 삭제하는 MessageId지원합니다.
그러나 JMS API의 경우 JMS 사양에서 잘못된 간주되는 JMS 메시지 ID를 설정해서는 안 됩니다. 따라서 이 기능은 현재 Spring Cloud Azure Service Bus JMS Starter에서 지원되지 않습니다.
이 기능에 대한 추가 업데이트는GitHub
AMQP 전송 로깅 사용
자세한 내용은
추가 도움말 보기
지원을 위해 연락하는 방법에 대한 자세한 내용은 리포지토리 루트의 지원 참조하세요.
Spring Cloud Azure Service Bus JMS 스타터에 대한 리소스
- JMS Azure Service Bus 사용
- Spring에서 JMS를 사용하여 Azure Service Bus 액세스
- Spring Cloud Azure 4.0 대한
마이그레이션 가이드 - 샘플
GitHub 문제 제출
GitHub 문제를 제출할 때 다음 세부 정보가 요청됩니다.
- Service Bus 구성/네임스페이스 환경
- 네임스페이스(표준 또는 프리미엄)는 어떤 계층인가요?
- 사용 중인 메시징 엔터티 유형(큐 또는 토픽)은 무엇인가요? 및 해당 구성
- 각 메시지의 평균 크기는 무엇인가요?
- 트래픽 패턴은 어떤가요? 즉, 분당 메시지 수와 클라이언트가 항상 사용 중이거나 트래픽 기간이 느린지 여부입니다.
- 코드 및 단계 재현
- 이는 환경에서 문제를 재현할 수 없는 경우가 많기 때문에 중요합니다.
- 로그