Patroon voor wachtrij met prioriteit

Prioriteit geven aan aanvragen die worden verzonden naar services, zodat aanvragen met een hogere prioriteit sneller worden ontvangen en verwerkt dan aanvragen met een lagere prioriteit. Dit patroon is nuttig voor toepassingen die verschillende garanties voor serviceniveau bieden voor verschillende klanten.

Context en probleem

Toepassingen kunnen specifieke taken delegeren naar andere services, bijvoorbeeld voor verwerking op de achtergrond of integratie met andere toepassingen of services. In de cloud wordt doorgaans een berichtenwachtrij gebruikt om taken te delegeren voor verwerking op de achtergrond. In veel gevallen is de volgorde waarin de aanvragen door een service worden ontvangen niet belangrijk. In sommige gevallen kan het echter nodig zijn bepaalde aanvragen prioriteit te geven. Deze aanvragen moeten eerder worden verwerkt dan aanvragen met een lagere prioriteit die eerder door de toepassing zijn verzonden.

Oplossing

Een wachtrij gebruikt meestal een FIFO-structuur (first in, first out) en consumenten ontvangen berichten doorgaans in dezelfde volgorde als waarin ze in de wachtrij zijn geplaatst. Sommige berichtenwachtrijen ondersteunen echter berichten met prioriteit. De toepassing die een bericht plaatst, kan een prioriteit toewijzen en de volgorde van de berichten in de wachtrij wordt automatisch gewijzigd, zodat berichten met een hogere prioriteit worden ontvangen voor berichten met een lagere prioriteit. In de illustratie ziet u een wachtrij met berichten met prioriteit.

Afbeelding 1: Een wachtrijmechanisme gebruiken dat prioriteit van berichten ondersteunt

De meeste implementaties van berichtenwachtrijen ondersteunen meerdere consumenten (volgens het patroon voor concurrerende consumenten) en het aantal consumentenprocessen kan omhoog of omlaag worden geschaald, afhankelijk van de vraag.

In systemen die geen berichtenwachtrijen op basis van prioriteit ondersteunen, bestaat een alternatieve oplossing eruit een afzonderlijke wachtrij te gebruiken voor elke prioriteit. De toepassing is ervoor verantwoordelijk berichten in de juiste wachtrij te plaatsen. Elke wachtrij kan een afzonderlijke groep consumenten hebben. Wachtrijen met een hogere prioriteit kunnen een grotere groep consumenten met snellere hardware hebben dan wachtrijen met een lagere prioriteit. De volgende afbeelding illustreert het gebruik van afzonderlijke berichtenwachtrijen voor elke prioriteit.

Afbeelding 2: Afzonderlijke berichtenwachtrijen gebruiken voor elke prioriteit

Een variant van deze strategie bestaat eruit één groep consumenten te hebben die eerst controleert op berichten in wachtrijen met een hoge prioriteit en daarna pas berichten gaat ophalen uit wachtrijen met een lagere prioriteit. Er zijn enkele semantische verschillen tussen een oplossing die gebruikmaakt van één groep consumentenprocessen (met één wachtrij die berichten met verschillende prioriteiten ondersteunt of met meerdere wachtrijen die elk berichten met één bepaalde prioriteit verwerken) en een oplossing die meerdere wachtrijen gebruikt met een afzonderlijke groep voor elke wachtrij.

In de benadering met één groep worden berichten met een hogere prioriteit altijd ontvangen en verwerkt vóór berichten met een lagere prioriteit. In theorie kunnen berichten met een zeer lage prioriteit steeds worden gepasseerd en mogelijk nooit worden verwerkt. In de benadering met meerdere groepen worden berichten met een lagere prioriteit altijd verwerkt, enkel niet zo snel als berichten met een hogere prioriteit (afhankelijk van de relatieve grootte van de groepen en de resources die beschikbaar zijn).

