Scenario voor het afstemmen van prestaties: Gedistribueerde bedrijfstransacties
In dit artikel wordt beschreven hoe een ontwikkelingsteam metrische gegevens gebruikt om knelpunten te vinden en de prestaties van een gedistribueerd systeem te verbeteren. Het artikel is gebaseerd op de werkelijke belastingstests die we hebben uitgevoerd voor een voorbeeldtoepassing. De toepassing is van de basislijn Azure Kubernetes Service (AKS) voor microservices.
Dit artikel maakt deel uit van een reeks. Lees het eerste deel hier.
Scenario: een clienttoepassing initieert een bedrijfstransactie die meerdere stappen omvat.
Dit scenario omvat een drone delivery-toepassing die wordt uitgevoerd op AKS. Klanten gebruiken een web-app om leveringen per drone te plannen. Voor elke transactie zijn meerdere stappen vereist die worden uitgevoerd door afzonderlijke microservices op de back-end:
- De Leveringsservice beheert leveringen.
- De Drone Scheduler-service plant drones om op te halen.
- De pakketservice beheert pakketten.
Er zijn twee andere services: een opnameservice die clientaanvragen accepteert en deze in een wachtrij plaatst voor verwerking, en een Workflow-service die de stappen in de werkstroom coördineert.

Zie Een microservicearchitectuurontwerpen voor meer informatie over dit scenario.
Test 1: Basislijn
Voor de eerste belastingstest heeft het team een AKS-cluster met zes knooppunt gemaakt en drie replica's van elke microservice geïmplementeerd. De belastingstest was een stapbelastingstest, beginnend bij twee gesimuleerde gebruikers en het opstarten van maximaal 40 gesimuleerde gebruikers.
| Instelling | Waarde |
|---|---|
| Clusterknooppunten | 6 |
| Peulen | 3 per service |
In het volgende diagram ziet u de resultaten van de belastingstest, zoals weergegeven in Visual Studio. De paarse lijn geeft de gebruikersbelasting weer en de oranje lijn geeft het totale aantal aanvragen weer.

Het eerste wat u over dit scenario moet realiseren, is dat clientaanvragen per seconde geen nuttige prestatiemeter zijn. Dat komt doordat aanvragen asynchroon worden verwerkt door de toepassing, zodat de client meteen een antwoord krijgt. De antwoordcode is altijd HTTP 202 (Geaccepteerd), wat betekent dat de aanvraag is geaccepteerd, maar de verwerking niet is voltooid.
Wat we echt willen weten, is of de back-end de aanvraagsnelheid bij houdt. De Service Bus kan pieken opvangen, maar als de back-end een langdurige belasting niet kan verwerken, zal de verwerking steeds verder achterop komen.
Hier ziet u een meer informatieve grafiek. Het aantal binnenkomende en uitgaande berichten wordt in de Service Bus wachtrij. Binnenkomende berichten worden lichtblauw weergegeven en uitgaande berichten worden donkerblauw weergegeven:

Deze grafiek laat zien dat de snelheid van binnenkomende berichten toeneemt, een piek bereikt en vervolgens weer terug daalt naar nul aan het einde van de belastingstest. Maar het aantal uitgaande berichten piekt vroeg in de test en daalt vervolgens. Dit betekent dat de Workflow-service, die de aanvragen verwerkt, niet bijwerkt. Zelfs nadat de belastingstest is beëindigd (ongeveer 9:22 in de grafiek), worden berichten nog steeds verwerkt omdat de Workflow-service de wachtrij blijft leeglaten.
Wat vertraagt de verwerking? Het eerste wat u moet zoeken, zijn fouten of uitzonderingen die kunnen duiden op een systematisch probleem. Het toepassingskaart in Azure Monitor toont de grafiek van aanroepen tussen onderdelen. Dit is een snelle manier om problemen op te lossen en vervolgens door te klikken voor meer informatie.
Het toepassingskaart laat zien dat de Workflow-service fouten krijgt van de Leveringsservice:

