Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo descreve como solucionar problemas conhecidos e erros comuns ao usar o Spring JMS. O artigo também responde a algumas perguntas frequentes sobre spring-cloud-azure-starter-servicebus-jms.
Problemas de conectividade
O MessageProducer foi fechado devido a um erro irrecuperável
Descrição do problema
Ao usar JmsTemplate para enviar mensagens, JmsTemplate fica indisponível durante um intervalo ocioso entre 10 e 15 minutos. Enviar mensagens nesse intervalo pode obter as exceções mostradas na seguinte saída de exemplo:
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]
...
Análise de causa
As exceções ocorrem para Barramento de Serviço do Azure quando a conexão e o link AMQP estão ativos, mas nenhuma chamada — por exemplo, chamadas de envio ou recebimento — for feita usando o link por 10 minutos. Nesse caso, o link é fechado. E quando todos os links da conexão tiverem sido fechados por não haver atividade (inatividade) e nenhum novo link tiver sido criado em 5 minutos, a conexão será fechada.
Para o starter JMS do Barramento de Serviço, a CachingConnectionFactory é usada por padrão, o que armazena em cache a sessão, o produtor e o consumidor. Quando o JmsProducer fica ocioso por mais de 10 minutos, mas por menos de 15, o link ocupado pelo produtor em cache foi fechado. As mensagens não podem ser enviadas durante esse intervalo. Em seguida, após mais 5 minutos ocioso, toda a conexão é fechada. Assim, qualquer operação de envio após o intervalo ocioso de 15 minutos faz com que o CachingConnectionFactory crie uma nova conexão para enviar. A operação de envio fica disponível após 15 minutos.
Solução alternativa
Atualmente, o starter fornece uma solução alternativa para o problema de desvinculação do link aplicando o JmsPoolConnectionFactory, que mantém em pool Connection, Session e MessageProducer e gerencia o ciclo de vida das instâncias mantidas em pool. Essa solução alternativa pode garantir que um produtor seja removido após ficar indisponível e, portanto, todas as operações de envio sejam executadas em produtores ativos.
Para usar essa solução alternativa, adicione a seguinte configuração:
spring:
jms:
servicebus:
pool:
enabled: true
max-connections: ${your-expected-max-connection-value}
Uso de spring.jms.servicebus.idle-timeout
As propriedades de tempo limite ocioso configuram o tempo limite ocioso de uma conexão AMQP. A especificação AMQP fornece a seguinte descrição:
As conexões estão sujeitas a um tempo limite de inatividade. O tempo limite será disparado por um par local quando nenhum quadro for recebido depois que um valor limite for excedido. O tempo limite de inatividade é medido em milissegundos e é contado a partir do momento em que o último frame é recebido. Se o limite for excedido, um par DEVERÁ tentar encerrar a conexão de maneira controlada usando um quadro de fechamento com um erro explicando o porquê. Se o par remoto não responder da maneira adequada dentro do limite, o par remoto PODERÁ fechar o soquete TCP.
Para um cliente JMS, ao configurar essa propriedade, você controla no lado do servidor quanto tempo espera que o servidor envie um quadro vazio para manter uma conexão ativa quando nenhuma mensagem for entregue. Essa propriedade controla o comportamento do par remoto e cada par pode ter seu próprio valor isolado.
Problemas de JmsTemplate
Mensagens agendadas
O Barramento de Serviço do Azure dá suporte ao processamento diferido de mensagens. Para obter mais informações, consulte a seção Mensagens agendadas em Sequenciamento de mensagens e carimbos de data/hora. Para o JMS, para agendar uma mensagem, defina a propriedade ScheduledEnqueueTimeUtc usando o cabeçalho de anotação da mensagem x-opt-scheduled-enqueue-time.
Problemas de JmsListener
Solicitações demais são enviadas ao Barramento de Serviço, embora não haja mensagens no servidor
Descrição do problema
Ao usar a API @JmsListener, em alguns casos, você pode ver no portal do Azure que há valores contínuos para solicitações de entrada enviadas para a fila ou os tópicos, mesmo que não haja mensagens no servidor a serem recebidas.
Análise de causa
@JmsListener é um listener de sondagem, projetado para tentativas repetidas de sondagem.
O ouvinte se senta em um loop de sondagem contínuo. Cada loop chama o método MessageConsumer.receive() JMS para sondar o consumidor local quanto às mensagens a serem consumidas. Por padrão, para cada operação de sondagem, o consumidor local envia solicitações de pull ao agente de mensagens para solicitar mensagens e, em seguida, bloqueia por um determinado período de tempo. O processo específico de sondagem é determinado por várias propriedades, incluindo receiveTimeout, prefetchSize e receiveLocalOnly ou receiveNoWaitLocalOnly. O método receiveNoWaitLocalOnly é usado somente quando você define receiveTimeout como um valor negativo.
Quando esse problema acontecer com seu aplicativo, verifique as seguintes configurações:
Verifique se a política de pré-busca está definida como 0, que também é o valor padrão. 0-prefetch significa que um consumidor pull envia solicitações de pull ao Barramento de Serviço para cada sondagem.
Se você configurou pré-busca diferente de zero, determine se a propriedade
receiveLocalOnlyoureceiveNoWaitLocalOnlyestá definida comofalse, que é a opção padrão. Um valorfalseaqui ainda resulta no envio de solicitações de pull para o servidor porque ele não sonda apenas o consumidor local.A configuração
receiveTimeoutdetermina por quanto tempo ela permanece bloqueada em cada pull request, de maneira que isso possa afetar a frequência com que pull requests são enviadas para o servidor. O valor padrão é 1 segundo.
Para uma análise completa, consulte a discussão na issue do GitHub.
Soluções
As seções a seguir descrevem duas soluções para lidar com esse problema
Solução 1. Alterar somente para enviar por push o consumidor e a verificação local
Ao alterar o modo para push, o consumidor se torna um consumidor de Notificação Assíncrona que não busca mensagens no broker, mas mantém uma quantidade-alvo de crédito de link. O valor é determinado por uma propriedade de prefetch. À medida que o Barramento de Serviço (remetente) envia mensagens, o crédito do link do remetente diminui e, quando esse crédito fica abaixo de um limite, o cliente (receptor) envia uma solicitação ao servidor para aumentar o crédito do link do remetente até o valor de destino desejado.
Para realizar essa solução, adicione a seguinte configuração:
Primeiro, configure o número prefetch como diferente de zero, o que configura o consumidor como não pull. A tabela a seguir mostra diversas propriedades de pré-fetch, cada uma delas controlando entidades do Barramento de Serviço diferentes. Defina as propriedades que se aplicam ao seu caso.
| Propriedade | Descrição |
|---|---|
spring.jms.servicebus.prefetch.all |
O valor padrão da opção de pré-busca neste namespace do Barramento de Serviço |
spring.jms.servicebus.prefetch.queue-prefetch |
O número de pré-busca da fila. |
spring.jms.servicebus.prefetch.queue-browser-prefetch |
O número de pré-fetch do navegador da fila. |
spring.jms.servicebus.prefetch.topic-prefetch |
O número de pré-busca do tópico. |
spring.jms.servicebus.prefetch.durable-topic-prefetch |
O número de pré-busca do tópico durável. |
Em segundo lugar, configure o non-local-check adicionando uma classe de configuração para o personalizador de fábrica, conforme mostrado no exemplo a seguir:
@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);
};
}
}
O valor de prefetch pode afetar a velocidade com que as mensagens são despachadas para o buffer local do consumidor. Você deve ajustar o valor de acordo com o desempenho de consumo e os volumes de mensagens. Um valor adequado pode acelerar o processo de consumo, enquanto um valor muito grande pode fazer com que as mensagens em buffer local fiquem desatualizadas e enviadas novamente. Para volumes de mensagens baixos, em que cada mensagem leva muito tempo para ser processada, defina o pré-busca como 1. Esse valor garante que um consumidor esteja processando apenas uma mensagem por vez.
Solução 2. Aumentar o tempo limite de recebimento para diminuir a frequência de pull
A propriedade de tempo limite de recebimento determina a estratégia de quanto tempo o consumidor bloqueia para aguardar um resultado de pull. Portanto, ao estender o tempo limite, você pode reduzir a frequência de pull e, em seguida, reduzir o número de solicitações de pull ao escolher o modo de pull. Em casos extremos, você pode definir a estratégia para aguardar indefinidamente até que chegue uma mensagem, ou seja, o consumidor só efetua pull após o consumo de uma mensagem. Nesse caso, quando não houver mensagens no servidor, ele bloqueará a espera.
Para realizar essa solução, configure a propriedade spring.jms.listener.receive-timeout. Essa propriedade é do tipo java.time.Duration e tem um valor padrão de 1 segundo. A lista a seguir explica o efeito de vários valores:
- Definir o tempo limite de recebimento como 0 significa que o pull bloqueia por tempo indeterminado até que uma mensagem seja expedida.
- Definir o tempo limite de recebimento como um valor positivo significa que o pull bloqueia até o tempo limite.
- Definir o tempo limite de recebimento como um valor negativo significa que o pull é um recebimento sem espera, o que significa que ele retorna uma mensagem imediatamente ou
nullse nenhuma mensagem estiver disponível.
Nota
Um valor de tempo limite alto pode trazer alguns efeitos colaterais. Por exemplo, um valor de timeout alto também prolongará o tempo em que a thread principal permanecerá em estado de bloqueio. Esse status significa que o contêiner será menos responsivo às chamadas stop() e só poderá parar entre receive() chamadas.
Além disso, o contêiner só pode enviar solicitações depois que o intervalo de receive-timeout tiver passado. Se o intervalo for superior a 10 minutos, o Barramento de Serviço fechará a conexão e impedirá o listener de enviar ou receber. Para obter mais informações, consulte a seção O link está fechado de Erros de AMQP no Barramento de Serviço do Azure. Por padrão, o ouvinte usa um CachingConnectionFactory.
Se você precisar de um tempo limite de recebimento alto, certifique-se de usar a JmsPoolConnectionFactory.
Para obter mais informações sobre o problema de fechamento do link e como usar JmsPoolConnectionFactory, consulte o O MessageProducer foi fechado devido a um erro irrecuperável seção.
Problema de pré-carregamento
Descrição do problema
Uma política de pré-busca inadequada pode causar os seguintes problemas:
- As mesmas mensagens são consumidas repetidamente.
- As mensagens são colocadas na fila de mensagens mortas após
MaxDeliveryCountExceeded, mesmo quando as mensagens são processadas sem erro ou exceção.
Análise de causa
Esse problema geralmente ocorre quando o valor de prefetch é maior do que a capacidade real de consumo, o que faz com que mensagens demais sejam pré-buscadas para o buffer local, onde ficam aguardando consumo. No entanto, as mensagens pré-buscadas são consideradas como expedidas em um modo peek-lock no lado do Barramento de Serviço. Cada mensagem expedida tem um max-delivery-count e lock-duration atributos. No modo de recebimento peek-lock, as mensagens buscadas no buffer de pré-fetch são obtidas no buffer em um estado bloqueado, com o tempo limite para o bloqueio sendo contado. Se o buffer de pré-busca for grande e o processamento demorar tanto que os bloqueios das mensagens expirem enquanto elas permanecem no buffer de pré-busca, a mensagem será tratada como abandonada e ficará disponível novamente para ser recuperada da fila.
Esse problema pode fazer com que a mensagem seja recuperada para o buffer de pré-busca e colocada no final. Se o buffer de pré-busca não for processado antes da expiração da mensagem, as mensagens serão repetidamente pré-buscadas, mas nunca efetivamente entregues em um estado utilizável (bloqueado validamente). Em seguida, quando essas cópias desatualizadas forem desativadas, o aplicativo consumirá a mesma mensagem repetidamente e não será capaz de concluí-las. Em outro caso, as mensagens repetidas expiram no buffer antes de serem consumidas. Nesse caso, a mensagem no Barramento de Serviço acabará sendo movida para a fila de mensagens mortas após exceder o número máximo de entregas.
Para obter mais informações, consulte a seção Por que a Pré-busca não é a opção padrão? de Pré-busca de mensagens do Barramento de Serviço do Azure.
Solução
Tenha cuidado com a configuração do prefetch para garantir que ela esteja adequada à capacidade de consumo. Você deve equilibrar o número máximo de pré-busca e a duração do bloqueio configurada na fila ou na assinatura, de modo que o tempo limite do bloqueio seja, no mínimo, maior que o tempo total esperado de processamento das mensagens para o tamanho máximo do buffer de pré-busca, mais uma mensagem. Ao mesmo tempo, o tempo limite do bloqueio não deve ser tão longo a ponto de as mensagens poderem exceder seu tempo de vida máximo quando forem descartadas acidentalmente, o que exigiria que o bloqueio expirasse antes de serem reentregues.
Para configurar o atributo prefetch, que tem um valor padrão de zero, use uma das seguintes propriedades:
| Propriedade | Descrição |
|---|---|
spring.jms.servicebus.prefetch.all |
O valor de fallback da opção "prefetch" neste namespace do Barramento de Serviço. |
spring.jms.servicebus.prefetch.queue-prefetch |
O número de pré-busca da fila. |
spring.jms.servicebus.prefetch.queue-browser-prefetch |
O número de pré-fetch do navegador da fila. |
spring.jms.servicebus.prefetch.topic-prefetch |
O número de pré-busca do tópico. |
spring.jms.servicebus.prefetch.durable-topic-prefetch |
O número de pré-busca do tópico durável. |
Como realizar o descarte AMQP para o Barramento de Serviço?
O JMS dá suporte a cinco tipos de disposição AMQP ao confirmar o recebimento de mensagens no agente de mensagens. Os valores com suporte são ACCEPTED, REJECTED, RELEASED, MODIFIED_FAILEDe MODIFIED_FAILED_UNDELIVERABLE. Para obter mais informações, consulte a seção disposition do AMQP e mapeamento de operações do Barramento de Serviço de Usar o Java Message Service 1.1 com o Barramento de Serviço do Azure Standard e AMQP 1.0.
Assim, para concluir de forma manual, abandonar, mover para a fila de mensagens mortas, adiar ou liberar uma mensagem usando JmsListener, siga estas etapas:
Desabilite a transação de sessão e use o modo de confirmação CLIENT.
Para realizar essa tarefa, ou declare seu próprio bean JmsListenerContainerFactory e, em seguida, defina as propriedades, ou faça o pós-processamento de definido em starter. O exemplo a seguir usa a abordagem de declarar outro bean:
@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; } }No manipulador de mensagens, conclua ou abandone mensagens explicitamente.
@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; } }
Problemas de configuração
Desabilitar a configuração automática do JMS do Barramento de Serviço
Descrição do problema
Alguns usuários importam algum Spring Cloud Azure Starter para a autoconfiguração de um serviço do Azure que não seja o Barramento de Serviço JMS. Eles também usam o framework Spring JMS sem a necessidade do JMS do Barramento de Serviço. Em seguida, quando o aplicativo tenta iniciar, as seguintes exceções são geradas:
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
Análise de causa
Esse problema ocorre porque todas as classes de autoconfiguração do Spring Cloud Azure ficam no mesmo módulo, portanto qualquer starter do Spring Cloud Azure acaba importando toda essa autoconfiguração, que também inclui o Barramento de Serviço JMS. Em seguida, quando o aplicativo usa a API Spring JMS, ele satisfaz a condição de configuração automática do JMS do Barramento de Serviço e a aciona. Então, para os usuários que não pretendem usar spring-cloud-azure-starter-servicebus-jms, as condições da propriedade não são satisfeitas, porque não há motivo para configurar o Barramento de Serviço para JMS. Essa situação faz com que as exceções sejam lançadas.
Solução
O Spring Cloud Azure para Barramento de Serviço JMS fornece uma propriedade para ativar ou desativar a autoconfiguração. Você pode optar por desabilitar essa funcionalidade conforme necessário usando a seguinte configuração de propriedade:
spring.jms.servicebus.enabled=false
Configurar atributos de mensagem
Como definir o tipo de conteúdo de mensagens de saída?
Para configurar o tipo de conteúdo, personalize o Conversor de Mensagens para modificar o atributo de tipo de conteúdo ao converter mensagens. O código a seguir usa mensagens de byte como exemplo.
Primeiro, personalize o conversor de mensagens a ser usado no JmsTemplate, conforme mostrado no exemplo a seguir:
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;
}
}
Em seguida, declare o bean do conversor de mensagens personalizado, conforme mostrado neste exemplo:
@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {
@Bean
public MessageConverter messageConverter() {
return new CustomMappingJackson2MessageConverter();
}
}
Como definir o nome da propriedade de ID de tipo para MappingJackson2MessageConverter?
O atributo type-id-property-name permite que o MappingJackson2MessageConverter determine qual classe usar para desserializar o conteúdo da mensagem. Ao serializar cada objeto Java para um payload da Spring Message, o conversor armazena o tipo do payload em uma propriedade de mensagem cujo nome é registrado por type-id-property-name. Em seguida, ao desserializar a mensagem, o conversor lê a ID do tipo da mensagem e executa a desserialização.
Para definir o type-id-property-name, declare seu próprio MappingJackson2MessageConverter bean e configure essa propriedade, conforme mostrado no exemplo a seguir:
@Configuration(proxyBeanMethods = false)
public class CustomJmsConfiguration {
@Bean
public MessageConverter jacksonJmsMessageConverter()
{
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setTypeIdPropertyName("your-custom-type-id-property-name");
return converter;
}
}
Detecção duplicada
O Barramento de Serviço do Azure dá suporte à detecção de duplicatas, que usa a propriedade MessageId para identificar mensagens de forma exclusiva e descartar as duplicatas enviadas ao Barramento de Serviço.
No entanto, para a API JMS, você não deve definir a ID da mensagem JMS, que é considerada ilegal em especificações JMS. Portanto, esse recurso não tem suporte no momento para o Spring Cloud Barramento de Serviço do Azure JMS Starter.
Para futuras atualizações sobre esta funcionalidade, consulte a issue do GitHub.
Habilitar log de transporte AMQP
Para obter mais informações, consulte a seção Habilitar o log de transporte AMQP de Solução de problemas do Barramento de Serviço.
Obter ajuda adicional
Para obter mais informações sobre maneiras de entrar em contato com o suporte, consulte suporte na raiz do repositório.
Recursos do starter JMS do Barramento de Serviço do Azure para Spring Cloud
- Usar o Barramento de Serviço do Azure com o JMS
- usar o JMS no Spring para acessar o Barramento de Serviço do Azure
- Guia de Migração do para o Spring Cloud Azure 4.0
- Exemplo
Arquivar problemas do GitHub
Ao apresentar problemas do GitHub, os seguintes detalhes são solicitados:
- Configuração do Barramento de Serviço / Ambiente do namespace
- Qual camada é o namespace (standard ou premium)?
- Que tipo de entidade de mensagens está sendo usada (fila ou tópico)? e sua configuração.
- Qual é o tamanho médio de cada Mensagem?
- Como é o padrão de tráfego? (ou seja, o número de mensagens por minuto e se o Cliente está sempre ocupado ou tem períodos de tráfego lentos).)
- Código e etapas de reprodução
- Isso é importante, pois muitas vezes não podemos reproduzir o problema em nosso ambiente.
- Registros