Het gebruik van een wachtrijmechanisme met prioriteiten kan de volgende voordelen bieden:

  • Het zorgt ervoor dat toepassingen voldoen aan zakelijke vereisten die vereisen dat prioriteit kan worden gegeven aan beschikbaarheid of prestaties, bijvoorbeeld door verschillende serviceniveaus te bieden aan specifieke groepen klanten.

  • Het kan helpen de operationele kosten tot het minimum te beperken. Bij de benadering met één wachtrij kunt u het aantal consumenten zo nodig omlaag schalen. Berichten met een hoge prioriteit worden nog steeds als eerste verwerkt (hoewel mogelijk langzamer) en berichten met een lagere prioriteit worden mogelijk langer uitgesteld. Als u de benadering met meerdere berichtenwachtrijen met afzonderlijke groepen consumenten voor elke wachtrij hebt geïmplementeerd, kunt u de groep consumenten voor wachtrijen met een lagere prioriteit verkleinen of de verwerking voor bepaalde wachtrijen met een zeer lage prioriteit zelfs opschorten door alle consumenten te stoppen die luisteren naar berichten in die wachtrijen.

  • De benadering met meerdere berichtenwachtrijen kan u helpen de prestaties en schaalbaarheid van de toepassing te optimaliseren door berichten te partitioneren op basis van verwerkingsvereisten. Essentiële taken kunnen bijvoorbeeld prioriteit krijgen en worden verwerkt door ontvangers die direct worden uitgevoerd, terwijl minder belangrijke achtergrondtaken kunnen worden afgehandeld door ontvangers die zijn gepland om in minder drukke perioden te worden uitgevoerd.

Problemen en overwegingen

Beschouw de volgende punten als u besluit hoe u dit patroon wilt implementeren:

Definieer de prioriteiten in de context van de oplossing. Een hoge prioriteit kan bijvoorbeeld betekenen dat berichten binnen tien seconden moeten worden verwerkt. Identificeer de vereisten voor het verwerken van items met hoge prioriteit en de andere resources die moeten worden toegewezen om te voldoen aan deze criteria.

Bepaal of alle items met hoge prioriteit moeten worden verwerkt voor alle items met een lagere prioriteit. Als de berichten worden verwerkt door één groep consumenten, moet u een mechanisme bieden dat een taak die een bericht met een lage prioriteit afhandelt, kan opschorten en vervangen als een bericht met een hogere prioriteit beschikbaar komt.

Als in de benadering met meerdere wachtrijen een groep consumentenprocessen wordt gebruikt die luisteren naar alle wachtrijen in plaats van een toegewezen groep consumenten voor elke wachtrij, moet de consument een algoritme toepassen dat ervoor zorgt dat deze berichten uit wachtrijen met een hogere prioriteit altijd verwerkt vóór berichten uit wachtrijen met een lagere prioriteit.

Controleer de verwerkingssnelheid van wachtrijen met een hoge en een lage prioriteit om er zeker van te zijn dat berichten in deze wachtrijen met de verwachte snelheden worden verwerkt.

Als u er zeker van wilt zijn dat berichten met een lage prioriteit worden verwerkt, moet u de benadering met meerdere berichtenwachtrijen met meerdere groepen consumenten implementeren. Anderzijds is het mogelijk om in een wachtrij die prioriteiten voor berichten ondersteunt, de prioriteit van een bericht in de wachtrij dynamisch te verhogen naarmate het bericht ouder wordt. Deze benadering is echter afhankelijk van de berichtenwachtrij die deze functie moet bieden.

Het gebruik van een afzonderlijke wachtrij voor elke berichtprioriteit werkt het beste voor systemen met een klein aantal goed gedefinieerde prioriteiten.

Berichtprioriteiten kunnen logisch worden bepaald door het systeem. In plaats van berichten met een expliciete hoge of lage prioriteit te gebruiken, kunnen berichten bijvoorbeeld worden aangeduid als 'betalende klant' of 'niet-betalende klant'. Afhankelijk van uw bedrijfsmodel kan uw systeem meer resources toewijzen aan het verwerken van berichten van betalende klanten dan van niet-betalende klanten.

