Compartilhar via


Transferências de mensagem, bloqueios e liquidação

A funcionalidade mais central de um agente de mensagens, como o Barramento de Serviço, é aceitar mensagens em uma fila ou tópico e mantê-las disponíveis para recuperação posterior. Enviar é o termo normalmente usado para a transferência de uma mensagem para o agente de mensagens. Receber é o termo normalmente usado para a transferência de uma mensagem para um cliente que vai recuperá-la.

Quando um cliente envia uma mensagem, ele geralmente quer saber se a mensagem foi transferida e aceita corretamente pelo corretor ou se ocorreu algum tipo de erro. Essa confirmação positiva ou negativa define o entendimento entre o cliente e o agente sobre o estado de transferência da mensagem. Portanto, ela é chamada de liquidação.

Da mesma forma, quando o corretor transfere uma mensagem para um cliente, o corretor e o cliente querem estabelecer um entendimento sobre se a mensagem foi processada com êxito e, portanto, pode ser removida, ou se a entrega ou o processamento da mensagem falhou e, por isso, a mensagem pode ter que ser entregue novamente.

Liquidando operações de envio

Usando um dos clientes de API do Barramento de Serviço com suporte, as operações de envio para o Barramento de Serviço são sempre explicitamente liquidadas, o que significa que a operação da API aguarda a chegada de um resultado de aceitação do Barramento de Serviço para concluir a operação de envio.

Se a mensagem tiver sido rejeitada pelo Barramento de Serviço, a rejeição conterá um indicador de erro e um texto com uma tracking-id dentro dele. O motivo da rejeição também inclui informações sobre a possibilidade de repetição da operação com algum grau de êxito. No cliente, essas informações são transformadas em uma exceção e geradas para o chamador da operação de envio. Se a mensagem foi aceita, a operação é concluída silenciosamente.

O AMQP (Advanced Messaging Queuing Protocol) é o único protocolo com suporte para clientes .NET Standard, Java, JavaScript, Python e Go. Para clientes .NET Framework, você pode usar o Protocolo SBMP ou AMQP. Quando você usa o protocolo AMQP, as transferências de mensagens e os acordos são em pipeline e assíncronos. Recomendamos que você use as variantes de API do modelo de programação assíncrona.

Em 30 de setembro de 2026, desativaremos as bibliotecas do SDK do Barramento de Serviço do Azure WindowsAzure.ServiceBus, Microsoft.Azure.ServiceBus e com.microsoft.azure.servicebus, que não estão em conformidade com as diretrizes do SDK do Azure. Também encerraremos o suporte ao protocolo SBMP, portanto, ele não poderá mais ser usado após 30 de setembro de 2026. Antes dessa data, migre para as bibliotecas mais recentes do SDK do Azure, que oferecem atualizações de segurança críticas e funcionalidades aprimoradas.

Embora as bibliotecas mais antigas ainda poderão ser usadas após 30 de setembro de 2026, elas não receberão mais suporte e atualizações oficiais da Microsoft. Para obter mais informações, confira o anúncio de desativação do suporte.

Um remetente pode enviar várias mensagens durante a transmissão em sucessão rápida sem ter que esperar cada mensagem ser confirmada, como seria o caso com o protocolo SBMP ou o HTTP 1.1. Essas operações de envio assíncrono são concluídas conforme as respectivas mensagens são aceitas e armazenadas em entidades particionadas ou quando as operações de envio para entidades diferentes se sobrepõem. As conclusões também podem ocorrer fora da ordem de envio original.

A estratégia para lidar com o resultado das operações de envio pode ter impacto imediato e significativo no desempenho do seu aplicativo. Os exemplos nesta seção são escritos em C# e se aplicam a Java futuros, Java monos, promessas de JavaScript e conceitos equivalentes em outras linguagens.

Se o aplicativo produz picos de mensagens, como ilustrado aqui com um loop simples, e tem que aguardar a conclusão de cada operação de envio antes de enviar a mensagem seguinte, seja em forma síncrona ou assíncrona da API, o envio de 10 mensagens só é concluído após 10 ciclos de liquidação completos sequenciais.

Com uma distância de latência de ida e volta presumida de 70 milissegundos do Protocolo de Controle de Transmissão (TCP) de um site local para o Service Bus e dando apenas 10 ms para o Barramento de Serviço aceitar e armazenar cada mensagem, o loop a seguir dura pelo menos 8 segundos, sem contar o tempo de transferência da carga útil ou os possíveis efeitos de congestionamento da rota:

