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 para do Barramento de Serviço do Azure quando a conexão e o link AMQP estão ativos, mas nenhuma chamada - por exemplo, enviar ou receber chamadas - é feita usando o link por 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 iniciador JMS do Service Bus, o CachingConnectionFactory JmsProducer
está ocioso por mais de 10 minutos, mas menos de 15, o link que o produtor armazenado em cache está ocupando foi fechado. As mensagens não podem ser enviadas durante este intervalo. Então, depois de 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 desanexação de link aplicando o JmsPoolConnectionFactory
, que agrupa Connection
, Session
e MessageProducer
e 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}
Uso de spring.jms.servicebus.idle-timeout
As propriedades de tempo limite ocioso configuram o de 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 limite de tempo limite ocioso. 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 Barramento de Serviço do Azure dá suporte ao processamento atrasado 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
, prefetchSize
e 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
receiveLocalOnly
oureceiveNoWaitLocalOnly
está definida comofalse
, que é a opção padrão. Um valorfalse
aqui ainda resulta no envio de solicitações pull para o servidor, porque ele não sonda apenas o consumidor local.A configuração
receiveTimeout
determina 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 se torna uma Notificação Assíncrona consumidor que não recebe mensagens do corretor, mas mantém uma quantidade alvo de crédito de link. O valor é decidido por um imóvel de pré-busca. À medida que o Service Bus (remetente) envia mensagens, o crédito de link do remetente diminui e, quando o crédito de link do remetente cai abaixo de um limite, o cliente (destinatário) envia uma solicitação ao servidor para aumentar o crédito de link do remetente de volta ao valor 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 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 recebimento determina a estratégia por quanto tempo o consumidor bloqueia para aguardar um resultado de pull. Assim, ao estender o tempo limite, você pode reduzir a frequência de tração e, em seguida, reduzir o número de solicitações de pull quando escolher o modo pull. 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
null
se nenhuma mensagem estiver disponível.
Observação
Um alto valor de tempo limite pode trazer alguns efeitos colaterais. Por exemplo, um valor de tempo limite alto também estenderá o tempo em que o thread principal está em um status de bloco. 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 seçã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 obter mais informações sobre o problema de fechamento de link e como usá-JmsPoolConnectionFactory
, consulte a seção O MessageProducer foi fechado devido a um erro irrecuperável seção.
Problema de pré-busca
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
Esse problema geralmente acontece quando o valor de de pré-busca de
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 retiradas da fila, o aplicativo consome a mesma mensagem repetidamente e não consegue concluí-las. Em outro caso, as mensagens repetidas são todas expiradas no buffer antes de serem consumidas. Nesse caso, a mensagem no Service Bus será eventualmente movida para a fila de mensagens mortas depois que a contagem máxima de entrega for excedida.
Para obter mais informações, consulte a Por que a pré-busca não é a opção padrão? seção de Prefetch mensagens do Barramento de Serviço do Azure.
Solução
Tenha cuidado com a configuração da pré-busca para garantir que ela se encaixe na 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 seu tempo máximo de vida quando são acidentalmente descartadas, exigindo assim que seu bloqueio expire antes de ser entregue novamente.
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 realizar a disposição AMQP para o 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_FAILED
e MODIFIED_FAILED_UNDELIVERABLE
. Para obter mais informações, consulte a seção de mapeamento de disposição AMQP e operação do Service Bus de Usar o Java Message Service 1.1 com o padrão do Azure Service Bus 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 essa tarefa, declare seu próprio
JmsListenerContainerFactory bean e, em seguida, defina as propriedades ou pós processe odefinido noinicial . 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 o aplicativo usa a API do Spring JMS, ele atende à condição de de autoconfiguração JMS do Service Bus e o aciona. 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 seu bean 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 ID do 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 uma carga útil do Spring Message, o conversor armazena o tipo de carga em uma propriedade message com o nome da propriedade registrado por 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 Barramento de Serviço do Azure dá suporte a de deteção de duplicados , que aplica a propriedade MessageId
para identificar mensagens exclusivamente e descartar as duplicatas enviadas ao 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 obter mais atualizações para esse recurso, consulte o problema do do GitHub.
Habilitar o 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 Service Bus.
Obter ajuda adicional
Para obter mais informações sobre maneiras de entrar em contato com suporte, consulte de suporte na raiz do repositório.
Recursos para o Spring Cloud Azure Service Bus JMS starter
- Usar o Barramento de Serviço do Azure 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á 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 lento.)
- Código de reprodução e etapas
- Isso é importante, pois muitas vezes não conseguimos reproduzir o problema em nosso ambiente.
- Registos