Mönster för konkurrerande konsumenter

Azure Functions
Azure Service Bus

Gör så att flera samtidiga användare kan bearbeta meddelanden som tas emot på samma meddelandekanal. Med flera samtidiga konsumenter kan ett system bearbeta flera meddelanden samtidigt för att optimera dataflödet, förbättra skalbarheten och tillgängligheten och balansera arbetsbelastningen.

Kontext och problem

Ett program som körs i molnet förväntas hantera ett stort antal begäranden. I stället för att bearbeta varje begäran synkront är en vanlig metod att programmet skickar dem via ett meddelandesystem till en annan tjänst (en konsumenttjänst) som hanterar dem asynkront. Den här strategin hjälper till att säkerställa att affärslogik i programmet inte blockeras medan begäranden bearbetas.

Antalet begäranden kan variera avsevärt över tid av flera orsaker. En plötslig ökning av användaraktivitet eller aggregerade begäranden som kommer från flera klienter kan orsaka en oförutsägbar arbetsbelastning. Vid rusningstid kan ett system behöva bearbeta många hundratals begäranden per sekund, medan antalet vid andra tillfällen kan vara mycket litet. Dessutom kan typen av arbete som utförs för att hantera dessa begäranden variera stort. Genom att använda en enda instans av konsumenttjänsten kan du göra så att den instansen översvämmas av begäranden. Eller så kan meddelandesystemet överbelastas av en tillströmning av meddelanden som kommer från programmet. För att kunna hantera den växlande arbetsbelastningen kan systemet köra flera instanser av konsumenttjänsten. Dessa konsumenter måste dock samordnas så att varje meddelande endast levereras till en enskild konsument. Arbetsbelastningen måste också belastningsutjämnas över konsumenterna för att förhindra att en instans blir en flaskhals.

Lösning

Använd en meddelandekö för att implementera kommunikationskanalen mellan appen och instanserna av konsumenttjänsten. Programmet skickar begäranden i form av meddelanden till kön och konsumenttjänstinstanser tar emot meddelanden från kön och bearbetar dem. Den här metoden gör att samma pool av konsumenttjänstinstanser kan hantera meddelanden från valfri instans i programmet. Bilden illustrerar hur en meddelandekö används för att fördela arbete till en tjänstinstans.

Använda en meddelandekö för att fördela arbete till tjänstinstanser

Kommentar

Även om det finns flera användare av dessa meddelanden är detta inte detsamma som mönstret Publicera prenumeration (pub/sub). Med metoden Konkurrerande konsumenter skickas varje meddelande till en enskild konsument för bearbetning, medan alla konsumenter skickas varjemeddelande med metoden Pub/Sub.

Den här lösningen har följande fördelar:

  • Den tillhandahåller ett belastningsutjämnat system som kan hantera stora volymvariationer i antalet begäranden som skickas av programinstanser. Kön fungerar som en buffert mellan programinstanserna och konsumenttjänstinstanserna. Den här bufferten kan bidra till att minimera påverkan på tillgänglighet och svarstider för både programmet och tjänstinstanserna. Mer information finns i Mönster för köbaserad belastningsutjämning. Att hantera ett meddelande som kräver långvarig bearbetning hindrar inte att andra meddelanden hanteras samtidigt av andra konsumenttjänstinstanser.

  • Det förbättrar tillförlitligheten. Om en producent kommunicerar direkt med en konsument i stället för att använda det här mönstret, men inte övervakar konsumenten, är det mycket troligt att meddelanden går förlorade eller inte kan bearbetas om konsumenten misslyckas. I det här mönstret skickas inte meddelanden till en specifik tjänstinstans. En misslyckad tjänstinstans blockerar inte en producent och meddelanden kan bearbetas av valfri fungerande tjänstinstans.

  • Det krävs ingen komplex samordning mellan konsumenterna, eller mellan producenten och konsumentinstanserna. Meddelandekön säkerställer att varje meddelande levereras minst en gång.

  • Det är skalbart. När du använder automatisk skalning kan systemet dynamiskt öka eller minska antalet instanser av konsumenttjänsten när mängden meddelanden varierar.

  • Det kan förbättra återhämtningen om meddelandekön tillhandahåller transaktionella läsåtgärder. Om en konsumenttjänstinstans läser och bearbetar meddelandet som en del av en transaktionell åtgärd, och konsumenttjänstinstansen misslyckas, kan det här mönstret säkerställa att meddelandet returneras till kön och hämtas och hanteras av en annan konsumenttjänstinstans. För att minska risken för att ett meddelande misslyckas kontinuerligt rekommenderar vi att du använder köer med obeställbara meddelanden.

