Mönster för prioritetskö

Azure Service Bus

Prioritera förfrågningar som skickas till tjänster så att förfrågningar med högre prioritet tas emot och bearbetas snabbare än de med lägre prioritet. Det här mönstret är användbart i program som erbjuder olika tjänstenivågarantier till enskilda klienter.

Kontext och problem

Program kan delegera specifika uppgifter till andra tjänster, t.ex. för att utföra bearbetning i bakgrunden eller för att integrera med andra program eller tjänster. I molnet används en meddelandekö normalt för att delegera aktiviteter till bearbetning i bakgrunden. I många fall är det inte viktigt i vilken ordning begäranden tas emot av en tjänst. I vissa fall är det dock nödvändigt att prioritera specifika begäranden. Dessa begäranden bör bearbetas tidigare än begäranden med lägre prioritet som tidigare skickats av programmet.

Lösning

En kö är vanligtvis en först-in-, först-ut-struktur (FIFO) och konsumenter tar vanligtvis emot meddelanden i samma ordning som de publiceras i kön. En del meddelandeköer har dock stöd för en meddelandefunktion med prioritering. Programmet som publicerar ett meddelande kan tilldela en prioritet. Meddelandena i kön ordnas om automatiskt så att de som har högre prioritet tas emot före de som har lägre prioritet. Det här diagrammet illustrerar processen:

Diagram som illustrerar en kömekanism som stöder meddelandeprioritering.

Kommentar

De flesta implementeringar av meddelandeköer stöder flera konsumenter. (Se Mönster för konkurrerande konsumenter.) Antalet konsumentprocesser kan skalas upp och ned baserat på efterfrågan.

I system som inte har stöd för prioritetsbaserade meddelandeköer kan en alternativ lösning vara att ha en separat kö för varje prioritet. Programmet ansvarar för att skicka meddelanden till lämpliga köer. Varje kö kan ha en separat pool med konsumenter. Köer med högre prioritet kan ha en större pool med konsumenter som körs på snabbare maskinvara än köer med lägre prioritet. Det här diagrammet illustrerar användningen av separata meddelandeköer för varje prioritet:

Diagram som illustrerar användningen av separata meddelandeköer för varje prioritet.

En variant av den här strategin är att implementera en enda pool med konsumenter som söker efter meddelanden i köer med hög prioritet först och först efter det börjar hämta meddelanden från köer med lägre prioritet. Det finns vissa semantiska skillnader mellan en lösning som använder en enda pool med konsumentprocesser (antingen med en enda kö som stöder meddelanden som har olika prioriteter eller med flera köer som var och en hanterar meddelanden med en enda prioritet) och en lösning som använder flera köer med en separat pool för varje kö.

I metoden med en pool tas meddelanden med högre prioritet alltid emot och bearbetas före meddelanden med lägre prioritet. I teorin kan lågprioriterade meddelanden kontinuerligt ersättas och kanske aldrig bearbetas. I metoden med flera pooler bearbetas alltid meddelanden med lägre prioritet, men inte lika snabbt som meddelanden med högre prioritet (beroende på poolernas relativa storlek och de resurser som är tillgängliga för dem).

Att använda en mekanism för prioritetsköer kan ge följande fördelar:

  • Det gör att program kan uppfylla affärskrav som kräver prioritering av tillgänglighet eller prestanda, till exempel att erbjuda olika tjänstnivåer till olika kundgrupper.

  • Det kan hjälpa till att sänka driftskostnaderna. Om du använder metoden med en kö kan du skala ned antalet konsumenter om du behöver det. Meddelanden med hög prioritet bearbetas fortfarande först (men eventuellt långsammare) och meddelanden med lägre prioritet kan fördröjas längre. Om du implementerar metoden för flera meddelandeköer med separata pooler med konsumenter för varje kö kan du minska konsumentpoolen för köer med lägre prioritet. Du kan till och med avbryta bearbetningen för vissa köer med mycket låg prioritet genom att stoppa alla konsumenter som lyssnar efter meddelanden i dessa köer.

  • Metoden med flera meddelandeköer kan hjälpa till att maximera programmets prestanda och skalbarheten genom att partitionera meddelanden baserat på bearbetningskrav. Du kan till exempel prioritera kritiska uppgifter så att de hanteras av mottagare som körs omedelbart, och mindre viktiga bakgrundsaktiviteter kan hanteras av mottagare som är schemalagda att köras vid tider som är mindre upptagna.

Att tänka på