for (int i = 0; i < 10; i++)
{
    // creating the message omitted for brevity
    await sender.SendMessageAsync(message);
}

Se o aplicativo inicia as dez operações de envio assíncrono em sucessão imediata e aguarda suas respectivas conclusões separadamente, os tempos de ida e volta para as dez operações de envio se sobrepõem. As dez mensagens são transferidas em sucessão imediata, podendo até mesmo compartilhar quadros TCP, e a duração total da transferência depende bastante do tempo relativo à rede que leva para transferir as mensagens para o agente.

Com as mesmas suposições do loop anterior, o tempo total de execução sobreposto para o seguinte loop pode ficar abaixo de um segundo:

var tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
    tasks.Add(sender.SendMessageAsync(message));
}
await Task.WhenAll(tasks);

É importante observar que todos os modelos de programação assíncrona usam alguma forma de fila de trabalho oculta e baseada na memória que contém todas as operações pendentes. Quando a API de envio retorna, a tarefa de envio é colocada na fila de trabalho, mas o gesto de protocolo só começa quando chega a vez da tarefa ser executada. Quanto ao código que tende a enviar rajadas de mensagens e onde a confiabilidade é uma preocupação, deve-se tomar cuidado para que não sejam colocadas muitas mensagens "em trânsito" de uma só vez, pois todas as mensagens enviadas ocupam memória até serem enviadas para a rede.

Semáforos, conforme mostrado no snippet de código em C# a seguir, são objetos de sincronização que permitem essa limitação no nível do aplicativo quando necessário. Esse uso do semáforo permite no máximo 10 mensagens em trânsito ao mesmo tempo. Um dos 10 bloqueios de semáforo disponíveis é usado antes do envio e liberado quando o envio é concluído. A 11ª passagem pelo loop aguarda até que pelo menos uma das operações de envio anteriores seja concluída e disponibilize seu bloqueio:

var semaphore = new SemaphoreSlim(10);

var tasks = new List<Task>();
for (int i = 0; i < 10; i++)
{
    await semaphore.WaitAsync();

    tasks.Add(sender.SendMessageAsync(message).ContinueWith((t)=>semaphore.Release()));
}
await Task.WhenAll(tasks);

Os aplicativos nunca devem iniciar uma operação de envio assíncrono de forma a "disparar e esquecer", sem recuperar o resultado da operação. Isso pode sobrecarregar a memória da fila interna e invisível ao ponto de esgotamento e impedir que o aplicativo detecte erros de envio:

for (int i = 0; i < 10; i++)
{
    sender.SendMessageAsync(message); // DON’T DO THIS
}

Com um cliente AMQP de baixo nível, o Barramento de Serviço também aceita transferências "predefinidas". Uma transferência predefinida é uma operação do tipo "disparar e esquecer" para a qual o resultado, de qualquer forma, não é relatado de volta ao cliente e a mensagem é considerada resolvida quando enviada. A falta de comentários para o cliente também indica que nenhum dado acionável está disponível para o diagnóstico, o que significa que esse modo não está qualificado para obter ajuda por meio do suporte do Azure.

Liquidando operações de recebimento

Para operações de recebimento, os clientes da API do Barramento de Serviço habilitam dois modos explícitos diferentes: Receive-and-Delete e Peek-Lock.

ReceiveAndDelete

O modo Receive-and-Delete diz ao agente para considerar todas as mensagens que ele envia para o cliente destinatário como liquidadas quando enviadas. Isso significa que a mensagem é considerada consumida assim que o corretor a envia para a rede. Se a transferência de mensagem falhar, a mensagem será perdida.

A vantagem desse modo é que o destinatário não precisa realizar outra ação em relação à mensagem e também não sofre atraso por causa do resultado da liquidação. Se os dados contidos nas mensagens individuais têm valor baixo e/ou só são úteis por um período muito curto, esse modo é uma opção razoável.

BloqueioDePico

O modo Peek-Lock diz ao agente que o cliente destinatário deseja liquidar as mensagens recebidas explicitamente. A mensagem é disponibilizada para o destinatário para ser processada enquanto é mantida em um bloqueio exclusivo no serviço, de forma que outros destinatários concorrentes não possam vê-la. A duração do bloqueio é inicialmente definida no nível da assinatura ou da fila e pode ser estendida pelo cliente proprietário do bloqueio por meio da operação RenewMessageLockAsync. Para obter detalhes sobre a renovação de bloqueios, confira a seção Renovar bloqueios neste artigo.