Problem och överväganden

Tänk på följande när du bestämmer hur du ska implementera mönstret:

  • Ordningsföljd för meddelanden. Ordningsföljden som konsumenttjänstinstanser tar emot meddelanden i är inte garanterad och återspeglar inte nödvändigtvis ordningen som meddelandena skapats i. Utforma systemet så att bearbetningen av meddelanden är idempotent eftersom det bidrar till att eliminera eventuella beroenden för ordningsföljden som meddelandehanteringen sker i. Mer information finns i Idempotensmönster på Jonathon Olivers blogg.

    Microsoft Azure Service Bus-köer kan implementera garanterad först-in-först-ut-ordningsföljd för meddelanden med hjälp av meddelandesessioner. Läs mer i informationen om sessionsbaserade meddelandemönster.

  • Utforma tjänster för återhämtning. Om systemet är utformat för att upptäcka och starta om misslyckade tjänstinstanser, kan det vara nödvändigt att implementera bearbetningen som utförs av tjänstinstanser som idempotenta åtgärder. På så vis minimeras effekterna av ett enda meddelande som hämtas och bearbetas mer än en gång.

  • Upptäcka skadliga meddelanden. Ett felaktigt meddelande, eller en uppgift som kräver åtkomst till resurser som inte är tillgängliga, kan orsaka fel hos en tjänstinstans. Systemet bör förhindra att sådana meddelanden returneras till kön, och i stället avbilda och lagra information om dessa meddelanden på en annan plats så att de kan analyseras vid behov.

  • Hantera resultat. Tjänstinstansen som hanterar ett meddelande är helt frikopplad från programlogiken som genererar meddelandet, och de kan eventuellt inte kommunicera direkt. Om tjänstinstansen genererar resultat som måste skickas tillbaka till programlogiken, måste den här informationen lagras på en plats som är tillgänglig för båda. För att förhindra att programlogiken hämtar ofullständiga data måste systemet ange när bearbetningen är klar.

    Om du använder Azure kan en arbetsprocess skicka tillbaka resultaten till programlogiken genom att använda en dedikerad kö för meddelandesvar. Programlogiken måste kunna korrelera dessa resultat med det ursprungliga meddelandet. Detta scenario beskrivs mer ingående i Asynkron primer för meddelanden.

  • Skalning av meddelandesystemet. I en storskalig lösning kan en enskild meddelandekö överbelastas till följd av antalet meddelanden och bli en flaskhals i systemet. I den här situationen kan du överväga att partitionera meddelandesystemet så att meddelanden från specifika producenter skickas till en viss kö, eller att använda belastningsutjämning för att distribuera meddelanden över flera meddelandeköer.

  • Säkerställa tillförlitligheten hos meddelandesystemet. Ett pålitligt meddelandesystem krävs för att garantera att ett meddelande inte försvinner när programmet placerar det i kö. Det här systemet är viktigt för att säkerställa att alla meddelanden levereras minst en gång.

När du ska använda det här mönstret