Tänk på följande när du bestämmer dig för hur du implementerar det här mönstret:

  • Definiera prioriteringarna i lösningens kontext. Ett meddelande med hög prioritet kan till exempel definieras som ett meddelande som ska bearbetas inom 10 sekunder. Identifiera kraven för hantering av objekt med hög prioritet och de resurser som behöver allokeras för att uppfylla dina kriterier.

  • Bestäm om alla objekt med hög prioritet måste bearbetas före objekt med lägre prioritet. Om meddelandena bearbetas av en enda pool med konsumenter måste du tillhandahålla en mekanism som kan föregripa och pausa en uppgift som hanterar ett meddelande med låg prioritet om ett meddelande med högre prioritet kommer in i kön.

  • När du använder en enda pool med konsumentprocesser som lyssnar på alla köer i stället för en dedikerad konsumentpool för varje kö, måste konsumenten använda en algoritm som säkerställer att den alltid servar meddelanden från köer med högre prioritet före meddelanden från köer med lägre prioritet.

  • Övervaka bearbetningshastigheten för köer med hög och låg prioritet för att säkerställa att meddelanden i dessa köer bearbetas enligt förväntade priser.

  • Om du behöver garantera att meddelanden med låg prioritet bearbetas implementerar du metoden för flera meddelandeköer med flera konsumentpooler. I en kö som stöder meddelandeprioritering kan du också dynamiskt öka prioriteten för ett köat meddelande när det åldras. Den här metoden är dock beroende av att meddelandekön tillhandahåller den här funktionen.

  • Strategin att använda separata köer baserat på meddelandeprioritet rekommenderas för system som har några väldefinierade prioriteringar.

  • Systemet kan logiskt fastställa meddelandeprioriteringar. I stället för att till exempel ha explicita meddelanden med hög och låg prioritet kan du ange meddelanden som "betalande kund" eller "icke-betalande kund". Systemet kan sedan allokera fler resurser till bearbetning av meddelanden från betalande kunder.

  • Det kan finnas en ekonomisk kostnad och bearbetningskostnad som är kopplad till att kontrollera en kö för ett meddelande. Till exempel debiterar vissa kommersiella meddelandesystem en liten avgift varje gång ett meddelande publiceras eller hämtas, och varje gång en kö efterfrågas efter meddelanden. Den här kostnaden ökar när du kontrollerar flera köer.

  • Du kan dynamiskt justera storleken på en pool med konsumenter baserat på längden på kön som poolen betjänar. Mer information finns i Vägledning för automatisk skalning.

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

Det här mönstret är användbart i scenarier där:

  • Systemet måste hantera flera uppgifter som har olika prioriteter.

  • Olika användare eller klienter bör hanteras med olika prioriteringar.

Design av arbetsbelastning

En arkitekt bör utvärdera hur prioritetskömönstret 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. Genom att separera objekt baserat på affärsprioritet kan du fokusera tillförlitlighetsarbetet på det mest kritiska arbetet.

- RE:02 Kritiska flöden
- RE:07 Bakgrundsjobb
Prestandaeffektivitet hjälper din arbetsbelastning att effektivt uppfylla kraven genom optimeringar inom skalning, data och kod. Genom att separera objekt baserat på affärsprioritet kan du fokusera prestandaarbetet på det mest tidskänsliga arbetet.

- PE:09 Kritiska flöden

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 ingen kömekanism som internt stöder automatisk prioritering av meddelanden via sortering. Den tillhandahåller dock Azure Service Bus-ämnen, Service Bus-prenumerationer som stöder en kömekanism som tillhandahåller meddelandefiltrering och en rad flexibla funktioner som gör Azure idealiskt för de flesta implementeringar av prioritetsköer.

En Azure-lösning kan implementera ett Service Bus-ämne som ett program kan skicka meddelanden till, precis som det skulle publicera dem i en kö. Meddelanden kan innehålla metadata i form av programdefinierade anpassade egenskaper. Du kan associera Service Bus-prenumerationer med ämnet och prenumerationerna kan filtrera meddelanden baserat på deras egenskaper. När ett program skickar ett meddelande till ett ämne dirigeras meddelandet till rätt prenumeration, där en konsument kan läsa det. Konsumentprocesser kan hämta meddelanden från en prenumeration med samma semantik som de skulle använda med en meddelandekö. (En prenumeration är en logisk kö.) Det här diagrammet visar hur du implementerar en prioritetskö med hjälp av Service Bus-ämnen och -prenumerationer:

Diagram som visar hur du implementerar en prioritetskö med hjälp av Service Bus-ämnen och -prenumerationer.

I föregående diagram skapar programmet flera meddelanden och tilldelar en anpassad egenskap som heter Priority i varje meddelande. Priority har värdet High eller Low. Programmet skickar dessa meddelanden till ett ämne. Ämnet har två associerade prenumerationer som filtrerar meddelanden baserat på egenskapen Priority . En prenumeration accepterar meddelanden med egenskapen inställd på PriorityHigh. Den andra accepterar meddelanden med egenskapen inställd på PriorityLow. En pool med konsumenter läser meddelanden från varje prenumeration. Prenumerationen med hög prioritet har en större pool och dessa konsumenter kan köras på mer kraftfulla datorer som har mer tillgängliga resurser än datorerna för poolen med låg prioritet.

