Compartilhar via


Solucionar problemas do Barramento de Serviço do Azure

Este artigo trata de técnicas de investigação de falhas, simultaneidade, erros comuns para os tipos de credencial na biblioteca de clientes do Barramento de Serviço do Azure para Java e etapas de mitigação para resolver esses erros.

Habilitar e configurar o registro em log

Os SDK do Azure para Java oferecem uma história de log consistente para ajudar na solução de erros de aplicativos e para agilizar a resolução. Os logs produzidos capturam o fluxo de um aplicativo antes de acessarem o estado do terminal para ajudar a localizar o problema raiz. Para obter diretrizes sobre o registro em log, consulte Configurar o registro em 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 insights sobre o estado da biblioteca. As seções a seguir mostram configurações de log4j2 e logback de exemplo para reduzir o excesso de mensagens quando o log detalhado está habilitado.

Configurar Log4J 2

Use as seguintes etapas para configurar o Log4J 2:

  1. 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 Log4j2".
  2. Adicione log4j2.xml à sua pasta src/main/resources.

Configurar o logback

Use as seguintes etapas para configurar o logback:

  1. 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".
  2. Adicione logback.xml à sua pasta src/main/resources.

Habilitar o log de transporte do AMQP

Se a habilitação do log do cliente não for suficiente para diagnosticar seus problemas, será possível habilitar o log em um arquivo na biblioteca AMQP subjacente, Qpid Proton-J. O Qpid Proton-J usa java.util.logging. Você pode habilitar o registro criando um arquivo de configuração com o conteúdo exibido na próxima seção. Ou, defina proton.trace.level=ALL quaisquer opções de configuração desejadas para a implementação java.util.logging.Handler. Para obter as classes de implementação e suas opções, consulte Pacote java.util.logging na documentação do SDK do Java 8.

Para rastrear os quadros de transporte do AMQP, defina a variável de ambiente PN_TRACE_FRM=1.

Exemplo de arquivo logging.properties

O arquivo de configuração a seguir registra a saída de nível de rastreamento 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 registros de log

Uma maneira de diminuir o registro é alterar o detalhamento. Outra opção é 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 no ReceiveLinkHandler.
  • com.azure.messaging.servicebus.implementation

Simultaneidade em ServiceBusProcessorClient

ServiceBusProcessorClient permite que o aplicativo configure quantas chamadas devem ocorrer simultaneamente para o manipulador de mensagens. Essa configuração possibilita o processamento de várias mensagens paralelamente. Para obter um ServiceBusProcessorClient consumo de mensagens de uma entidade que não seja de sessão, o aplicativo pode configurar a simultaneidade desejada usando a API maxConcurrentCalls. 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, isso pode ocorrer porque o pool de threads não está dimensionado adequadamente.

ServiceBusProcessorClient usa threads de daemon do pool de threads boundedElastic do Reactor para invocar o manipulador de mensagens. O número máximo de threads simultâneos nesse 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 de maneira efetiva à simultaneidade desejada do aplicativo (maxConcurrentCalls ou maxConcurrentSessions vezes maxConcurrentCalls), você deve ter um boundedElastic valor de limite de pool maior do que a simultaneidade desejada. Você pode substituir o limite padrão definindo a propriedade do sistema reactor.schedulers.defaultBoundedElasticSize.

Você deve ajustar o pool de threads e a alocação de CPU caso a caso. No entanto, quando você substituir o limite do pool, como ponto de partida, estabeleça o limite de threads simultâneos a aproximadamente 20 a 30 por núcleo de CPU. Recomendamos que você estabeleça o limite de simultaneidade desejada por ServiceBusProcessorClient instância para aproximadamente 20 a 30. Crie o perfil e meça seu caso de uso específico e ajuste os aspectos de simultaneidade adequadamente. Para cenários de alta carga, execute várias ServiceBusProcessorClient instâncias em que cada instância é criada a partir de uma nova instância ServiceBusClientBuilder. Além disso, execute cada ServiceBusProcessorClient em um host dedicado, como um contêiner ou VM, de forma que o tempo de inatividade em um host não afete o processamento geral da mensagem.

Lembre-se de que a definição de um valor alto para o limite de pool em um host com poucos núcleos de CPU pode ter efeitos adversos. Alguns sinais de poucos recursos de CPU ou um pool com muitos threads em menos CPUs são: tempos limite frequentes, bloqueio perdido, deadlock ou taxa de transferência mais baixa. Se você estiver executando o aplicativo Java em um contêiner, recomendamos usar dois ou mais núcleos de vCPU. Não recomendamos a seleção de nada menos que 1 núcleo de vCPU durante a execução do aplicativo Java em ambientes conteinerizados. Para obter recomendações detalhadas sobre recursos, consulte Conteinerizar seus aplicativos Java.

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 Barramento de Serviço.

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 possui 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 compartilhado e o progresso de cada cliente depende da conclusão oportuna de seu trabalho na fila. O thread de E/S trata 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 de CPU baixa. Essa condição é descrita na seção anterior sobre simultaneidade, por exemplo, paralisação de clientes, tempo limite, bloqueio perdido ou lentidão no caminho de recuperação.