Använd det här mönstret i sådana här scenarier:

  • Arbetsbelastningen för ett program delas upp i aktiviteter som kan köras asynkront.
  • Uppgifterna är fristående och kan köras parallellt.
  • Arbetsmängden varierar stort, vilket kräver en skalbar lösning.
  • Lösningen måste ha hög tillgänglighet och måste kunna återhämtas om bearbetning av en uppgift misslyckas.

Det här mönstret är kanske inte användbart om:

  • Det är svårt att dela upp programmets arbetsbelastning i diskreta uppgifter, eller om det finns en hög beroendegrad mellan uppgifterna.
  • Uppgifter måste utföras synkront och programlogiken måste vänta tills en aktivitet slutförts innan den kan gå vidare.
  • Uppgifter måste utföras i en viss sekvens.

Vissa meddelandesystem har stöd för sessioner som gör det möjligt för en producent att gruppera meddelanden och se till att alla hanteras av samma konsument. Den här mekanismen kan användas med prioriterade meddelanden (om de stöds) för att implementera en typ av meddelandeordningsföljd som levererar meddelanden i sekvens från en producent till en enskild konsument.

Design av arbetsbelastning

En arkitekt bör utvärdera hur mönstret Konkurrerande konsumenter kan användas i arbetsbelastningens design för att uppfylla de mål och principer som beskrivs i grundpelarna i Azure Well-Architected Framework. Till exempel:

Grundpelare Så här stöder det här mönstret pelarmål
Beslut om tillförlitlighetsdesign hjälper din arbetsbelastning att bli motståndskraftig mot fel och se till att den återställs till ett fullt fungerande tillstånd när ett fel inträffar. Det här mönstret skapar redundans i köbearbetning genom att behandla konsumenter som repliker, så ett instansfel hindrar inte andra konsumenter från att bearbeta kömeddelanden.

- RE:05 Redundans
- RE:07 Bakgrundsjobb
Kostnadsoptimering fokuserar på att upprätthålla och förbättra arbetsbelastningens avkastning på investeringen. Det här mönstret kan hjälpa dig att optimera kostnaderna genom att aktivera skalning som baseras på ködjup, ned till noll när kön är tom. Det kan också optimera kostnaderna genom att göra det möjligt att begränsa det maximala antalet samtidiga konsumentinstanser.

- CO:05 Hastighetsoptimering
- CO:07 Komponentkostnader
Prestandaeffektivitet hjälper din arbetsbelastning att effektivt uppfylla kraven genom optimeringar inom skalning, data och kod. Om du distribuerar belastningen över alla konsumentnoder ökar användningen och den dynamiska skalningen baserat på ködjupet, vilket minimerar överetablering.

- PE:05 Skalning och partitionering
- PE:07 Kod och infrastruktur

Som med alla designbeslut bör du överväga eventuella kompromisser mot målen för de andra pelarna som kan införas med det här mönstret.

Exempel

Azure tillhandahåller Service Bus-köer och Azure Function-köutlösare som, när de kombineras, är en direkt implementering av det här molndesignmönstret. Azure Functions integreras med Azure Service Bus via utlösare och bindningar. Genom att integrera med Service Bus kan du skapa funktioner som använder kömeddelanden som skickas av utgivare. Publiceringsapplikationerna publicerar meddelanden till en kö och konsumenter, som implementeras som Azure Functions, kan hämta meddelanden från den här kön och hantera dem.

För återhämtning gör en Service Bus-kö det möjligt för en konsument att använda PeekLock läget när det hämtar ett meddelande från kön. Det här läget tar faktiskt inte bort meddelandet, utan döljer det bara för andra konsumenter. Azure Functions-körningen tar emot ett meddelande i PeekLock-läge, om funktionen har slutförts anropas Slutfört i meddelandet, eller om den anropar Avbryt om funktionen misslyckas, och meddelandet visas igen, vilket gör att en annan konsument kan hämta den. Om funktionen körs under en period som är längre än PeekLock-tidsgränsen förnyas låset automatiskt så länge funktionen körs.

