Transferências de mensagens, bloqueios e acordo

A capacidade central de um agente de mensagens, como o Service Bus, é aceitar mensagens em uma fila ou tópico e mantê-las disponíveis para recuperação posterior. Enviar é o termo que é comumente usado para a transferência de uma mensagem para o agente de mensagens. Receber é o termo comumente usado para a transferência de uma mensagem para um cliente de recuperação.

Quando um cliente envia uma mensagem, ele geralmente quer saber se a mensagem é devidamente transferida e aceita pelo corretor ou se ocorreu algum tipo de erro. Este reconhecimento positivo ou negativo estabelece o entendimento do cliente e do corretor sobre o estado de transferência da mensagem. Portanto, é referido como um assentamento.

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

Liquidação de operações de envio

Usando qualquer um dos clientes de API do Service Bus suportados, as operações de envio para o Service Bus são sempre liquidadas explicitamente, o que significa que a operação da API aguarda a chegada de um resultado de aceitação do Service Bus e, em seguida, conclui a operação de envio.

Se a mensagem for rejeitada pelo Service Bus, a rejeição conterá um indicador de erro e texto com um ID de rastreamento. A rejeição também inclui informações sobre se a operação pode ser repetida com alguma expectativa de sucesso. No cliente, essa informação é transformada em exceção e levantada para o chamador da operação de envio. Se a mensagem for aceite, a operação é concluída silenciosamente.

O AMQP (Advanced Messaging Queuing Protocol) é o único protocolo suportado para clientes .NET Standard, Java, JavaScript, Python e Go. Para clientes .NET Framework, você pode usar o Service Bus Messaging Protocol (SBMP) ou AMQP. Quando você usa o protocolo AMQP, as transferências e liquidações de mensagens são canalizadas e assíncronas. Recomendamos que você use as variantes da 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, para que você não possa mais usar esse protocolo após 30 de setembro de 2026. Migre para as bibliotecas mais recentes do SDK do Azure, que oferecem atualizações de segurança críticas e recursos aprimorados, antes dessa data.

Embora as bibliotecas mais antigas ainda possam 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, consulte o anúncio de aposentadoria de suporte.

Um remetente pode colocar várias mensagens no fio em rápida sucessão sem ter que esperar que cada mensagem seja reconhecida, como seria o caso com o protocolo SBMP ou com HTTP 1.1. Essas operações de envio assíncronas são concluídas à medida que as respetivas mensagens são aceitas e armazenadas, em entidades particionadas ou quando a operação de envio para entidades diferentes se sobrepõe. As finalizaçõ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 um impacto imediato e significativo no desempenho do seu aplicativo. Os exemplos nesta seção são escritos em C# e se aplicam a futuros Java, monos Java, promessas JavaScript e conceitos equivalentes em outras linguagens.

Se o aplicativo produz rajadas de mensagens, ilustradas aqui com um loop simples, e aguarda a conclusão de cada operação de envio antes de enviar a próxima mensagem, formas de API síncronas ou assíncronas, o envio de 10 mensagens só é concluído após 10 viagens de ida e volta sequenciais completas para liquidação.

Com uma distância de latência de ida e volta do protocolo TCP (Transmission Control Protocol) de 70 milissegundos de um site local para o Service Bus e fornecendo apenas 10 ms para o Service Bus aceitar e armazenar cada mensagem, o loop a seguir ocupa pelo menos 8 segundos, sem contar o tempo de transferência de carga útil ou os possíveis efeitos de congestionamento de rota:

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

Se o aplicativo iniciar as 10 operações de envio assíncronas em sucessão imediata e aguardar sua respetiva conclusão separadamente, o tempo de ida e volta para essas 10 operações de envio se sobrepõe. As 10 mensagens são transferidas em sucessão imediata, potencialmente até mesmo compartilhando quadros TCP, e a duração total da transferência depende em grande parte do tempo relacionado à rede que leva para obter as mensagens transferidas para o corretor.

Com as mesmas suposições do loop anterior, o tempo total de execução sobreposto para o loop a seguir pode ficar bem 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 baseada em memória que mantém operações pendentes. Quando a API de envio retorna, a tarefa de envio é enfileirada nessa fila de trabalho, mas o gesto de protocolo só começa quando é a vez da tarefa ser executada. Para códigos que tendem a enviar rajadas de mensagens e onde a confiabilidade é uma preocupação, deve-se tomar cuidado para que não muitas mensagens sejam colocadas "em voo" ao mesmo tempo, porque todas as mensagens enviadas ocupam memória até serem colocadas no fio.

Os semáforos, conforme mostrado no trecho de código a seguir em C#, são objetos de sincronização que permitem essa limitação no nível do aplicativo quando necessário. Este uso de um semáforo permite que no máximo 10 mensagens estejam em voo ao mesmo tempo. Um dos 10 bloqueios de semáforo disponíveis é tirado 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, em seguida, disponibiliza 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íncrona de forma "disparar e esquecer" sem recuperar o resultado da operação. Isso pode carregar a fila de tarefas interna e invisível até o esgotamento da memória e impedir que o aplicativo detete 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 Service Bus também aceita transferências "pré-estabelecidas". Uma transferência pré-estabelecida é uma operação de incêndio e esquecimento 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 ao cliente também significa que não há dados acionáveis disponíveis para diagnóstico, o que significa que esse modo não se qualifica para ajuda por meio do suporte do Azure.

Operações de receção de liquidação

Para operações de recebimento, os clientes da API do Service Bus habilitam dois modos explícitos diferentes: Receber e Excluir e Peek-Lock.

ReceiveAndDelete