Voor meer informatie kunt u een knooppunt in de grafiek selecteren en in een end-to-end transactieweergave klikken. In dit geval ziet u dat de Leveringsservice HTTP 500-fouten retournt. De foutberichten geven aan dat er een uitzondering wordt veroorzaakt door geheugenlimieten in Azure Cache voor Redis.

Mogelijk ziet u dat deze aanroepen naar Redis niet worden weergegeven in het toepassingskaart. Dat komt doordat de .NET-bibliotheek voor Application Insights geen ingebouwde ondersteuning voor het bijhouden van Redis als afhankelijkheid heeft. (Zie Automatische verzameling van afhankelijkheden voor een lijst met wat buiten het vak wordt ondersteund.) Als terugval kunt u de TrackDependency-API gebruiken om elke afhankelijkheid bij te houden. Belastingstests tonen vaak dit soort hiaten in de telemetrie, die kunnen worden verteerd.
Test 2: Grotere cachegrootte
Voor de tweede belastingstest heeft het ontwikkelteam de cachegrootte in Azure Cache voor Redis. (Zie How to Scale Azure Cache voor Redis.) Door deze wijziging zijn de out-of-memory-uitzonderingen opgelost en nu bevat het toepassingskaart nul fouten:

Er is echter nog steeds een enorme vertraging bij het verwerken van berichten. Op het piekpunt van de belastingstest is de snelheid van het binnenkomende bericht meer dan 5 × de uitgaande snelheid:

In de volgende grafiek wordt de doorvoer in termen van voltooiing van berichten met andere woorden, de snelheid waarmee de Werkstroomservice de Service Bus — als voltooid markeert. Elk punt in de grafiek vertegenwoordigt 5 seconden aan gegevens, met een maximale doorvoer van ~16 per seconde.

Deze grafiek is gegenereerd door een query uit te voeren in de Log Analytics-werkruimte met behulp van de Kusto-querytaal:
let start=datetime("2020-07-31T22:30:00.000Z");
let end=datetime("2020-07-31T22:45:00.000Z");
dependencies
| where cloud_RoleName == 'fabrikam-workflow'
| where timestamp > start and timestamp < end
| where type == 'Azure Service Bus'
| where target has 'https://dev-i-iuosnlbwkzkau.servicebus.windows.net'
| where client_Type == "PC"
| where name == "Complete"
| summarize succeeded=sumif(itemCount, success == true), failed=sumif(itemCount, success == false) by bin(timestamp, 5s)
| render timechart
Test 3: De back-endservices uitschalen
Het lijkt erop dat de back-end het knelpunt is. Een eenvoudige volgende stap is het uitschalen van de bedrijfsservices (Pakket, Levering en Drone Scheduler) en te kijken of de doorvoer verbetert. Voor de volgende belastingstest schaalde het team deze services uit van drie replica's naar zes replica's.
| Instelling | Waarde |
|---|---|
| Clusterknooppunten | 6 |
| Opnameservice | 3 replica's |
| Werkstroomservice | 3 replica's |
| Pakket, levering, Drone Scheduler-services | 6 replica's elk |
Helaas toont deze belastingstest slechts een lichte verbetering. Uitgaande berichten blijven nog steeds niet up-up met binnenkomende berichten:

De doorvoer is consistenter, maar het maximum dat wordt bereikt, is ongeveer hetzelfde als de vorige test:

Als u bovendien naar Azure Monitor voor containerskijkt, lijkt het erop dat het probleem niet wordt veroorzaakt door resource-uitputting in het cluster. Ten eerste laten de metrische gegevens op knooppuntniveau zien dat het CPU-gebruik onder de 40% blijft, zelfs in het 95e percentiel, en dat het geheugengebruik ongeveer 20% is.