Det är inget speciellt med att utse meddelanden med hög och låg prioritet i det här exemplet. De är helt enkelt etiketter som anges som egenskaper i varje meddelande. De används för att dirigera meddelanden till en specifik prenumeration. Om ytterligare prioriteringar behövs är det relativt enkelt att skapa fler prenumerationer och pooler med konsumentprocesser för att hantera dessa prioriteringar.

PriorityQueue-lösningen på GitHub baseras på den här metoden. Den här lösningen innehåller Azure Function-projekt med namnet PriorityQueueConsumerHigh och PriorityQueueConsumerLow. Dessa Azure Function-projekt integreras med Service Bus via utlösare och bindningar. De ansluter till olika prenumerationer som definieras i ServiceBusTrigger och reagerar på inkommande meddelanden.

public static class PriorityQueueConsumerHighFn
{
    [FunctionName("HighPriorityQueueConsumerFunction")]
    public static void Run(
      [ServiceBusTrigger("messages", "highPriority", Connection = "ServiceBusConnection")]string highPriorityMessage,
      ILogger log)
    {
        log.LogInformation($"C# ServiceBus topic trigger function processed message: {highPriorityMessage}");
    }
}

Som administratör kan du konfigurera hur många instanser funktionerna i Azure App Service kan skalas ut till. Du kan göra det genom att konfigurera alternativet Framtvinga utskalningsgräns från Azure-portalen och ange en maximal utskalningsgräns för varje funktion. Du behöver vanligtvis ha fler instanser av PriorityQueueConsumerHigh funktionen än PriorityQueueConsumerLow funktionen. Den här konfigurationen säkerställer att meddelanden med hög prioritet läse från kön snabbare än meddelanden med låg prioritet.

Ett annat projekt, PriorityQueueSender, innehåller en tidsutlöst Azure-funktion som är konfigurerad att köras var 30:e sekund. Den här funktionen integreras med Service Bus via en utdatabindning och skickar batchar med meddelanden med låg och hög prioritet till ett IAsyncCollector objekt. När funktionen skickar meddelanden till det ämne som är associerat med de prenumerationer som används av PriorityQueueConsumerHigh funktionerna och PriorityQueueConsumerLow anger den prioriteten med hjälp av den Priority anpassade egenskapen, enligt följande:

public static class PriorityQueueSenderFn
{
    [FunctionName("PriorityQueueSenderFunction")]
    public static async Task Run(
        [TimerTrigger("0,30 * * * * *")] TimerInfo myTimer,
        [ServiceBus("messages", Connection = "ServiceBusConnection")] IAsyncCollector<ServiceBusMessage> collector)
    {
        for (int i = 0; i < 10; i++)
        {
            var messageId = Guid.NewGuid().ToString();
            var lpMessage = new ServiceBusMessage() { MessageId = messageId };
            lpMessage.ApplicationProperties["Priority"] = Priority.Low;
            lpMessage.Body = BinaryData.FromString($"Low priority message with Id: {messageId}");
            await collector.AddAsync(lpMessage);

            messageId = Guid.NewGuid().ToString();
            var hpMessage = new ServiceBusMessage() { MessageId = messageId };
            hpMessage.ApplicationProperties["Priority"] = Priority.High;
            hpMessage.Body = BinaryData.FromString($"High priority message with Id: {messageId}");
            await collector.AddAsync(hpMessage);
        }
    }
}

Nästa steg

Följande resurser kan vara till hjälp när du implementerar det här mönstret:

  • Ett exempel som visar det här mönstret på GitHub.

  • Asynkron meddelandeprimör. En konsumenttjänst som bearbetar en begäran kan behöva skicka ett svar till instansen för det program som skickade begäran. Den här artikeln innehåller information om de strategier som du kan använda för att implementera meddelanden för begäran/svar.

  • Vägledning för automatisk skalning. Du kan ibland skala storleken på poolen med konsumentprocesser som hanterar en kö baserat på köns längd. Den här strategin kan hjälpa dig att förbättra prestanda, särskilt för pooler som hanterar meddelanden med hög prioritet.

Följande mönster kan vara till hjälp när du implementerar det här mönstret:

  • Mönster för konkurrerande förbrukare. Om du vill öka dataflödet för köerna kan du implementera flera konsumenter som lyssnar på samma kö och bearbetar uppgifter parallellt. Dessa konsumenter konkurrerar om meddelanden, men bara en ska kunna bearbeta varje meddelande. Den här artikeln innehåller mer information om fördelarna och nackdelarna med att implementera den här metoden.

  • Mönster för begränsning. Du kan implementera begränsning med hjälp av köer. Du kan använda prioriterade meddelanden för att säkerställa att begäranden från kritiska program eller program som körs av kunder med högt värde prioriteras framför begäranden från mindre viktiga program.