Er zijn mogelijk financiële en verwerkingskosten verbonden aan het controleren van een wachtrij op een bericht (sommige commerciële berichtsystemen vragen een klein bedrag telkens wanneer een bericht wordt geplaatst of opgehaald en telkens wanneer een wachtrij wordt gecontroleerd op berichten). Deze kosten nemen toe wanneer meerdere wachtrijen worden gecontroleerd.

Het is mogelijk de grootte van een groep consumenten dynamisch aan te passen op basis van de lengte van de wachtrij die de groep bedient. Zie Hulp bij automatisch schalen voor meer informatie.

Wanneer dit patroon gebruiken

Dit patroon is bruikbaar voor scenario's waarbij:

  • Het systeem meerdere taken met verschillende prioriteiten moet verwerken.

  • Verschillende gebruikers of tenants moeten worden bediend met verschillende prioriteiten.

Voorbeeld

Microsoft Azure biedt geen wachtrijmechanisme dat ingebouwde ondersteuning biedt voor het automatisch prioriteit geven aan berichten door ze te sorteren. Het biedt echter wel Azure Service Bus-onderwerpen en -abonnementen die een wachtrijmechanisme ondersteunen dat het mogelijk maakt berichten te filteren, samen met een breed scala aan flexibele mogelijkheden, waardoor dit ideaal is voor gebruik in de meeste implementaties van prioriteitswachtrijen.

Een Azure-oplossing kan een Service Bus-onderwerp implementeren waarin een toepassing berichten kan plaatsen, op dezelfde manier als bij een wachtrij. Berichten kunnen metagegevens bevatten in de vorm van door de toepassing gedefinieerde eigenschappen. Service Bus-abonnementen kunnen worden gekoppeld aan het onderwerp en deze abonnementen kunnen berichten filteren op basis van hun eigenschappen. Wanneer een toepassing een bericht naar een onderwerp verzendt, wordt het bericht doorgestuurd naar het juiste abonnement waar het kan worden gelezen door een consument. Consumentenprocessen kunnen berichten van een abonnement ophalen via dezelfde semantiek als een berichtenwachtrij (een abonnement is een logische wachtrij). In de volgende afbeelding ziet u de implementatie van een prioriteitenwachtrij met Azure Service Bus-onderwerpen en -abonnementen.

Afbeelding 3: Implementatie van een prioriteitenwachtrij met Azure Service Bus-onderwerpen en -abonnementen

In de bovenstaande afbeelding maakt de toepassing meerdere berichten en wijst een aangepaste eigenschap genaamd Priority toe aan elk bericht met de waarde High of Low. De toepassing plaatst deze berichten in een onderwerp. Het onderwerp heeft twee gekoppelde abonnementen die beide berichten filteren door de eigenschap Priority te bekijken. Het ene abonnement accepteert berichten waarvoor de eigenschap Priority is ingesteld op High en het andere accepteert berichten waarvoor de eigenschap Priority is ingesteld op Low. Een groep consumenten leest berichten van elk abonnement. Het abonnement met hoge prioriteit heeft een grotere groep en deze consumenten kunnen worden uitgevoerd op krachtiger computers met meer beschikbare resources dan de consumenten in de groep met lage prioriteit.

Houd er rekening mee dat er in dit voorbeeld niet speciaals is aan de toewijzing van berichten met een hoge en lage prioriteit. Dit zijn eenvoudig labels die worden opgegeven als eigenschappen in elk bericht en die worden gebruikt om berichten naar een bepaald abonnement te sturen. Als extra prioriteiten vereist zijn, is het relatief gemakkelijk om meer abonnementen en groepen consumentenprocessen te maken die deze prioriteiten afhandelen.