In een Kubernetes-omgeving is het mogelijk dat afzonderlijke pods resourcebeperkt zijn, zelfs wanneer dat niet het aantal knooppunten is. In de weergave op podniveau ziet u echter dat alle pods in orde zijn.

Uit deze test blijkt dat alleen het toevoegen van meer pods aan de back-end niet helpt. De volgende stap is om de Workflow-service beter te bekijken om te begrijpen wat er gebeurt wanneer berichten worden verwerkt. Application Insights laat zien dat de gemiddelde duur van de bewerking van de Process Werkstroomservice 246 ms is.

We kunnen ook een query uitvoeren om metrische gegevens op te halen over de afzonderlijke bewerkingen binnen elke transactie:
| Doel | percentile_duration_50 | percentile_duration_95 |
|---|---|---|
https://dev-i-iuosnlbwkzkau.servicebus.windows.net/ | dev-i-iuosnlbwkzkau |
86.66950203 | 283.4255578 |
| levering | 37 | 57 |
| pakket | 12 | 17 |
| dronescheduler | 21 | 41 |
De eerste rij in deze tabel vertegenwoordigt de Service Bus wachtrij. De andere rijen zijn de aanroepen naar de back-endservices. Ter referentie is dit de Log Analytics-query voor deze tabel:
let start=datetime("2020-07-31T22:30:00.000Z");
let end=datetime("2020-07-31T22:45:00.000Z");
let dataset=dependencies
| where timestamp > start and timestamp < end
| where (cloud_RoleName == 'fabrikam-workflow')
| where name == 'Complete' or target in ('package', 'delivery', 'dronescheduler');
dataset
| summarize percentiles(duration, 50, 95) by target

Deze latentie lijkt redelijk. Maar dit is het belangrijkste inzicht: Als de totale bewerkingstijd ~250 ms is, wordt er een strikte bovengrens gegeven aan hoe snel berichten serieel kunnen worden verwerkt. De sleutel tot het verbeteren van de doorvoer is daarom groter parallellisme.
Dat moet mogelijk zijn in dit scenario, om twee redenen:
- Dit zijn netwerkoproepen, dus de meeste tijd wordt besteed aan het wachten op voltooiing van I/O
- De berichten zijn onafhankelijk en hoeven niet op volgorde te worden verwerkt.
Test 4: Parallellelisme verhogen
Voor deze test heeft het team zich gericht op het vergroten van de parallellisme. Hiervoor hebben ze twee instellingen aangepast op de Service Bus client die wordt gebruikt door de Workflow-service:
| Instelling | Beschrijving | Standaard | Nieuwe waarde |
|---|---|---|---|
MaxConcurrentCalls |
Het maximum aantal berichten dat gelijktijdig moet worden verwerkt. | 1 | 20 |
PrefetchCount |
Hoeveel berichten de client van tevoren zal ophalen in de lokale cache. | 0 | 3000 |
Zie Best Practices for performance improvements using Service Bus Messaging (Best Practices voor prestatieverbeteringen met behulp van Service Bus Messaging) voor meer informatie over deze instellingen. Het uitvoeren van de test met deze instellingen heeft de volgende grafiek geproduceerd:

Zoals u weet, worden binnenkomende berichten lichtblauw weergegeven en worden uitgaande berichten donkerblauw weergegeven.
Op het eerste gezicht is dit een zeer grafische grafiek. De snelheid van het uitgaande bericht houdt de inkomende snelheid een tijdje exact bij. Maar om ongeveer 2:03 uur neemt de snelheid van binnenkomende berichten af, terwijl het aantal uitgaande berichten blijft toenemen, wat het totale aantal binnenkomende berichten overschrijdt. Dat lijkt onmogelijk.
De aanwijzing hiervoor is te vinden in de weergave Afhankelijkheden in Application Insights. Deze grafiek bevat een overzicht van alle aanroepen die de Workflow-service heeft gedaan Service Bus:

