Mönster för prioritetskö

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 inte den ordning i vilken en tjänst tar emot begäranden viktig. I vissa fall är det dock nödvändigt att prioritera specifika begäranden. Dessa begäranden ska bearbetas före begäranden med lägre prioritet som har skickats tidigare av programmet.

Lösning

En kö är vanligtvis en FIFO-struktur (först-in-först-ut) och konsumenterna tar normalt emot meddelanden i samma ordning som de skickades till kön. En del meddelandeköer har dock stöd för en meddelandefunktion med prioritering. Programmet som skickar ett meddelande kan tilldela en prioritet och då kommer ändringen på meddelandena i kön att ändras automatiskt så att meddelanden med högre prioritet tas emot före dem som har lägre prioritet. Bilden visar en kö med meddelandefunktion med prioritering.

Bild 1 – Använda en kömekanism med stöd för meddelandeprioritering

De flesta implementeringar av meddelandeköer har stöd för flera konsumenter (enligt mönstret Konkurrerande konsumenter), och antalet konsumentprocesser kan skalas upp eller ned på begäran.

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 programvara än köer med lägre prioritet. Nästa bild visar hur man använder separata meddelandeköer för varje prioritet.

Bild 2 – Använda separata meddelandeköer för varje prioritet

En variant av den här strategin är att ha en enda pool med konsumenter som kontrollerar om det finns meddelanden i köer med hög prioritet först och börjar hämta meddelanden från köer med lägre prioritet först efter detta. Det finns vissa semantiska skillnader mellan en lösning som använder en enda pool med konsumentprocesser (antingen med en enda kö som har stöd för meddelanden med olika prioritet 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 enda pool kommer meddelanden med högre prioritet alltid att tas emot och bearbetas före meddelanden med lägre prioritet. Teoretiskt sett kan meddelanden som har en mycket låg prioritet att åsidosättas hela tiden och aldrig bearbetas. I metoden med flera pooler kommer meddelanden med lägre prioritet alltid att bearbetas, bara inte lika fort som meddelanden med högre prioritet (beroende på poolernas relativa storlek och de resurser som de har tillgängliga).

Att använda en metod med prioriterade kö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, t.ex. att erbjuda olika tjänstenivåer till specifika kundgrupper.

  • Det kan hjälpa till att sänka driftskostnaderna. Vid metoden med en kö kan du skala tillbaka antalet konsumenter vid behov. Meddelanden med hög prioritet bearbetas fortfarande först (fast eventuellt långsammare) och meddelanden med lägre prioritet kan fördröjas längre. Om du har implementerat metoden med flera meddelandeköer och separata pooler med kunder för varje kö, kan du minska poolen med konsumenter för köer med lägre prioritet, eller till och med pausa bearbetning för 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. Viktiga uppgifter kan till exempel prioriteras så att de hanteras av mottagare som körs direkt, medan bakgrundsuppgifter som är mindre viktiga kan hanteras av mottagare som schemaläggs för att köras under perioder med lägre belastning.

Problem och överväganden

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

Definiera prioriteringarna i lösningens kontext. Hög prioritet kan till exempel innebära att meddelanden ska bearbetas inom tio sekunder. Identifiera kraven för hantering av objekt med hög prioritet och de övriga resurser som ska tilldelas för att uppfylla dessa kriterier.

Avgör om alla objekt med hög prioritet måste bearbetas före eventuella objekt med lägre prioritet. Om meddelandena bearbetas av en enda pool med konsumenter måste du tillhandahålla en metod som kan förutse och pausa en uppgift som hanterar ett meddelande med låg prioritet om ett meddelande med högre prioritet blir tillgängligt.

Om du använder metoden med flera köer och 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 betjänar meddelanden från köer med högre prioritet innan den betjänar meddelanden från köer med lägre prioritet.

Övervaka bearbetningshastigheter i köer med hög och låg prioritet för att säkerställa att meddelanden i dessa köer bearbetas med förväntad hastighet.

Om du måste garantera att meddelanden med låg prioritet bearbetas är det nödvändigt att implementera metoden med flera meddelandeköer och flera konsumentpooler. I en kö som har stöd för meddelandeprioritering går det även att dynamiskt öka prioriteringen av ett meddelande i kön ju äldre det blir. Den här metoden är dock beroende av att meddelandekön tillhandahåller den här funktionen.

Att använda en separat kö för varje meddelandeprioritet fungerar bäst för system som har ett litet antal väldefinierade prioriteter.

Meddelandeprioriteter kan fastställas logiskt av systemet. I stället för att till exempel ha explicita hög- och lågprioriterade meddelanden kan de betecknas som "kund som betalar avgifter" eller "kund som inte betalar avgift". Beroende på din affärsmodell kan systemet allokera mer resurser till bearbetning av meddelanden från kunder som betalar avgifter än kunder som inte betalar avgifter.

Det kan finnas en ekonomi- och bearbetningskostnad som är kopplad till att kontrollera om det finns ett meddelande i en kö (en del kommersiella meddelandesystem debiterar en liten avgift varje gång ett meddelande skickas eller tas emot, och varje gång en fråga om meddelanden skickas till en kö). Den här kostnaden ökar vid kontroll av flera köer.

Det går att justera storleken på en pool med konsumenter baserat på längden på den kö som poolen betjänar. Mer information finns i Vägledning om autoskalning.

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 ska hanteras med olika prioritet.

Exempel

Microsoft Azure tillhandahåller inte en kömekanism som har inbyggt stöd för automatisk prioritering av meddelanden via sortering. Det tillhandahåller dock ämnen och prenumerationer för Azure Service Bus med stöd för en kömekanism som har meddelandefiltrering, tillsammans med ett stort antal flexibla funktioner som gör det perfekt att använda i de flesta implementeringar av prioritetsköer.

En Azure-lösning kan implementera ett Service Bus-ämne som ett program kan skicka meddelanden till, på samma sätt som en kö. Meddelanden kan innehålla metadata i form av programdefinierade anpassade egenskaper. Service Bus-prenumerationer kan kopplas till ämnet, och dessa prenumerationer 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 det kan läsas av en konsument. Konsumentprocesser kan hämta meddelanden från en prenumeration med samma semantik som en meddelandekö (en prenumeration är en logisk kö). Följande bild visar implementering av en prioritetskö med ämnen och prenumerationer i Azure Service Bus.

Bild 3 – Implementera en prioritetskö med ämnen och prenumerationer i Azure Service Bus

I bilden ovan skapar programmet flera meddelanden och tilldelar en anpassad egenskap som kallas Priority i varje meddelande med ett värde, antingen High eller Low. Programmet skickar dessa meddelanden till ett ämne. Ämnet har två kopplade prenumerationer som båda filtrerar meddelanden genom att undersöka egenskapen Priority. En prenumeration godkänner meddelanden där egenskapen Priority är inställd på High, och den andra godkänner meddelanden där egenskapen Priority är inställd på Low. 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 med mer tillgängliga resurser än konsumenterna i poolen med låg prioritet.

Notera att det inte är något särskilt med tilldelningen av hög- och lågprioriterade meddelanden i det här exemplet. De är bara etiketter som har angetts som egenskaper i varje meddelande, och de används för att dirigera meddelanden till en viss prenumeration. Om det krävs ytterligare prioriteringar är det relativt enkelt att skapa ytterligare prenumerationer och pooler med konsumentprocesser för att hantera dessa prioriteringar.

PriorityQueue-lösningen som finns på GitHub innehåller en implementering av den här metoden. Den här lösningen innehåller två arbetsrollsprojekt som heter PriorityQueue.High och PriorityQueue.Low. Dessa arbetsroller ärver från PriorityWorkerRole-klassen som innehåller funktioner för att ansluta till en angiven prenumeration i OnStart-metoden.

Arbetsrollerna PriorityQueue.High och PriorityQueue.Low ansluter till olika prenumerationer som definieras av sina konfigurationsinställningar. En administratör kan konfigurera olika antal av varje roll som ska köras. Det finns normalt fler instanser av arbetsrollen PriorityQueue.High än arbetsrollen PriorityQueue.Low.

Run-metoden i PriorityWorkerRole-klassen ordnar så att den virtuella ProcessMessage-metoden (definieras även i PriorityWorkerRole-klassen) kan köras för varje meddelande som tas emot i kön. Följande kod visar Run- och ProcessMessage-metoderna. QueueManager-klassen, som definieras i projektet PriorityQueue.Shared, tillhandahåller hjälpkomponentmetoder för att använda Azure Service Bus-köer.

public class PriorityWorkerRole : RoleEntryPoint
{
  private QueueManager queueManager;
  ...

  public override void Run()
  {
    // Start listening for messages on the subscription.
    var subscriptionName = CloudConfigurationManager.GetSetting("SubscriptionName");
    this.queueManager.ReceiveMessages(subscriptionName, this.ProcessMessage);
    ...;
  }
  ...

  protected virtual async Task ProcessMessage(BrokeredMessage message)
  {
    // Simulating processing.
    await Task.Delay(TimeSpan.FromSeconds(2));
  }
}

Arbetsrollerna PriorityQueue.High och PriorityQueue.Low åsidosätter båda standardfunktionerna i ProcessMessage-metoden. Koden nedan visar ProcessMessage-metoden för arbetsrollen PriorityQueue.High.

protected override async Task ProcessMessage(BrokeredMessage message)
{
  // Simulate message processing for High priority messages.
  await base.ProcessMessage(message);
  Trace.TraceInformation("High priority message processed by " +
    RoleEnvironment.CurrentRoleInstance.Id + " MessageId: " + message.MessageId);
}

När ett program skickar meddelanden till det ämne som är kopplat till prenumerationerna som används av arbetsrollerna PriorityQueue.High och PriorityQueue.Low anger det prioriteten med hjälp av den anpassade egenskapen Priority, vilket visas i följande kodexempel. Den här koden (som implementeras i WorkerRole-klassen i projektet PriorityQueue.Sender) använder hjälpkomponentmetoden SendBatchAsync för QueueManager-klassen för att skicka meddelanden till ett ämne i batcher.

// Send a low priority batch.
var lowMessages = new List<BrokeredMessage>();

for (int i = 0; i < 10; i++)
{
  var message = new BrokeredMessage() { MessageId = Guid.NewGuid().ToString() };
  message.Properties["Priority"] = Priority.Low;
  lowMessages.Add(message);
}

this.queueManager.SendBatchAsync(lowMessages).Wait();
...

// Send a high priority batch.
var highMessages = new List<BrokeredMessage>();

for (int i = 0; i < 10; i++)
{
  var message = new BrokeredMessage() { MessageId = Guid.NewGuid().ToString() };
  message.Properties["Priority"] = Priority.High;
  highMessages.Add(message);
}

this.queueManager.SendBatchAsync(highMessages).Wait();

Nästa steg

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

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

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

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

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

  • Mönster för konkurrerande konsumenter. Om du vill öka köflödet är det möjligt att ha flera konsumenter som lyssnar på samma kö och bearbetar aktiviteterna parallellt. De här konsumenterna tävlar om meddelanden, men endast en bör kunna bearbeta varje meddelande. Tillhandahåller mer information om fördelar och nackdelar med att implementera denna metod.

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

  • Enterprise-integration mönster med Service Bus på Abhishek Lals blogg.