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 aborda técnicas de investigação de falhas, simultaneidade, erros comuns para os tipos de credencial na biblioteca de clientes 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 log consistente para ajudar na solução de problemas de erros do aplicativo e para ajudar a agilizar sua resolução. Os logs produzidos capturam o fluxo de um aplicativo antes de atingir 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:
- 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".
- 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 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 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 implementação do 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 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 o desmatamento
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 única exceção é que você pode ignorar a mensagem de
onDelivery
emReceiveLinkHandler
.
- A única exceção é que você pode ignorar a mensagem de
com.azure.messaging.servicebus.implementation
Concorrência no ServiceBusProcessorClient
ServiceBusProcessorClient
permite que o aplicativo configure quantas chamadas para o manipulador de mensagens devem ocorrer simultaneamente. Essa configuração possibilita processar várias mensagens em paralelo. Para um ServiceBusProcessorClient
que consome mensagens de uma entidade sem 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, pode ser porque o pool de threads não é 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
dê suporte efetivamente à simultaneidade desejada do aplicativo (maxConcurrentCalls
ou maxConcurrentSessions
vezes maxConcurrentCalls
), você deve ter um valor de limite de pool boundedElastic
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ê limite a simultaneidade desejada por instância ServiceBusProcessorClient
para aproximadamente de 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, considere a execução de várias instâncias ServiceBusProcessorClient
em que cada instância é criada a partir de uma nova instância ServiceBusClientBuilder
. Além disso, considere executar cada ServiceBusProcessorClient
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.
Tenha em mente 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 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 selecionar nada menos que 1 núcleo de vCPU ao executar o aplicativo Java em ambientes em contêineres. Para obter recomendações detalhadas sobre alocação de recursos, consulte Containerizar 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 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 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, ele 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 uma carga extremamente alta para uma entidade específica, talvez 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 dão suporte a diferentes versões de protocolo TLS. Há políticas predefinidas que impõem o 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, ACK para fechar a conexão quando suas propriedades forem atualizadas, o SDK não conseguirá detectá-los, portanto, ele não se reconectará e os clientes não poderão mais enviar ou receber mensagens. Essa parada 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.
Mensagem ou bloqueio de 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 dura pelo período de tempo definido 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. Você pode ajustar a duração da renovação de 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 de 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 for definida explicitamente, ela será padrão para 5 minutos.
O aplicativo ativou o recurso Prefetch definindo o valor de pré-busca para 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 permanecem no buffer de pré-busca até que sejam 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 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 Prefetch não é a opção padrão?O ambiente do host tem problemas ocasionais de rede - por exemplo, falha ou interrupção de rede transitória - que impedem que a tarefa de renovação de bloqueio renove o bloqueio a tempo.
O ambiente do host não tem CPUs suficientes ou sofre de escassez de ciclos de CPU periodicamente, o que atrasa a execução da tarefa de renovação do bloqueio no tempo previsto.
A hora do sistema de host não é precisa - por exemplo, o relógio está desajustado - atrasando a tarefa de renovação de bloqueio e impedindo que ela seja executada 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 a tempo. Os dois cenários a seguir podem causar esse problema:
- O aplicativo está executando muitos clientes de recepção que compartilham a mesma conexão. Para obter mais informações, consulte a seção Gargalo de compartilhamento de conexão.
- O aplicativo configurou
ServiceBusReceiverClient.receiveMessages
ouServiceBusProcessorClient
para ter grandes valoresmaxMessages
oumaxConcurrentCalls
. Para obter mais informações, consulte a seção Simultaneidade no ServiceBusProcessorClient.
Um padrão de aplicativo comum que aumenta a probabilidade de um erro de bloqueio perdido envolve o agendamento de tarefas de renovação de bloqueio de execução longa - por exemplo, tarefas com durações que abrangem várias horas. Conforme mencionado anteriormente, vários fatores fora do controle de um cliente do Barramento de Serviço podem interferir na renovação bem-sucedida do bloqueio, portanto, os designs de aplicativos devem evitar assumir a renovação garantida por longos períodos. Para evitar a necessidade de reprocessar operações de execução longa, considere dividir o trabalho em partes menores ou implementar a lógica de ponto de verificação idempotente.
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 tiver recursos suficientes, o bloqueio ainda poderá ser perdido mesmo se houver 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 selecionar nada menos que 1 núcleo de vCPU ao executar aplicativos Java em ambientes em contêineres. Para obter recomendações detalhadas sobre alocação de recursos, consulte Containerizar 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 precisa continuar renovando o bloqueio de sessão antes de expirar. 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 acontece, os bloqueios de sessão podem ser perdidos. Se o aplicativo tentar concluir ou abandonar uma mensagem depois que o bloqueio da sessão for perdido, a chamada à API falhará com o erro com.azure.messaging.servicebus.ServiceBusException: The session lock was lost. Request a new session receiver
.
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.