U ziet dat de vermelding voor DeadLetter . Die aanroepen geven aan dat berichten in de wachtrij Service Bus in de wachtrij staan.
Als u wilt weten wat er gebeurt, moet u inzicht hebben in de semantiek van Peek-Lock in Service Bus. Wanneer een client Peek-Lock gebruikt, Service Bus een bericht atomisch opgehaald en vergrendeld. Tijdens het vergrendelen wordt het bericht gegarandeerd niet bezorgd bij andere ontvangers. Als de vergrendeling is verlopen, wordt het bericht beschikbaar voor andere ontvangers. Na een maximum aantal bezorgingspogingen (dat configureerbaar is), Service Bus de berichten in een wachtrij voor in de wachtrij geplaatst, waar ze later kunnen worden onderzocht.
Houd er wel voor dat de Workflow-service vooraf grote batches met berichten — van 3000 berichten tegelijk opeet). Dit betekent dat de totale tijd voor het verwerken van elk bericht langer is, wat resulteert in time-outs van berichten, teruggaan naar de wachtrij en uiteindelijk naar de wachtrij voor in de wachtrij voor in wachtrij te gaan.
U kunt dit gedrag ook zien in de uitzonderingen, waarbij talloze MessageLostLockException uitzonderingen worden vastgelegd:

Test 5: Duur vergrendeling verhogen
Voor deze belastingstest is de duur van de berichtvergrendeling ingesteld op 5 minuten om time-outs van vergrendelingen te voorkomen. In de grafiek van binnenkomende en uitgaande berichten ziet u nu dat het systeem de snelheid van binnenkomende berichten bij houdt:

Gedurende de totale duur van de belastingstest van 8 minuten heeft de toepassing 25.000 bewerkingen voltooid, met een piekdoorvoer van 72 bewerkingen per seconde, wat een toename van de maximale doorvoer van 400% vertegenwoordigt.

Het uitvoeren van dezelfde test met een langere duur heeft echter aangetoond dat de toepassing deze snelheid niet kan handhaven:

De metrische gegevens van de container laten zien dat het maximale CPU-gebruik dicht bij 100% ligt. Op dit moment lijkt de toepassing CPU-gebonden te zijn. Het schalen van het cluster kan de prestaties nu verbeteren, in tegenstelling tot de vorige poging om uit te schalen.

Test 6: De back-endservices uitschalen (opnieuw)
Voor de laatste belastingstest in de reeks heeft het team het Kubernetes-cluster en de pods als volgt uitschaald:
| Instelling | Waarde |
|---|---|
| Clusterknooppunten | 12 |
| Opnameservice | 3 replica's |
| Werkstroomservice | 6 replica's |
| Pakket, levering, Drone Scheduler-services | 9 replica's per |
Deze test heeft geleid tot een hogere doorvoer, zonder significante vertragingen bij het verwerken van berichten. Bovendien blijft het CPU-gebruik van knooppunt onder de 80%.

Samenvatting
Voor dit scenario zijn de volgende knelpunten geïdentificeerd:
- Out-of-memory uitzonderingen in Azure Cache voor Redis.
- Gebrek aan parallelle verwerking van berichten.
- Onvoldoende berichtvergrendelingsduur, wat leidt tot time-outs van vergrendelingen en berichten die in de wachtrij voor niet-vergrendelde berichten worden geplaatst.
- CPU-uitputting.
Om deze problemen te diagnosticeren, vertrouwde het ontwikkelteam op de volgende metrische gegevens:
- De snelheid van binnenkomende en uitgaande Service Bus berichten.
- Toepassingskaart in Application Insights.
- Fouten en uitzonderingen.
- Aangepaste Log Analytics-query's.
- CPU- en geheugengebruik in Azure Monitor voor containers.
Volgende stappen
Zie Een microservicearchitectuurontwerpen voor meer informatie over het ontwerp van dit scenario.