Azure Functions kan skala ut/in baserat på köns djup, som alla fungerar som konkurrerande konsumenter av kön. Om flera instanser av funktionerna skapas konkurrerar de alla genom att självständigt hämta och bearbeta meddelandena.

Detaljerad information om hur du använder Azure Service Bus-köer finns i Service Bus queues, topics, and subscriptions (Service Bus: köer, ämnen och prenumerationer).

Information om Köutlösta Azure Functions finns i Azure Service Bus-utlösare för Azure Functions.

Följande kod visar hur du kan skapa ett nytt meddelande och skicka det till en Service Bus-kö med hjälp av en ServiceBusClient instans.

private string serviceBusConnectionString = ...;
...

  public async Task SendMessagesAsync(CancellationToken  ct)
  {
   try
   {
    var msgNumber = 0;

    var serviceBusClient = new ServiceBusClient(serviceBusConnectionString);

    // create the sender
    ServiceBusSender sender = serviceBusClient.CreateSender("myqueue");

    while (!ct.IsCancellationRequested)
    {
     // Create a new message to send to the queue
     string messageBody = $"Message {msgNumber}";
     var message = new ServiceBusMessage(messageBody);

     // Write the body of the message to the console
     this._logger.LogInformation($"Sending message: {messageBody}");

     // Send the message to the queue
     await sender.SendMessageAsync(message);

     this._logger.LogInformation("Message successfully sent.");
     msgNumber++;
    }
   }
   catch (Exception exception)
   {
    this._logger.LogException(exception.Message);
   }
  }

I följande kodexempel visas en konsument, skriven som en C# Azure-funktion, som läser meddelandemetadata och loggar ett Service Bus-kömeddelande. Observera hur attributet ServiceBusTrigger används för att binda det till en Service Bus-kö.

[FunctionName("ProcessQueueMessage")]
public static void Run(
    [ServiceBusTrigger("myqueue", Connection = "ServiceBusConnectionString")]
    string myQueueItem,
    Int32 deliveryCount,
    DateTime enqueuedTimeUtc,
    string messageId,
    ILogger log)
{
    log.LogInformation($"C# ServiceBus queue trigger function consumed message: {myQueueItem}");
    log.LogInformation($"EnqueuedTimeUtc={enqueuedTimeUtc}");
    log.LogInformation($"DeliveryCount={deliveryCount}");
    log.LogInformation($"MessageId={messageId}");
}

Nästa steg

  • Asynkron primer för meddelanden. Meddelandeköer är mekanism för asynkron kommunikation. Om en konsumenttjänst behöver skicka ett svar till ett program, kan det vara nödvändigt att implementera svarsmeddelanden av någon typ. Primern för asynkrona meddelanden tillhandahåller information om hur du implementerar begäran-/svarsmeddelanden genom att använda meddelandeköer.

  • Vägledning om autoskalning. Det kan vara möjligt att starta och stoppa instanser av en konsumenttjänst eftersom längden på kön som programmet placerar meddelanden i varierar. Autoskalning kan hjälpa till att upprätthålla genomflödet vid hög belastning.

Följande mönster och riktlinjer kan vara relevanta när du implementerar det här mönstret:

  • Mönster för konsolidering av beräkningsresurser. Det kan vara möjligt att konsolidera flera instanser av en konsumenttjänst i en enda process för att minska kostnaderna och de indirekta hanteringskostnaderna. Mönstret för konsolidering av beräkningsresurser beskriver för- och nackdelarna med den här metoden.

  • Mönster för köbaserad belastningsutjämning. Att införa en meddelandekö kan förbättra systemets återhämtning, och göra det möjligt för tjänstinstanser att hantera många olika volymer för begäranden från programinstanser. Meddelandekön fungerar som en buffert, som utjämnar belastningen. Mönstret för köbaserad belastningsutjämning beskriver det här scenariot i detalj.