O SDK do Barramento de Serviço usa o reactor-executor-* padrão de nomenclatura para o thread de E/S de conexão. Quando o aplicativo experimenta o gargalo de conexão compartilhada, isso pode ser refletido no uso da CPU do thread de E/S. Além disso, no despejo de heap ou na memória ativa, 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 gargalo pode mostrar que o thread de E/S compartilhado está sobrecarregado com trabalhos pendentes.

Portanto, se a carga do aplicativo para um ponto de extremidade do Barramento de Serviço for relativamente alta, em termos de número geral de mensagens enviadas/recebidas ou tamanho da carga, 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 uma nova ServiceBusClientBuilder e construir um cliente a partir dela. No caso de carga extremamente alta para uma entidade específica, é provável que você queira 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 quando usam o ponto de extremidade personalizado do Gateway de Aplicativo

O endereço de ponto de extremidade personalizado refere-se a um endereço de ponto de extremidade HTTPS fornecido pelo aplicativo que pode ser resolvido para o Barramento de Serviço ou configurado para rotear o tráfego para o Barramento de Serviço. O Gateway de Aplicativo do Azure facilita a criação de um front-end HTTPS que encaminha o tráfego para o Barramento de Serviço. Você pode configurar o SDK do Barramento de Serviço para que um aplicativo use um endereço IP de front-end do Gateway de Aplicativo como o ponto de extremidade personalizado para se conectar ao Barramento de Serviço.

O Gateway de Aplicativo oferece várias políticas de segurança que oferecem suporte a 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 Barramento de Serviço não reconhece determinadas terminações TCP remotas pelo front-end do Gateway de Aplicativo, que usa TLSv1.0 como a versão mínima. Por exemplo, se o front-end enviar pacotes TCP FIN e ACK para fechar a conexão quando suas propriedades forem atualizadas, o SDK não poderá detectá-lo, portanto, ele não se reconectará e os clientes não poderão mais enviar ou receber mensagens. Essa interrupção só acontece ao usar o 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 Gateway de Aplicativo.

O suporte para TLSv1.0 e 1.1 em todos os Serviços do Azure já foi anunciado para ser encerrado em 31 de outubro de 2024, por isso, a transição para TLSv1.2 é altamente recomendada.

O bloqueio de mensagem ou sessão foi perdido

Uma fila do Barramento de Serviço ou uma assinatura de tópico tem uma duração de bloqueio definida em nível de recurso. Quando o cliente receptor recebe uma mensagem do recurso, o agente do Barramento de Serviço aplica um bloqueio inicial à mensagem. O bloqueio inicial tem 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 Barramento de Serviço liberará a mensagem para disponibilizá-la para outros destinatários. Se o aplicativo tentar concluir ou abandonar uma mensagem após a expiração do bloqueio, a chamada à 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 Barramento de Serviço oferece suporte à execução de uma tarefa de renovação de bloqueio em segundo plano que faz a renovação do bloqueio de mensagem continuamente a cada vez, antes que ele expire. Por padrão, a tarefa de renovação de bloqueio é executada por 5 minutos. É possível ajustar a duração da renovação do bloqueio usando ServiceBusReceiverClientBuilder.maxAutoLockRenewDuration(Duration). Se você passar o valor Duration.ZERO, a tarefa de renovação de bloqueio será desabilitada.

As seguintes listas descrevem alguns dos padrões de uso ou ambientes de host que podem levar ao erro de bloqueio perdido:

  • A tarefa de renovação de bloqueio está desabilitada 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 com a definição do 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 Barramento de Serviço, fila ou tópico, e as armazenará no buffer de pré-busca na memória. As mensagens continuam apenas no buffer de pré-busca na memória até que sejam recebidas no aplicativo. O cliente não amplia o bloqueio das mensagens enquanto elas estão no buffer de pré-busca. Se o processamento do aplicativo demorar tanto que os bloqueios de mensagens expiram 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 de host tem problemas de rede ocasionais, por exemplo, falha ou interrupção transitória da rede, que impedem que a tarefa de renovação de bloqueio renove o bloqueio a tempo.

  • O ambiente de host não tem CPUs suficientes ou tem escassez de ciclos de CPU intermitentemente, o que provoca atraso na execução da tarefa de renovação de bloqueio a tempo.

  • A hora do sistema host não é precisa, por exemplo, o relógio está distorcido, causando atraso na tarefa de renovação de bloqueio e impedindo-a de ser executada no tempo.

  • O thread de E/S de conexão está sobrecarregado, afetando sua capacidade de executar chamadas de rede de renovação de bloqueio a tempo. Os dois cenários seguintes podem causar esse problema:

    • O aplicativo está executando muitos clientes receptores compartilhando a mesma conexão. Para obter mais informações, consulte a seção Gargalo de compartilhamento de conexão.
    • O aplicativo configurou ServiceBusReceiverClient.receiveMessages ou ServiceBusProcessorClient para ter um grande maxMessages ou valores maxConcurrentCalls. Para obter mais informações, consulte a seção Simultaneidade no ServiceBusProcessorClient.

