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 aborda técnicas de investigação de falhas, concorrência e erros comuns na biblioteca Azure Service Bus Java cliente. Use esta orientação para identificar as causas raízes e aplicar passos de mitigação para resolver os problemas do Azure Service Bus mais rapidamente.
Ativar e configurar o registo de logs
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 registos que produzes captam o fluxo de uma aplicação antes de atingires o estado terminal para ajudar a localizar o problema raiz. Para obter orientação sobre o registo de log, consulte Configurar o log no SDK do Azure para Java e Visão geral da resolução de problemas.
Além de habilitar o registro em log, definir o nível de log como VERBOSE ou DEBUG fornece informações sobre o estado da biblioteca. As seções a seguir mostram exemplos de configurações do log4j2 e do logback para reduzir o excesso de mensagens quando o registo detalhado está ativado.
Configurar o Log4J 2
Use as seguintes etapas para configurar o Log4J 2:
- Adicione as dependências no seupom.xml usando as do exemplo de registo pom.xml, na secçã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 ao seu pom.xml, utilizando as do pom.xml de exemplo de registo, na secção "Dependências necessárias para logback".
- Adicione
logback.xml à sua pasta src/main/resources.
Habilitar o log de transporte AMQP
Se ativar o registo do cliente não for suficiente para diagnosticar os seus problemas, pode ativar o registo em ficheiro 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 implementação java.util.logging.Handler. 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 variável de ambiente PN_TRACE_FRM=1.
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 corte de árvores
Uma maneira de reduzir o logging é 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.implementationcom.azure.core.amqp.implementation.handler- A exceção é que você pode ignorar a mensagem
onDeliverynoReceiveLinkHandler.
- A exceção é que você pode ignorar a mensagem
com.azure.messaging.servicebus.implementation
Concorrência em ServiceBusProcessorClient
ServiceBusProcessorClient Permite configurar quantas chamadas para o manipulador de mensagens acontecem simultaneamente. Esta configuração torna possível processar várias mensagens em paralelo. Para um ServiceBusProcessorClient que consome mensagens de uma entidade sem sessão, pode configurar a concorrência pretendida utilizando a API maxConcurrentCalls. Para uma entidade com sessões ativadas, a concorrência desejada é maxConcurrentSessions vezes maxConcurrentCalls.
Se observar menos chamadas simultâneas ao processador de mensagens do que o nível de concorrência configurado, isso pode dever-se ao facto de o pool de threads não ter a dimensão adequada.
ServiceBusProcessorClient usa threads de daemon do pool global de threads Reactor boundedElastic 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 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.
Ajuste o pool de threads e a alocação da CPU caso a caso. No entanto, ao substituir o limite do pool, como ponto de partida, limite os threads simultâneos a aproximadamente 20-30 por núcleo da CPU. Limite a concorrência desejada por ServiceBusProcessorClient instância a aproximadamente 20-30. Defina e meça o seu caso de uso específico e ajuste os aspetos de concorrência de acordo. Para cenários de alta carga, considere a execução de várias instâncias de 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.
Lembra-te que definir um valor elevado para o limite do pool num host com poucos núcleos de CPU tem 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 estiveres a correr a aplicação Java num contentor, usa dois ou mais núcleos vCPU. Não selecione menos do que 1 núcleo vCPU ao correr uma aplicação Java em ambientes containerizados. Para obter recomendações detalhadas sobre recursos, consulte Containerizar os seus aplicativos Java.
Gargalo de compartilhamento de conexão
Todos os clientes que crias a partir de uma instância partilhada ServiceBusClientBuilder partilham a mesma ligação ao namespace 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. Quando se partilha uma ligação, os clientes colocam as suas tarefas na fila de tarefas desta thread de E/S partilhada, e o progresso de cada cliente depende da execução atempada das suas tarefas 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 ligação partilhada acabar por acumular muito trabalho pendente para processar, os sintomas serão semelhantes aos de uma baixa utilização da CPU. Essa condição é descrita na seção anterior sobre simultaneidade - por exemplo, clientes parando, tempo de espera, bloqueio perdido ou lentidão no processo de recuperação.
O SDK do Service Bus usa o padrão de nomenclatura reactor-executor-* para o thread de E/S de conexão. Quando a aplicação sofre o gargalo de ligação partilhada, isso pode refletir-se na utilização da CPU da thread de I/O. Além disso, no dump do heap ou na memória ao vivo, o objeto ReactorDispatcher$workQueue é a fila de trabalho do thread de I/O. Uma longa fila de trabalho no instantâneo de memória durante o período de gargalo pode indicar que a thread de I/O partilhada está sobrecarregada com trabalhos pendentes.
Portanto, se a carga da aplicação para um ponto final do Service Bus for razoavelmente elevada em termos do número total de mensagens enviadas e recebidas ou do tamanho da carga útil, utilize uma instância de criação separada para cada cliente que criar. Por exemplo, para cada entidade - fila ou tópico - podes 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 final personalizado refere-se a um endereço HTTPS fornecido por uma aplicação que pode ser resolvido para o Service Bus ou ser configurado para encaminhar tráfego para o Service Bus. O Gateway de Aplicação do Azure facilita a criação de uma interface HTTPS que encaminha o tráfego para o Service Bus. Pode configurar o Service Bus SDK para uma aplicação usar um endereço IP front-end do Application Gateway como endpoint personalizado para se ligar 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 pré-definidas que aplicam o TLS 1.2 como versão mínima, e há políticas mais antigas que usam o TLS 1.0 como versão mínima. Aplica-se uma política TLS à interface HTTPS.
Atualmente, o Service Bus SDK não reconhece certas terminações remotas de TCP pela interface do Application Gateway, que usa TLS 1.0 como versão mínima. Por exemplo, se a interface envia pacotes TCP FIN e ACK para fechar a ligação quando atualiza as suas propriedades, o SDK não consegue detetar esses pacotes. Portanto, não se reconecta, e os clientes já não conseguem enviar ou receber mensagens. Esta paragem só acontece quando se usa o TLS 1.0 como versão mínima. Para mitigar este problema, utilize uma política de segurança que defina TLS 1.2 ou superior como versão mínima para a interface do Gateway de Aplicações.
O suporte para TLS 1.0 e 1.1 em todos os serviços do Azure já foi anunciado para terminar até 31 de outubro de 2024, por isso faça a transição para o TLS 1.2.
Mensagem ou bloqueio de sessão são perdidos
Uma fila ou subscrição de tópico do Service Bus tem uma duração de bloqueio definida ao 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 da mensagem não for renovado antes de expirar, o intermediário do Service Bus liberta a mensagem para a disponibilizar a outros destinatários. 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 valor Duration.ZERO, a tarefa de renovação de bloqueio será desabilitada.
A lista seguinte descreve alguns dos padrões de utilização ou ambientes anfitriões que podem levar ao erro de perda do 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. Note que, se a duração da renovação do bloqueio não estiver definida explicitamente, por defeito passa a ser 5 minutos.
A aplicação ativa a funcionalidade de Prefetch definindo o valor do prefetch para um inteiro positivo usando
ServiceBusReceiverClientBuilder.prefetchCount(prefetch). Quando a funcionalidade de Prefetch está ativada, o cliente recupera o número de mensagens igual à pré-busca da entidade Service Bus - fila ou tópico - e armazena-as no buffer de pré-busca em 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 da aplicação demorar tanto tempo que os bloqueios das mensagens expirem enquanto permanecem na memória intermédia de pré-carregamento, a aplicação poderá obter as mensagens com o 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 sofre intermitentemente de escassez de ciclos de CPU, atrasando a execução pontual da tarefa de renovação de bloqueio.
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 a sua capacidade de executar chamadas de rede para 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.
- A aplicação configura
ServiceBusReceiverClient.receiveMessagesouServiceBusProcessorClientpara ter valores elevados demaxMessagesoumaxConcurrentCalls. Para obter mais informações, consulte a seção Simultaneidade em ServiceBusProcessorClient.
Um padrão de aplicação comum que aumenta a probabilidade de um erro de perda de bloqueio envolve o agendamento de tarefas de renovação de bloqueio de longa duração - por exemplo, tarefas com duração de várias horas. Como mencionado anteriormente, vários fatores fora do controlo de um cliente do Service Bus podem interferir na renovação bem-sucedida do bloqueio, portanto, as conceções de aplicações devem evitar assumir que a renovação será garantida por longos períodos. Para evitar ter que reprocessar operações de longa duração, 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âmetros maxMessages 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 dispuser de recursos suficientes, o bloqueio ainda pode ser perdido, mesmo que estejam em execução apenas algumas tarefas de renovação do bloqueio. Se estiveres a correr a aplicação Java num contentor, usa dois ou mais núcleos vCPU. Não selecione menos do que 1 núcleo vCPU ao executar aplicações Java em ambientes containerizados. Para obter recomendações detalhadas sobre recursos, consulte Containerizar os seus aplicativos Java.
As mesmas observações sobre bloqueios também são relevantes para uma fila do Service Bus ou para uma assinatura de tópico com sessão ativada. 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.
Próximos passos
Se as orientações de resolução de problemas neste artigo não ajudarem a resolver os problemas ao utilizar as bibliotecas de cliente do SDK do Azure para Java, submeta um problema no repositório do GitHub do SDK do Azure para Java.