De oplossing PriorityQueue, beschikbaar op GitHub, bevat een implementatie van deze benadering. Deze oplossing bevat twee werkrolprojecten genaamd PriorityQueue.High en PriorityQueue.Low. Deze werkrollen nemen gegevens over van de klasse PriorityWorkerRole die de functionaliteit bevat om verbinding te maken met een opgegeven abonnement in de methode OnStart.

De werkrollen PriorityQueue.High en PriorityQueue.Low maken verbinding met verschillende abonnementen, die worden gedefinieerd door hun configuratie-instellingen. Een beheerder kan verschillende aantallen van elke rol configureren om uit te voeren. Doorgaans zijn er meer exemplaren van de werkrol PriorityQueue.High dan de werkrol PriorityQueue.Low.

De methode Run in de klasse PriorityWorkerRole zorgt ervoor dat de virtuele methode ProcessMessage (ook gedefinieerd in de klasse PriorityWorkerRole) wordt uitgevoerd voor elk bericht dat in de wachtrij wordt ontvangen. In de volgende code ziet u de methoden Run en ProcessMessage. De klasse QueueManager, gedefinieerd in het project PriorityQueue.Shared, biedt helpermethoden voor het gebruik van Azure Service Bus-wachtrijen.

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));
  }
}

De werkrollen PriorityQueue.High en PriorityQueue.Low overschrijven beide de standaardfunctionaliteit van de methode ProcessMessage. De onderstaande code toont de methode ProcessMessage in de werkrol 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);
}

Wanneer een toepassing berichten plaatst in het onderwerp dat is gekoppeld aan de abonnementen die worden gebruikt door de werkrollen PriorityQueue.High en PriorityQueue.Low, geeft deze de prioriteit aan met behulp van de aangepaste eigenschap Priority, zoals weergegeven in het volgende codevoorbeeld. Deze code (geïmplementeerd in de klasse WorkerRole in het project PriorityQueue.Sender) gebruikt de helpermethode SendBatchAsync van de klasse QueueManager om berichten in batches in een onderwerp te plaatsen.

// 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();

Volgende stappen

De volgende richtlijnen zijn mogelijk ook relevant bij de implementatie van dit patroon:

  • Een voorbeeld waarin dit patroon wordt gedemonstreerd, is beschikbaar op GitHub.

  • Asynchronous Messaging Primer (Inleiding in asynchrone berichtpatronen). Een consumentenservice die een aanvraag verwerkt, moet mogelijk een antwoord sturen naar het exemplaar van de toepassing dat de aanvraag heeft geplaatst. Biedt informatie over de strategieën die u kunt gebruiken om de aanvraag/antwoord-berichten te implementeren.

  • Richtlijnen voor automatisch schalen. Het is wellicht mogelijk de grootte van de groep consumentenprocessen die een wachtrij verwerkt te schalen, afhankelijk van de lengte van de wachtrij. Deze strategie kan u helpen de prestaties te verbeteren, met name bij groepen die berichten met hoge prioriteit verwerken.

De volgende patronen kunnen ook relevant zijn bij het implementeren van dit patroon:

  • Patroon Concurrerende consumenten. Als u de doorvoer van de wachtrijen wilt verhogen, is het mogelijk meerdere consumenten te laten luisteren naar dezelfde wachtrij en de taken parallel te verwerken. Deze consumenten concurreren om berichten, maar slechts één ervan kan een bericht verwerken. Biedt meer informatie over de voor- en nadelen van de implementatie van deze benadering.

  • Patroon voor beperking. U kunt aanvraagbeperking implementeren met behulp van wachtrijen. Berichten met prioriteit kunnen worden gebruikt om ervoor te zorgen dat aanvragen van kritieke toepassingen of toepassingen die worden uitgevoerd door waardevolle klanten, prioriteit krijgen ten opzichte van aanvragen van minder belangrijke toepassingen.

  • Bedrijfsintegratie patronen met Service Bus in de blog van Abhishek Lal.