Solucionar problemas do Barramento de Serviço do Azure
Este artigo aborda técnicas de investigação de falhas, simultaneidade, erros comuns para os tipos de credenciais na biblioteca de cliente Java do Barramento de Serviço do Azure e etapas de mitigação para resolver esses erros.
Habilitar e configurar o registro em log
O SDK do Azure para Java oferece uma história de registro consistente para ajudar na solução de erros de aplicativos e ajudar a agilizar sua resolução. Os logs produzidos capturam o fluxo de um aplicativo antes de atingir o estado terminal para ajudar a localizar o problema raiz. Para obter orientação sobre o registro em log, consulte Configurar o log no SDK do Azure para Java e Visão geral da solução de problemas.
Além de habilitar o registro em log, definir o nível de log para VERBOSE
ou DEBUG
fornece informações sobre o estado da biblioteca. As seções a seguir mostram exemplos de log4j2 e configurações de logback para reduzir o excesso de mensagens quando o log detalhado está habilitado.
Configurar o Log4J 2
Use as seguintes etapas para configurar o Log4J 2:
- Adicione as dependências em seu pom.xml usando as do pom.xml de exemplo de registro em log, na seção "Dependências necessárias para Log4j2".
- Adicione log4j2.xml à sua pasta src/main/resources.
Configurar o logback
Use as seguintes etapas para configurar o logback:
- Adicione as dependências em seu pom.xml usando as do pom.xml de exemplo de log, na seção "Dependências necessárias para logback".
- Adicione logback.xml à sua pasta src/main/resources.
Habilitar o log de transporte AMQP
Se habilitar o log do cliente não for suficiente para diagnosticar seus problemas, você poderá habilitar o registro em log em um arquivo na biblioteca AMQP subjacente, Qpid Proton-J. Qpid Proton-J usa java.util.logging
. Você pode habilitar o registro em log criando um arquivo de configuração com o conteúdo mostrado na próxima seção. Ou, defina proton.trace.level=ALL
e as opções de configuração desejadas para a java.util.logging.Handler
implementação. Para obter as classes de implementação e suas opções, consulte Package java.util.logging na documentação do Java 8 SDK.
Para rastrear os quadros de transporte AMQP, defina a PN_TRACE_FRM=1
variável de ambiente.
Exemplo de arquivo logging.properties
O seguinte arquivo de configuração registra a saída de nível TRACE do Proton-J para o arquivo 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
Reduzir o registo
Uma maneira de diminuir o registro em log é alterar a verbosidade. Outra maneira é adicionar filtros que excluem logs de pacotes de nomes de logger como com.azure.messaging.servicebus
ou com.azure.core.amqp
. Para obter exemplos, consulte os arquivos XML nas seções Configurar Log4J 2 e Configurar logback .
Quando você envia um bug, as mensagens de log das classes nos seguintes pacotes são interessantes:
com.azure.core.amqp.implementation
com.azure.core.amqp.implementation.handler
- A exceção é que você pode ignorar a
onDelivery
mensagem noReceiveLinkHandler
.
- A exceção é que você pode ignorar a
com.azure.messaging.servicebus.implementation
Simultaneidade em ServiceBusProcessorClient
ServiceBusProcessorClient
Permite que o aplicativo configure quantas chamadas para o manipulador de mensagens devem acontecer simultaneamente. Esta configuração torna possível processar várias mensagens em paralelo. Para um ServiceBusProcessorClient
consumo de mensagens de uma entidade que não seja de sessão, o aplicativo pode configurar a simultaneidade desejada usando a maxConcurrentCalls
API. Para uma entidade habilitada para sessão, a simultaneidade desejada é maxConcurrentSessions
vezes maxConcurrentCalls
.
Se o aplicativo observar menos chamadas simultâneas para o manipulador de mensagens do que a simultaneidade configurada, pode ser porque o pool de threads não é dimensionado adequadamente.
ServiceBusProcessorClient
usa threads de daemon do pool de threads global boundedElastic do Reator para invocar o manipulador de mensagens. O número máximo de threads simultâneos neste pool é limitado por um limite. Por padrão, esse limite é dez vezes o número de núcleos de CPU disponíveis. Para que o ServiceBusProcessorClient
suporte efetivamente a simultaneidade desejada do aplicativo (maxConcurrentCalls
ou maxConcurrentSessions
vezes maxConcurrentCalls
), você deve ter um boundedElastic
valor de limite de pool que seja maior do que a simultaneidade desejada. Você pode substituir o limite padrão definindo a propriedade reactor.schedulers.defaultBoundedElasticSize
do sistema .
Você deve ajustar o pool de threads e a alocação da CPU caso a caso. No entanto, quando você substituir a tampa do pool, como ponto de partida, limite os threads simultâneos para aproximadamente 20-30 por núcleo da CPU. Recomendamos que você limite a simultaneidade desejada por ServiceBusProcessorClient
instância para aproximadamente 20-30. Crie o perfil e meça seu caso de uso específico e ajuste os aspetos de simultaneidade de acordo. Para cenários de alta carga, considere a execução de várias ServiceBusProcessorClient
instâncias em que cada instância é criada a partir de uma nova ServiceBusClientBuilder
instância. Além disso, considere executar cada ServiceBusProcessorClient
um em um host dedicado - como um contêiner ou VM - para que o tempo de inatividade em um host não afete o processamento geral de mensagens.
Lembre-se de que definir um valor alto para o limite de pool em um host com poucos núcleos de CPU teria efeitos adversos. Alguns sinais de baixos recursos de CPU ou um pool com muitos threads em menos CPUs são: tempos limite frequentes, bloqueio perdido, deadlock ou menor taxa de transferência. Se você estiver executando o aplicativo Java em um contêiner, recomendamos o uso de dois ou mais núcleos vCPU. Não recomendamos selecionar nada menos que 1 núcleo vCPU ao executar o aplicativo Java em ambientes conteinerizados. Para obter recomendações detalhadas sobre recursos, consulte Containerize your Java applications.
Gargalo de compartilhamento de conexão
Todos os clientes criados a partir de uma instância compartilhada ServiceBusClientBuilder
compartilham a mesma conexão com o namespace do Service Bus.
O uso de uma conexão compartilhada permite operações de multiplexação entre clientes em uma conexão, mas o compartilhamento também pode se tornar um gargalo se houver muitos clientes ou se os clientes juntos gerarem alta carga. Cada conexão tem um thread de E/S associado a ela. Ao compartilhar a conexão, os clientes colocam seu trabalho na fila de trabalho desse thread de E/S compartilhada e o progresso de cada cliente depende da conclusão oportuna de seu trabalho na fila. O thread de E/S lida com o trabalho enfileirado em série. Ou seja, se a fila de trabalho do thread de E/S de uma conexão compartilhada acabar com muito trabalho pendente para lidar, os sintomas serão semelhantes aos da CPU baixa. Essa condição é descrita na seção anterior sobre simultaneidade - por exemplo, clientes parando, tempo limite, bloqueio perdido ou lentidão no caminho de recuperação.
O SDK do Service Bus usa o reactor-executor-*
padrão de nomenclatura para o thread de E/S de conexão. Quando o aplicativo enfrenta o afunilamento de conexão compartilhada, ele pode ser refletido no uso da CPU do thread de E/S. Além disso, no despejo de pilha ou na memória dinâmica, o objeto ReactorDispatcher$workQueue
é a fila de trabalho do thread de E/S. Uma longa fila de trabalho no instantâneo de memória durante o período de afunilamento pode indicar que o thread de E/S compartilhado está sobrecarregado com trabalhos pendentes.
Portanto, se a carga do aplicativo para um ponto de extremidade do Service Bus for razoavelmente alta em termos de número total de mensagens enviadas recebidas ou tamanho da carga útil, você deverá usar uma instância de construtor separada para cada cliente que você criar. Por exemplo, para cada entidade - fila ou tópico - você pode criar um novo ServiceBusClientBuilder
e construir um cliente a partir dele. No caso de carga extremamente alta para uma entidade específica, convém criar várias instâncias de cliente para essa entidade ou executar clientes em vários hosts - por exemplo, contêineres ou VMs - para balancear a carga.
Os clientes param ao usar o ponto de extremidade personalizado do Application Gateway
O endereço de ponto de extremidade personalizado refere-se a um endereço de ponto de extremidade HTTPS fornecido pelo aplicativo resolúvel para o Service Bus ou configurado para rotear o tráfego para o Service Bus. O Gateway de Aplicativo do Azure facilita a criação de um front-end HTTPS que encaminha o tráfego para o Service Bus. Você pode configurar o SDK do Service Bus para um aplicativo usar um endereço IP front-end do Application Gateway como o ponto de extremidade personalizado para se conectar ao Service Bus.
O Application Gateway oferece várias políticas de segurança que suportam diferentes versões do protocolo TLS. Existem políticas predefinidas que impõem TLSv1.2 como a versão mínima, também existem políticas antigas com TLSv1.0 como a versão mínima. O front-end HTTPS terá uma política TLS aplicada.
No momento, o SDK do Service Bus não reconhece determinadas terminações TCP remotas pelo front-end do Application Gateway, que usa TLSv1.0 como a versão mínima. Por exemplo, se o front-end enviar pacotes TCP FIN, ACK para fechar a conexão quando suas propriedades forem atualizadas, o SDK não poderá detetá-lo, portanto, não se reconectará e os clientes não poderão mais enviar ou receber mensagens. Tal parada só acontece ao usar TLSv1.0 como a versão mínima. Para atenuar, use uma política de segurança com TLSv1.2 ou superior como a versão mínima para o front-end do Application Gateway.
O suporte para TLSv1.0 e 1.1 em todos os Serviços do Azure já está anunciado para terminar em 31 de outubro de 2024, portanto, a transição para TLSv1.2 é altamente recomendada.
Mensagem ou bloqueio de sessão é perdido
Uma fila ou assinatura de tópico do Service Bus tem uma duração de bloqueio definida no nível do recurso. Quando o cliente recetor extrai uma mensagem do recurso, o agente do Service Bus aplica um bloqueio inicial à mensagem. O bloqueio inicial dura a duração do bloqueio definida no nível do recurso. Se o bloqueio de mensagem não for renovado antes de expirar, o agente do Service Bus liberará a mensagem para disponibilizá-la para outros recetores. Se o aplicativo tentar concluir ou abandonar uma mensagem após a expiração do bloqueio, a chamada de API falhará com o erro com.azure.messaging.servicebus.ServiceBusException: The lock supplied is invalid. Either the lock expired, or the message has already been removed from the queue
.
O cliente do Service Bus oferece suporte à execução de uma tarefa de renovação de bloqueio em segundo plano que renova o bloqueio de mensagem continuamente cada vez antes que ele expire. Por padrão, a tarefa de renovação de bloqueio é executada por 5 minutos. Você pode ajustar a duração da renovação do bloqueio usando ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration)
. Se você passar o Duration.ZERO
valor, a tarefa de renovação de bloqueio será desabilitada.
As listas a seguir descrevem alguns dos padrões de uso ou ambientes de host que podem levar ao erro de perda de bloqueio:
A tarefa de renovação de bloqueio está desativada e o tempo de processamento de mensagens do aplicativo excede a duração do bloqueio definida no nível do recurso.
O tempo de processamento de mensagens do aplicativo excede a duração da tarefa de renovação de bloqueio configurada. Observe que, se a duração da renovação do bloqueio não estiver definida explicitamente, o padrão será de 5 minutos.
O aplicativo ativou o recurso de pré-busca definindo o valor de pré-busca como um inteiro positivo usando
ServiceBusReceiverClientBuilder.prefetchCount(prefetch)
. Quando o recurso de pré-busca estiver habilitado, o cliente recuperará o número de mensagens igual à pré-busca da entidade do Service Bus - fila ou tópico - e as armazenará no buffer de pré-busca na memória. As mensagens permanecem no buffer de pré-busca até serem recebidas no aplicativo. O cliente não estende o bloqueio das mensagens enquanto elas estão no buffer de pré-busca. Se o processamento do aplicativo demorar tanto que os bloqueios de mensagem expirarem enquanto permanecem no buffer de pré-busca, o aplicativo poderá adquirir as mensagens com um bloqueio expirado. Para obter mais informações, consulte Por que a pré-busca não é a opção padrão?O ambiente host tem problemas de rede ocasionais - por exemplo, falha de rede transitória ou interrupção - que impedem que a tarefa de renovação de bloqueio renove o bloqueio a tempo.
O ambiente host não tem CPUs suficientes ou tem escassez de ciclos de CPU intermitentemente, o que atrasa a execução da tarefa de renovação de bloqueio no prazo.
A hora do sistema host não é precisa - por exemplo, o relógio está distorcido - atrasando a tarefa de renovação de bloqueio e impedindo-a de funcionar a tempo.
O thread de E/S de conexão está sobrecarregado, afetando sua capacidade de executar chamadas de rede de renovação de bloqueio no prazo. Os dois cenários a seguir podem causar esse problema:
- O aplicativo está executando muitos clientes recetores compartilhando a mesma conexão. Para obter mais informações, consulte a seção Afunilamento de compartilhamento de conexão.
- O aplicativo configurou
ServiceBusReceiverClient.receiveMessages
ouServiceBusProcessorClient
para ter um grandemaxMessages
oumaxConcurrentCalls
valores. Para obter mais informações, consulte a seção Simultaneidade em ServiceBusProcessorClient .
O número de tarefas de renovação de bloqueio no cliente é igual aos maxMessages
valores de parâmetro ou maxConcurrentCalls
definidos para ServiceBusProcessorClient
ou ServiceBusReceiverClient.receiveMessages
. Um alto número de tarefas de renovação de bloqueio que fazem várias chamadas de rede também pode ter um efeito adverso na limitação do namespace do Service Bus.
Se o host não tiver recursos suficientes, o bloqueio ainda poderá ser perdido, mesmo que haja apenas algumas tarefas de renovação de bloqueio em execução. Se você estiver executando o aplicativo Java em um contêiner, recomendamos o uso de dois ou mais núcleos vCPU. Não recomendamos selecionar nada menos que 1 núcleo vCPU ao executar aplicativos Java em ambientes conteinerizados. Para obter recomendações detalhadas sobre recursos, consulte Containerize your Java applications.
As mesmas observações sobre bloqueios também são relevantes para uma fila do Service Bus ou uma assinatura de tópico que tenha sessão habilitada. Quando o cliente recetor se conecta a uma sessão no recurso, o broker aplica um bloqueio inicial à sessão. Para manter o bloqueio na sessão, a tarefa de renovação de bloqueio no cliente tem de continuar a renovar o bloqueio de sessão antes de expirar. Para um recurso habilitado para sessão, as partições subjacentes às vezes se movem para obter o balanceamento de carga entre nós do Service Bus - por exemplo, quando novos nós são adicionados para compartilhar a carga. Quando isso acontece, os bloqueios de sessão podem ser perdidos. Se o aplicativo tentar concluir ou abandonar uma mensagem depois que o bloqueio de sessão for perdido, a chamada de API falhará com o erro com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver
.
Atualize para 7.15.x ou mais recente
Se você encontrar algum problema, primeiro tente resolvê-lo atualizando para a versão mais recente do SDK do Service Bus. A versão 7.15.x é uma grande reformulação, resolvendo problemas de desempenho e confiabilidade de longa data.
A versão 7.15.x e posterior reduz o salto de threads, remove bloqueios, otimiza o código em caminhos quentes e reduz as alocações de memória. Essas alterações resultam em uma taxa de transferência até 45-50 vezes maior no ServiceBusProcessorClient
.
A versão 7.15.x e posterior também vem com várias melhorias de confiabilidade. Ele aborda várias condições de corrida (como pré-busca e cálculos de crédito) e melhorou o tratamento de erros. Essas alterações resultam em maior confiabilidade na presença de problemas transitórios em vários tipos de clientes.
Utilizar os clientes mais recentes
A nova estrutura subjacente com essas melhorias - na versão 7.15.x e posterior - é chamada de V2-Stack. Esta linha de lançamento inclui a geração anterior da pilha subjacente - a pilha que a versão 7.14.x usa - e a nova V2-Stack.
Por padrão, alguns dos tipos de cliente usam o V2-Stack, enquanto outros exigem o opt-in V2-Stack. Você pode realizar o opt-in ou opt-out de uma pilha específica (V2 ou a geração anterior) para um tipo de cliente fornecendo com.azure.core.util.Configuration
valores quando você cria o cliente.
Por exemplo, a sessão baseada em V2-Stack recebe com ServiceBusSessionReceiverClient
requer opt-in, conforme mostrado no exemplo a seguir:
ServiceBusSessionReceiverClient sessionReceiver = new ServiceBusClientBuilder()
.connectionString(Config.CONNECTION_STRING)
.configuration(new com.azure.core.util.ConfigurationBuilder()
.putProperty("com.azure.messaging.servicebus.session.syncReceive.v2", "true") // 'false' by default, opt-in for V2-Stack.
.build())
.sessionReceiver()
.queueName(Config.QUEUE_NAME)
.buildClient();
A tabela a seguir lista os tipos de cliente e os nomes de configuração correspondentes e indica se o cliente está atualmente habilitado por padrão para usar o V2-Stack na versão 7.17.0 mais recente. Para um cliente que não está na V2-Stack por padrão, você pode usar o exemplo mostrado para aceitar.
Tipo de cliente | Nome da configuração | Está no V2-Stack por padrão? |
---|---|---|
Remetente e cliente de gerenciamento | com.azure.messaging.servicebus.sendAndManageRules.v2 |
sim |
Cliente do recetor do reator e do processador que não é de sessão | com.azure.messaging.servicebus.nonSession.asyncReceive.v2 |
sim |
Cliente recetor do processador de sessão | com.azure.messaging.servicebus.session.processor.asyncReceive.v2 |
sim |
Cliente recetor do reator de sessão | com.azure.messaging.servicebus.session.reactor.asyncReceive.v2 |
sim |
Cliente recetor síncrono sem sessão | com.azure.messaging.servicebus.nonSession.syncReceive.v2 |
não |
Cliente recetor síncrono de sessão | com.azure.messaging.servicebus.session.syncReceive.v2 |
não |
Como alternativa ao uso com.azure.core.util.Configuration
do , você pode fazer o opt-in ou opt-out definindo os mesmos nomes de configuração usando variáveis de ambiente ou propriedades do sistema.
Próximos passos
Se as diretrizes de solução de problemas neste artigo não ajudarem a resolver problemas quando você usa o SDK do Azure para bibliotecas de cliente Java, recomendamos que você registre um problema no repositório do SDK do Azure para Java GitHub.