Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. 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 a 15 minutos. O envio de mensagens nesse intervalo pode obter as exceções mostradas na saída de exemplo a seguir:
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 no Azure Service Bus quando a ligação AMQP e o link estão ativos, mas não são efetuadas quaisquer operações — por exemplo, operações de envio ou receção — através do link durante 10 minutos. Neste caso, o link está fechado. E quando todos os links na conexão foram fechados porque não houve atividade (ocioso) e um novo link não foi criado em 5 minutos, a conexão é fechada.
Para o starter JMS do Service Bus, a CachingConnectionFactory é usada por predefinição e armazena em cache a sessão, o produtor e o consumidor. Quando o JmsProducer permanece inativo durante mais de 10 minutos, mas menos de 15, a ligação que o produtor em cache está a ocupar foi fechada. As mensagens não podem ser enviadas durante este intervalo. Então, após mais 5 minutos de inatividade, a ligação é encerrada. 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 desanexação de link aplicando o JmsPoolConnectionFactory, que agrupa Connection, Sessione MessageProducere gerencia o ciclo de vida das instâncias agrupadas. Essa solução alternativa pode garantir que um produtor seja despejado depois de estar indisponível e, portanto, todas as operações de envio sejam realizadas 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}
Utilização de spring.jms.servicebus.idle-timeout
As propriedades idle-timeout configuram o tempo limite de inatividade de uma conexão AMQP. A especificação AMQP fornece a seguinte descrição:
As conexões estão sujeitas a um limiar de tempo limite de inatividade. O tempo limite é acionado por um par local quando nenhum quadro é recebido depois que um valor de limite é excedido. O tempo limite de inatividade é medido em milissegundos e começa a partir do momento em que o último quadro é recebido. Se o limite for excedido, então um par DEVE tentar fechar a conexão usando um quadro fechado com um erro explicando o porquê. Se o peer remoto não responder normalmente dentro de um limite a isso, o peer PODE fechar o soquete TCP.
Para um cliente JMS, ao configurar essa propriedade, você controla no lado do servidor por quanto tempo espera que o servidor envie um quadro vazio para manter uma conexão ativa quando nenhuma mensagem é entregue. Essa propriedade controla o comportamento do par remoto e cada par pode ter seu próprio valor isolado.
Problemas do JmsTemplate
Mensagens agendadas
O Azure Service Bus suporta o processamento diferido de mensagens. Para obter mais informações, consulte a seção Mensagens agendadas de Sequenciamento de mensagens e carimbos de data/hora. Para JMS, para agendar uma mensagem, defina a propriedade ScheduledEnqueueTimeUtc usando o cabeçalho de anotação de mensagem x-opt-scheduled-enqueue-time.
Problemas do JmsListener
Muitas solicitações são enviadas para o Service Bus mesmo que não haja mensagens no servidor
Descrição do problema
Ao usar a API do @JmsListener, em alguns casos, você pode ver no portal do Azure que há valores contínuos para solicitações de entrada enviadas para sua fila ou tópicos, mesmo que não haja mensagens no servidor para receber.
Análise de causa
@JmsListener é um ouvinte de sondagens, que é construído para repetidas tentativas de sondagem.
O ouvinte senta-se em um circuito de votação contínua. Cada loop chama o método JMS MessageConsumer.receive() para sondar o consumidor local em busca de mensagens a serem consumidas. Por padrão, para cada operação de pesquisa, o consumidor local envia solicitações pull para o agente de mensagens para solicitar mensagens e, em seguida, bloqueia por um determinado período de tempo. O processo de votação concreta é decidido por várias propriedades, incluindo receiveTimeout, prefetchSizee receiveLocalOnly ou receiveNoWaitLocalOnly. O método receiveNoWaitLocalOnly é usado somente quando você define receiveTimeout para um valor negativo.
Quando esse problema acontecer com seu aplicativo, verifique as seguintes definições de configuração:
Determine se sua política de pré-busca é 0, que também é a opção padrão. 0-prefetch significa um consumidor pull que envia solicitações pull para o Service Bus para cada pesquisa.
Se você configurou uma pré-busca diferente de zero, determine se sua propriedade
receiveLocalOnlyoureceiveNoWaitLocalOnlyestá definida comofalse, que é a opção padrão. Um valorfalseaqui ainda resulta no envio de solicitações pull para o servidor, porque ele não sonda apenas o consumidor local.A configuração
receiveTimeoutdetermina por quanto tempo ela bloqueia para cada solicitação pull, de modo que pode afetar a frequência de envio de solicitações pull para o servidor. O valor padrão é 1 segundo.
Para uma análise completa, consulte a discussão na edição do GitHub.
Soluções
As seções a seguir descrevem duas soluções para lidar com esse problema
Solução 1. Alterar para empurrar apenas o consumidor e a verificação local
Ao alterar o modo para push, o consumidor torna-se um consumidor de Notificação Assíncrona que não extrai mensagens do broker, mas mantém uma quantidade pretendida de crédito da ligação. O valor é decidido por um imóvel de pré-busca. À medida que o Service Bus (emissor) envia mensagens, o crédito de ligação do remetente diminui, e quando o crédito de ligação do remetente fica abaixo de um determinado limite, o cliente (recetor) envia um pedido ao servidor para aumentar o crédito de ligação do remetente de volta ao valor alvo desejado.
Para realizar essa solução, adicione a seguinte configuração:
Primeiro, configure o número prefetch com um valor diferente de zero, o que faz com que o consumidor fique sem pull. A tabela a seguir mostra várias propriedades de pré-busca, cada uma das quais controla diferentes entidades do Service Bus. Defina as propriedades que se aplicam ao seu caso.
| Propriedade | Descrição |
|---|---|
spring.jms.servicebus.prefetch.all |
O valor de fallback para a opção de pré-busca neste namespace do Service Bus |
spring.jms.servicebus.prefetch.queue-prefetch |
O número de pré-busca para a fila. |
spring.jms.servicebus.prefetch.queue-browser-prefetch |
O número de pré-busca para o navegador de fila. |
spring.jms.servicebus.prefetch.topic-prefetch |
O número de pré-busca para o tópico. |
spring.jms.servicebus.prefetch.durable-topic-prefetch |
O número de pré-busca para o 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 pré-busca pode afetar a rapidez com que as mensagens são enviadas para o buffer local do consumidor. Você deve ajustar o valor de acordo com seu desempenho de consumo e volumes de mensagens. Um valor adequado pode acelerar o processo de consumo, enquanto um valor muito grande pode fazer com que as mensagens armazenadas em buffer local fiquem desatualizadas e sejam enviadas novamente. Para volumes de mensagens baixos, em que cada mensagem leva muito tempo para ser processada, defina a pré-busca como 1. Esse valor garante que um consumidor esteja processando apenas uma mensagem de cada vez.
Solução 2. Aumente o tempo limite de receção para diminuir a frequência de tração
A propriedade de tempo limite de receção determina a estratégia relativa ao tempo durante o qual o consumidor bloqueia enquanto aguarda um resultado de extração. Assim, ao aumentar o tempo limite, pode reduzir a frequência de consulta e, em seguida, reduzir o número de pedidos de extração ao escolher o modo de extração. Em casos extremos, você pode definir a estratégia para esperar indefinidamente até que uma mensagem chegue, o que significa que o consumidor só puxa depois de consumir uma mensagem. Neste caso, quando não houver mensagens no servidor, ele será bloqueado para espera.
Para realizar essa solução, configure a propriedade spring.jms.listener.receive-timeout. Esta 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 indefinidamente até que uma mensagem seja enviada.
- Definir o tempo limite de recebimento para um valor positivo significa que o pull bloqueia até a quantidade de 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.
Observação
Um alto valor de tempo limite pode trazer alguns efeitos colaterais. Por exemplo, um valor elevado de tempo de espera também prolongará o tempo durante o qual a thread principal permanece em estado de bloqueio. Esse status significa que o contêiner responderá menos às chamadas stop() e só poderá parar entre receive() chamadas.
Além disso, o contêiner só pode enviar solicitações após o intervalo de receive-timeout ter passado. Se o intervalo for superior a 10 minutos, o Service Bus fechará o link e impedirá que o ouvinte envie ou receba. Para obter mais informações, consulte a secção Link is closed de Erros AMQP no Azure Service Bus. Por padrão, o ouvinte usa um CachingConnectionFactory.
Se você precisar de um tempo limite de recebimento alto, certifique-se de usar o JmsPoolConnectionFactory.
Para mais informações sobre o problema de encerramento da ligação e como utilizar JmsPoolConnectionFactory, consulte a secção O MessageProducer foi encerrado devido a um erro irrecuperável.
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 repetidamente consumidas.
- As mensagens são colocadas na fila de letra morta após
MaxDeliveryCountExceeded, mesmo quando as mensagens são processadas sem erro ou exceção.
Análise de causa
Este problema geralmente ocorre quando o prefetch valor é superior à capacidade real de consumo, com o efeito de que demasiadas mensagens são pré-recolhidas para o buffer local à espera de serem consumidas. No entanto, as mensagens pré-carregadas são consideradas como despachadas em modo peek-lock do lado do Service Bus. Cada mensagem enviada tem um max-delivery-count atributo e lock-duration . No peek-lock modo de receção, as mensagens obtidas no buffer de pré-busca são adquiridas no buffer num estado bloqueado, com o relógio de tempo limite do bloqueio a funcionar. Se o buffer de pré-obtenção for grande e o processamento demorar tanto tempo que os bloqueios das mensagens expirem enquanto estas permanecem no buffer de pré-obtenção, a mensagem é tratada como abandonada e fica novamente disponível para ser obtida da fila.
Esse problema pode fazer com que a mensagem seja buscada no 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 (validamente bloqueado). Em seguida, quando essas cópias desatualizadas são removidas da fila, a aplicação passa a consumir repetidamente a mesma mensagem e não consegue processá-la até ao fim. Em outro caso, as mensagens repetidas são todas expiradas no buffer antes de serem consumidas. Neste caso, a mensagem no Service Bus acabará por ser movida para a fila de mensagens não entregues após ser excedido o número máximo de tentativas de entrega.
Para obter mais informações, consulte a secção Porque é que o pré-carregamento não é a opção predefinida? de Pré-carregar mensagens do Azure Service Bus.
Solução
Tenha cuidado com a configuração do prefetch para garantir que se adequa à capacidade de consumo. Você deve equilibrar a contagem máxima de pré-busca e a duração do bloqueio configurada na fila ou na assinatura de modo que o tempo limite de bloqueio exceda pelo menos o tempo de processamento cumulativo esperado da mensagem para o tamanho máximo do buffer de pré-busca, além de uma mensagem. Ao mesmo tempo, o tempo limite de bloqueio não deve ser tão longo que as mensagens possam exceder o respetivo tempo de vida máximo quando são acidentalmente descartadas, o que exige que o respetivo bloqueio expire antes de serem novamente entregues.
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 para a opção de pré-busca neste namespace do Service Bus. |
spring.jms.servicebus.prefetch.queue-prefetch |
O número de pré-busca para a fila. |
spring.jms.servicebus.prefetch.queue-browser-prefetch |
O número de pré-busca para o navegador de fila. |
spring.jms.servicebus.prefetch.topic-prefetch |
O número de pré-busca para o tópico. |
spring.jms.servicebus.prefetch.durable-topic-prefetch |
O número de pré-busca para o tópico durável. |
Como efetuar a liquidação AMQP no Service Bus?
O JMS suporta cinco tipos de disposição AMQP ao reconhecer mensagens para o agente de mensagens. Os valores suportados são ACCEPTED, REJECTED, RELEASED, MODIFIED_FAILEDe MODIFIED_FAILED_UNDELIVERABLE. Para obter mais informações, consulte a secção disposition AMQP e mapeamento de operações do Service Bus de Utilizar o Java Message Service 1.1 com o Azure Service Bus Standard e o AMQP 1.0.
Portanto, para preencher, abandonar, colocar letra morta, adiar ou liberar manualmente uma mensagem usando JmsListener, use as seguintes etapas:
Desative a sessão transacionada e use o modo ack CLIENT.
Para realizar esta tarefa, ou declara o seu próprio bean
JmsListenerContainerFactory e depois define as propriedades, ou faz o pós-processamento dodefinido no starter . O exemplo a seguir usa a abordagem de declarar outro feijão:@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, preencha 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
Desativar a configuração automática JMS do Service Bus
Descrição do problema
Alguns usuários importam alguns Spring Cloud Azure Starter para a configuração automática de um serviço do Azure diferente do Service Bus JMS. Eles também usam a estrutura Spring JMS sem a necessidade do Service Bus JMS. Em seguida, quando o aplicativo tenta iniciar, as seguintes exceções são lançadas:
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 configuração automática do Spring Cloud Azure são colocadas no mesmo módulo, portanto, qualquer Spring Cloud Azure Starter realmente importa toda essa configuração automática, que também inclui o Service Bus JMS. Em seguida, quando a aplicação usa a API do Spring JMS, cumpre a condição de autoconfiguração JMS do Service Bus e ativa-a. Em seguida, para os usuários que não pretendem usar spring-cloud-azure-starter-servicebus-jms, as condições de propriedade não são atendidas porque não há motivo para configurar o Service Bus for JMS. Esta situação faz com que as exceções sejam lançadas.
Solução
O Spring Cloud Azure for Service Bus JMS fornece uma propriedade para ativar ou desativar sua configuração automática. 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 das 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 seu bean personalizado de conversão de mensagens, como se mostra 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 a carga útil da mensagem. Ao serializar cada objeto Java para o payload de uma Message do Spring, o conversor armazena o tipo de payload numa propriedade da mensagem com o nome de propriedade registado em type-id-property-name. Em seguida, ao desserializar a mensagem, o conversor lê o ID de 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;
}
}
Deteção de duplicados
O Azure Service Bus suporta a deteção de duplicados, que utiliza a propriedade MessageId para identificar univocamente as mensagens e descartar os duplicados enviados para o Service Bus.
No entanto, para a API JMS, você não deve definir o ID da mensagem JMS, que é considerado ilegal nas especificações JMS. Portanto, esse recurso não é suportado atualmente para o Spring Cloud Azure Service Bus JMS Starter.
Para quaisquer atualizações futuras sobre esta funcionalidade, consulte a issue do GitHub.
Ativar o registo do transporte AMQP
Para obter mais informações, consulte a secção ativar o registo de transporte AMQP de Resolução de problemas do Service Bus.
Obter ajuda adicional
Para mais informações sobre formas de contactar o suporte, consulte Support na raiz do repositório.
Recursos para o inicializador JMS do Spring Cloud Azure Service Bus
- Utilizar o Azure Service Bus com JMS
- Usar JMS no Spring para acessar o Azure Service Bus
- Guia de migração para o Spring Cloud Azure 4.0
- Exemplo
Arquivando problemas do GitHub
Ao arquivar problemas do GitHub, os seguintes detalhes são solicitados:
- Configuração do Service Bus/Ambiente de namespace
- Qual é a camada do namespace (padrão ou premium)?
- Que tipo de entidade de mensagens está a ser utilizada (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 lento.)
- Código de reprodução e etapas
- Isso é importante, pois muitas vezes não conseguimos reproduzir o problema em nosso ambiente.
- Registos