Quando uma mensagem é bloqueada, outros clientes destinatários da mesma fila ou assinatura podem usar bloqueios e recuperar as mensagens disponíveis seguintes que não estejam em bloqueio ativo. Quando o bloqueio de uma mensagem é explicitamente liberado ou quando o bloqueio expira, a mensagem é colocada na ordem de recuperação ou próxima a ela para nova entrega.

Quando a mensagem é liberada repetidamente por destinatários ou eles deixam o bloqueio expirar determinada quantidade de vezes (Contagem Máxima de Entregas), a mensagem é removida da fila ou assinatura e colocada na fila de mensagens mortas automaticamente.

O cliente receptor inicia a liquidação de uma mensagem recebida com uma confirmação positiva ao chamar a API Complete para a mensagem. Isso indica ao agente que a mensagem foi processada com êxito e ela é removida da fila ou assinatura. O agente responde à intenção de liquidação do destinatário com uma resposta que indica se a liquidação pode ser feita.

Quando o cliente receptor falha ao processar uma mensagem, mas deseja que ela seja entregue novamente, ele pode solicitar explicitamente a liberação e o desbloqueio imediatos dela chamando a API Abandon ou não fazer nada e permitir que o bloqueio expire.

Quando um cliente receptor falha ao processar uma mensagem e sabe que a nova entrega e a nova tentativa da operação não serão úteis, ele pode rejeitar a mensagem, que será movida para a fila de mensagens mortas, chamando a API DeadLetter na mensagem, que também permite definir uma propriedade personalizada, incluindo um código de motivo que pode ser recuperado com a mensagem da fila de mensagens mortas.

Observação

Uma subconsulta de mensagens mortas para uma fila ou uma assinatura de tópico existe somente quando você tem o recurso de mensagens mortas habilitado para a fila ou assinatura.

Um caso especial de liquidação é o adiamento, que é abordado em outro artigo.

As operações Complete, DeadLetterou RenewLock podem falhar devido a problemas de rede, se o bloqueio mantido tiver expirado ou houver outras condições do lado do serviço que impeçam a liquidação. Em um destes casos, o serviço envia uma confirmação negativa que aparece como uma exceção nos clientes da API. Se o motivo for uma conexão de rede interrompida, o bloqueio será descartado porque o Barramento de Serviço não dá suporte à recuperação de links AMQP existentes em uma conexão diferente.

Em caso de falha Complete, que geralmente ocorre no final do tratamento da mensagem e, em alguns casos, após minutos de processamento do trabalho, o aplicativo receptor pode decidir se preserva o estado do trabalho e ignora a mesma mensagem quando ela for entregue uma segunda vez ou se descarta o resultado do trabalho e tenta novamente quando a mensagem é reentregue.

O mecanismo típico para identificar as entregas de mensagem duplicada é verificando a identificação da mensagem, que pode e deve ser definida pelo remetente como um valor exclusivo, possivelmente alinhado com um identificador do processo de origem. Um agendador provavelmente definiria a ID de mensagem do identificador da tarefa que ele está tentando atribuir a um trabalho, e o trabalho ignoraria a segunda ocorrência da atribuição de tarefa se ela já estiver concluída.

Importante

É importante observar que o bloqueio que o PeekLock ou SessionLock adquire na mensagem é volátil e pode ser perdido nas seguintes condições

  • Atualização de serviço
  • Atualização do SO
  • Alterando propriedades na entidade (fila, tópico, assinatura) enquanto mantém o bloqueio.

Quando o bloqueio for perdido, o Barramento de Serviço do Azure vai gerar uma MessageLockLostException que será exibida no código do aplicativo cliente. Nesse caso, a lógica de repetição padrão do cliente deve iniciar automaticamente e tentar a operação novamente.

Renovar bloqueios

O valor padrão da duração do bloqueio é de um minuto. Você pode especificar um valor diferente para a duração do bloqueio na fila ou no nível de assinatura. O cliente que possui o bloqueio pode renovar o bloqueio de mensagem usando métodos no objeto receptor. Em vez disso, você pode usar o recurso de renovação automática de bloqueios, em que é possível especificar a duração pela qual você deseja manter o bloqueio renovado.

É melhor definir a duração do bloqueio como superior ao tempo normal de processamento, para que você não precise renovar o bloqueio. O valor máximo é de 5 minutos, portanto, você precisa renovar o bloqueio se quiser tê-lo por mais tempo. Uma duração de bloqueio mais longa do que o necessário também tem algumas implicações. Por exemplo, quando o cliente parar de funcionar, a mensagem só ficará disponível novamente depois que a duração do bloqueio for decorrida.

Próximas etapas