O número de tarefas de renovação de bloqueio no cliente é igual aos valores de parâmetro maxMessages ou maxConcurrentCalls definidos para ServiceBusProcessorClient ou ServiceBusReceiverClient.receiveMessages. Um grande número de tarefas de renovação de bloqueio que efetuam várias chamadas de rede também pode ter um efeito adverso na limitação de namespace do Barramento de Serviço.

Se o host não possuir recursos suficientes, o bloqueio ainda poderá ser perdido, mesmo que tenha apenas algumas tarefas de renovação de bloqueio em execução. Se você estiver executando o aplicativo Java em um contêiner, recomendamos usar dois ou mais núcleos de vCPU. Não recomendamos a seleção de nada menos que 1 núcleo de vCPU durante a execução dos aplicativos Java em ambientes conteinerizados. Para obter recomendações detalhadas sobre recursos, consulte Conteinerizar seus aplicativos Java.

As mesmas observações sobre bloqueios também são relevantes para uma fila do Barramento de Serviço ou uma assinatura de tópico que tem a sessão habilitada. Quando o cliente receptor se conecta a uma sessão no recurso, o agente aplica um bloqueio inicial à sessão. Para manter o bloqueio na sessão, a tarefa de renovação de bloqueio no cliente tem que continuar renovando o bloqueio de sessão antes que ele expire. Para obter um recurso habilitado para sessão, as partições subjacentes às vezes se movem para obter o balanceamento de carga entre os nós do Barramento de Serviço, por exemplo, quando novos nós são adicionados para compartilhar a carga. Quando isso ocorre, os bloqueios de sessão podem ser perdidos. Se o aplicativo tentar concluir ou abandonar uma mensagem após a perda do bloqueio da sessão, a chamada à API falhará com o erro com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver.

Atualização 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 Barramento de Serviço. 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 thread, remove bloqueios, otimiza o código em caminhos críticos e reduz as alocações de memória. Essas alterações resultam em uma taxa de transferência até 45 a 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 cálculos de pré-busca e crédito) e tratamento de erros aprimorado. Essas alterações resultam em melhor confiabilidade na presença de problemas transitórios em vários tipos de clientes.

Usando os clientes mais recentes

A nova estrutura subjacente com essas melhorias, na versão 7.15.x e posterior, é chamada de Pilha V2. 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 Pilha V2.

Por padrão, alguns dos tipos de cliente usam a Pilha V2, enquanto outros exigem a aceitação da Pilha V2. É possível realizar a aceitação ou recusa de uma pilha específica (V2 ou a geração anterior) para um tipo de cliente fornecendo valores com.azure.core.util.Configuration ao criar o cliente.

Por exemplo, o recebimento de sessão baseado em pilha V2 com ServiceBusSessionReceiverClient requer aceitação, conforme mostrado no seguinte exemplo:

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á habilitado por padrão para usar a Pilha V2 na versão mais recente 7.17.0. Para um cliente que não está na Pilha V2 por padrão, você pode usar o exemplo mostrado para aceitar.

Tipo de cliente Nome da configuração Está na pilha V2 por padrão?
Cliente remetente e de gerenciamento com.azure.messaging.servicebus.sendAndManageRules.v2 sim
Processador não-sessão e cliente receptor de reator com.azure.messaging.servicebus.nonSession.asyncReceive.v2 sim
Cliente receptor do processador de sessão com.azure.messaging.servicebus.session.processor.asyncReceive.v2 sim
Cliente receptor do reator de sessão com.azure.messaging.servicebus.session.reactor.asyncReceive.v2 sim
Cliente receptor síncrono não-sessão com.azure.messaging.servicebus.nonSession.syncReceive.v2 não
Cliente receptor síncrono da sessão com.azure.messaging.servicebus.session.syncReceive.v2 não

Como alternativa ao uso do com.azure.core.util.Configuration, você pode aceitar ou recusar definindo os mesmos nomes de configuração usando variáveis de ambiente ou propriedades do sistema.

Próximas etapas

Se as diretrizes de solução de problemas neste artigo não ajudarem a resolver problemas ao usar as bibliotecas de cliente do SDK do Azure para Java, recomendamos que você registre um problema no repositório GitHub do SDK do Azure para Java.