O modo Receber e Excluir diz ao corretor para considerar todas as mensagens que ele envia ao cliente recetor como liquidadas quando enviadas. Isso significa que a mensagem é considerada consumida assim que o corretor a coloca no fio. Se a transferência da mensagem falhar, a mensagem será perdida.

A vantagem deste modo é que o recetor não precisa tomar mais medidas sobre a mensagem e também não é retardado por esperar pelo resultado da liquidação. Se os dados contidos nas mensagens individuais tiverem baixo valor e/ou forem significativos apenas por um período de tempo muito curto, este modo é uma escolha razoável.

PeekLock

O modo Peek-Lock informa ao corretor que o cliente recetor deseja liquidar as mensagens recebidas explicitamente. A mensagem é disponibilizada para o recetor processar, enquanto mantida sob um bloqueio exclusivo no serviço para que outros recetores concorrentes não possam vê-la. A duração do bloqueio é inicialmente definida no nível da fila ou da assinatura 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, consulte a seção Renovar bloqueios neste artigo.

Quando uma mensagem é bloqueada, outros clientes que recebem da mesma fila ou assinatura podem assumir bloqueios e recuperar as próximas mensagens disponíveis que não estão sob bloqueio ativo. Quando o bloqueio de uma mensagem é explicitamente liberado ou quando o bloqueio expira, a mensagem é colocada na frente ou perto da frente da ordem de recuperação para reentrega.

Quando a mensagem é repetidamente liberada pelos recetores ou eles deixam o bloqueio passar por um número definido de vezes (Max Delivery Count), a mensagem é automaticamente removida da fila ou assinatura e colocada na fila de mensagens mortas associada.

O cliente recetor inicia a liquidação de uma mensagem recebida com uma confirmação positiva quando chama a API completa para a mensagem. Ele indica ao agente que a mensagem foi processada com êxito e a mensagem foi removida da fila ou assinatura. O corretor responde à intenção de liquidação do destinatário com uma resposta que indica se a liquidação pode ser realizada.

Quando o cliente recetor não consegue processar uma mensagem, mas deseja que a mensagem seja entregue novamente, ele pode pedir explicitamente que a mensagem seja liberada e desbloqueada instantaneamente chamando a API Abandon para a mensagem ou não pode fazer nada e deixar o bloqueio passar.

Se um cliente recetor não conseguir processar uma mensagem e souber que reentregar a mensagem e tentar novamente a operação não ajudará, ele poderá rejeitar a mensagem, o que a move para a fila de mensagens mortas chamando a API DeadLetter na mensagem, o 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.

Nota

Uma subfila de letra morta existe para uma fila ou uma assinatura de tópico somente quando você tem o recurso de carta morta habilitado para a fila ou assinatura.

Um caso especial de liquidação é o diferimento, que é discutido em um artigo separado.

As Completeoperações , DeadLetterou RenewLock podem falhar devido a problemas de rede, se o bloqueio mantido tiver expirado ou se houver outras condições do lado do serviço que impeçam a liquidação. Em um dos últimos casos, o serviço envia uma confirmação negativa que surge como uma exceção nos clientes de API. Se o motivo for uma conexão de rede quebrada, o bloqueio será descartado, pois o Service Bus não oferece suporte à recuperação de links AMQP existentes em uma conexão diferente.

Se Complete falhar, o que ocorre normalmente no final do processamento de mensagens e, em alguns casos, após minutos de trabalho de processamento, o aplicativo recetor pode decidir se preserva o estado do trabalho e ignora a mesma mensagem quando ela é entregue uma segunda vez, ou se deseja descartar o resultado do trabalho e repetir as tentativas à medida que a mensagem é entregue novamente.

O mecanismo típico para identificar entregas de mensagens duplicadas é verificando o ID da mensagem, que pode e deve ser definido pelo remetente para um valor exclusivo, possivelmente alinhado com um identificador do processo de origem. Um agendador de tarefas provavelmente definiria o ID da mensagem para o identificador do trabalho que está tentando atribuir a um trabalhador com determinado trabalhador, e o trabalhador ignoraria a segunda ocorrência da atribuição de trabalho se esse trabalho já estiver concluído.

Importante

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

  • Atualização do Serviço
  • Atualização do SO
  • Alterar propriedades na entidade (Fila, Tópico, Assinatura) enquanto mantém o bloqueio.

Quando o bloqueio for perdido, o Barramento de Serviço do Azure gerará um MessageLockLostException ou SessionLockLostException, que aparecerá no aplicativo cliente. Neste caso, a lógica de repetição predefinida do cliente tem de ser acionada automaticamente e a operação será repetida.

Renovar fechaduras

O valor padrão para a duração do bloqueio é 1 minuto. Pode especificar um valor diferente para a duração do bloqueio ao nível da fila ou da subscrição. O cliente que possui o bloqueio pode renovar o bloqueio de mensagem usando métodos no objeto recetor. Em vez disso, você pode usar o recurso de renovação automática de bloqueio, onde pode especificar a duração do tempo durante o qual deseja manter o bloqueio renovado.

É melhor definir a duração do bloqueio para algo maior do que o seu tempo de processamento normal, para que você não precise renovar o bloqueio. O valor máximo é de 5 minutos, então você precisa renovar o bloqueio se quiser tê-lo por mais tempo. Ter 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 tiver passado.

Próximos passos

  • Um caso especial de liquidação é o diferimento. Consulte o Adiamento de mensagem para obter detalhes.
  • Para saber mais sobre letras mortas, consulte Filas de letras mortas.
  • Para saber mais sobre as mensagens do Service Bus em geral, consulte Filas, tópicos e assinaturas do Service Bus