Mönster för hastighetsbegränsning

Kontrollera hur snabbt programmet skickar begäranden till en tjänst så att du håller dig inom tjänstens begränsningsgränser och övergripande kapacitet. Den här metoden hjälper dig att undvika eller minimera begränsningsrelaterade fel och att förutsäga genomströmningen mer exakt.

Hastighetsbegränsning är lämpligt i många scenarier, men det är särskilt användbart för storskaliga, repetitiva automatiserade uppgifter som batchbearbetning.

Kontext och problem

Att utföra ett stort antal åtgärder mot en begränsad tjänst kan leda till ökad trafik och minskat dataflöde, eftersom du måste spåra avvisade begäranden och sedan försöka utföra åtgärderna igen. När antalet operationer ökar kan en strypningsgräns innebära att data måste skickas om i flera omgångar, vilket leder till större prestandapåverkan.

Tänk till exempel på följande problematiska återförsök vid fel för att mata in data i Azure Cosmos DB:

  1. Ditt program måste mata in 10 000 poster i Azure Cosmos DB. Varje post kostar 10 begärandeenheter (RU) att importera, så totalt krävs 100 000 RU för att slutföra uppgiften.

  2. Din Azure Cosmos DB-instans har 20 000 RU i allokerad kapacitet.

  3. Du skickar alla 10 000 poster till Azure Cosmos DB. 2 000 poster skrivs och 8 000 poster avvisas.

  4. Du skickar de återstående 8 000 posterna till Azure Cosmos DB. 2 000 poster skrevs utan problem och 6 000 poster avvisades.

  5. Du skickar de återstående 6 000 posterna till Azure Cosmos DB. 2 000 poster skrivs in utan problem och 4 000 poster avvisas.

  6. Du skickar de återstående 4 000 posterna till Azure Cosmos DB. 2 000 poster skrivs in utan problem och 2 000 poster avvisas.

  7. Du skickar de återstående 2 000 posterna till Azure Cosmos DB. Alla skrevs utan problem.

Inmatningsjobbet slutförs, men först efter att 30 000 poster har skickats till Azure Cosmos DB. Hela datamängden består av endast 10 000 poster.

Det finns andra faktorer att tänka på i det här exemplet:

  • Ett stort antal fel kan också resultera i extra arbete för att logga dessa fel och bearbeta resulterande loggdata. Föregående metod hanterar 20 000 fel, och loggning av dessa fel kan medföra en bearbetnings-, minnes- eller lagringsresurskostnad.

  • Eftersom du inte känner till begränsningsgränserna för inmatningstjänsten kan du inte ange förväntningar på hur lång tid databearbetningen tar. Med hastighetsbegränsning kan du beräkna den tid som krävs för inmatning.

Lösning

Hastighetsbegränsning kan minska din trafik och potentiellt förbättra dataflödet genom att minska antalet poster som skickas till en tjänst under en viss tidsperiod.

En tjänst kan begränsa begäranden baserat på olika mått över tid, till exempel:

  • Antalet åtgärder (till exempel 20 begäranden per sekund).
  • Mängden data (till exempel 2 GiB per minut).
  • Den relativa kostnaden för åtgärder (till exempel 20 000 RU:er per sekund).

Oavsett vilket mått du använder för begränsning innebär din hastighetsbegränsningsimplementering att du kontrollerar antalet och/eller storleken på de åtgärder som skickas till tjänsten under en viss tidsperiod. Hastighetsbegränsning optimerar din användning av tjänsten utan att överskrida dess begränsningskapacitet.

I scenarier där dina API:er kan hantera begäranden snabbare än vad begränsade inmatningstjänster tillåter, måste du hantera hur snabbt du använder tjänsten. Att endast behandla begränsning som ett fel i datahastighetsmatchning och att buffra inmatningsförfrågningar tills tjänsten återhämtar sig skapar risk. Om programmet slutar svara i det här scenariot kan eventuella buffrade data gå förlorade.

För att undvika den här risken bör du överväga att skicka dina poster till ett beständigt meddelandesystem som kan hantera din fullständiga inmatningshastighet. (Tjänster som Azure Event Hubs kan hantera miljontals åtgärder per sekund.) Du kan sedan använda en eller flera jobbprocessorer för att läsa posterna från meddelandesystemet med en kontrollerad hastighet som ligger inom den begränsade tjänstens gränser. Om du skickar poster till meddelandesystemet kan du spara internt minne genom att endast ta bort de poster som kan bearbetas under ett visst tidsintervall.

Azure tillhandahåller flera hållbara meddelandetjänster som du kan använda med det här mönstret, inklusive:

Diagram som visar ett flöde för beständig meddelandehantering. Tre jobbprocessorer anropar en hastighetsbegränsad tjänst.

När du skickar poster kan den tidsperiod som du använder för att frigöra poster ha finare granularitet än den period som tjänsten tillämpar begränsning för. System ställer ofta in begränsningar baserat på tidsintervall som du enkelt kan förstå och arbeta med. För den dator som kör en tjänst kan dessa tidsramar dock vara mycket långa jämfört med hur snabbt den kan bearbeta information. Ett system kan till exempel strypa antalet förfrågningar per sekund eller per minut, men vanligtvis körs koden i storleksordningen nanosekunder eller millisekunder.

Även om det inte krävs rekommenderar vi ofta att du skickar mindre antal poster oftare för att förbättra dataflödet. Så, i stället för att försöka bearbeta poster i batchar inför en release en gång per sekund eller en gång per minut kan du göra det med finare granularitet för att hålla din resursförbrukning (minne, CPU och nätverk) på en jämnare nivå. Den här metoden förhindrar potentiella flaskhalsar som orsakas av plötsliga mängder begäranden. Om en tjänst till exempel tillåter 100 åtgärder per sekund kan implementeringen av en hastighetsbegränsare jämna ut begäranden genom att släppa 20 åtgärder var 200:e millisekunder, enligt följande diagram.

Diagram som visar ett hastighetsbegränsad flöde över tid.

Dessutom är det ibland nödvändigt att flera okoordinerade processer delar en begränsad tjänst. För att implementera hastighetsbegränsning i det här scenariot kan du logiskt partitionera tjänstens kapacitet och sedan använda ett distribuerat system för ömsesidig uteslutning för att hantera exklusiva lås på dessa partitioner. De okoordinerade processerna kan sedan konkurrera om lås på dessa partitioner när de behöver kapacitet. För varje partition som en process har ett lås för beviljas den en viss mängd kapacitet.

Om det begränsade systemet till exempel tillåter 500 begäranden per sekund kan du skapa 20 partitioner värda 25 begäranden per sekund vardera. Om en process behövde utfärda 100 förfrågningar skulle den kunna begära fyra partitioner från det distribuerade systemet för ömsesidig uteslutning. Systemet kan bevilja två partitioner i 10 sekunder. Processen skulle sedan betygsätta gränsen till 50 begäranden per sekund, slutföra uppgiften på två sekunder och sedan släppa låset.

Ett sätt att implementera det här mönstret är att använda Azure Storage. I det här scenariot skapar du en 0-bytes blob per logisk partition i en container. Dina program kan sedan få exklusiva lås direkt på dessa blobbar under en kort tidsperiod (till exempel 15 sekunder). För varje leasingavtal som en applikation beviljas kan den använda den mängd kapacitet som partitionen har. Programmet måste sedan spåra lånetiden så att programmet, när tiden går ut, kan sluta använda den kapacitet som det beviljades. När du implementerar det här mönstret vill du ofta att varje process ska försöka låna en slumpmässig partition när den behöver kapacitet.

Om du vill minska svarstiden ytterligare kan du allokera en liten mängd exklusiv kapacitet för varje process. En process skulle då endast försöka få tillgång till delad kapacitet om den behövde överskrida sin reserverade kapacitet.

Diagram som visar flera processer som konkurrerar om exklusiva lån på blobpartitioner i Azure Blob Storage.

Som ett alternativ till Azure Storage kan du även implementera den här typen av lånehanteringssystem med hjälp av tekniker som ZooKeeper, etcd och Redis/Redsync.

Problem och överväganden

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

  • Även om mönstret för hastighetsbegränsning kan minska antalet strypningsfel måste programmet fortfarande hantera eventuella strypningsfel som kan uppstå på rätt sätt.

  • Se till att återförsök samordnas med frekvensbegränsning. Blinda eller alltför aggressiva återförsök kan öka belastningen och skapa återförsöksstormar, så sprid signaler med bakåttryck (till exempel HTTP 429 med Retry-After) och använda ett begränsat antal återförsök med små slumpmässiga fördröjningar mellan försök.

  • Om ditt program har flera arbetsströmmar som har åtkomst till samma begränsade tjänst måste du integrera dem alla i din strategi för hastighetsbegränsning. Du kan till exempel ha stöd för massinläsning av poster i en databas men även frågor om poster i samma databas. Du kan hantera kapaciteten genom att se till att alla arbetsflöden styrs genom samma mekanism för hastighetsbegränsning. Du kan också reservera separata kapacitetspooler för varje arbetsström.

  • Den begränsade tjänsten kan användas i flera program. I vissa fall är det möjligt att samordna användningen (som du ser tidigare i den här artikeln). Om du börjar se ett större antal begränsningsfel än förväntat kan det ökade antalet indikera konkurrens mellan program som har åtkomst till en tjänst. I det här fallet kan du behöva överväga att tillfälligt minska det dataflöde som införts av din hastighetsbegränsningsmekanism tills användningen från andra program minskar.

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

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

  • Du måste minska begränsningsfel som genereras av en hastighetsbegränsad tjänst.

  • Du vill minimera trafiken jämfört med naiva metoder för återförsök på fel.

  • Du behöver minska minnesförbrukningen genom att bara dequeuera poster när det finns tillräckligt med kapacitet för att bearbeta dem.

Det här mönstret kanske inte är lämpligt när:

  • Åtgärden kräver omedelbar, synkron slutförande med mycket låg svarstid och kan inte tolerera köer eller uppskjuten bearbetning.

  • Den primära flaskhalsen är inte frekvensen på förfrågningar, utan snarare hög samtidighet eller konkurrens om resurser (till exempel CPU-mättnad eller långvarigt pågående arbete). I dessa fall är skalnings- eller samtidighetskontroller lämpligare.

Design av arbetsbelastning

Utvärdera hur du använder mönstret Hastighetsbegränsning i en arbetsbelastnings design för att hantera de mål och principer som beskrivs i Azure Well-Architected Framework-pelarna. Följande tabell innehåller vägledning om hur det här mönstret stöder målen för varje pelare.

Grundpelare Så här stöder det här mönstret pelarmål
Tillförlitlighets designbeslut hjälper din arbetsbelastning att bli motståndskraftig mot funktionsfel och säkerställer att den återställer sig till ett fullständigt fungerande tillstånd när ett fel uppstår. Den här taktiken skyddar klienten genom att erkänna och respektera begränsningarna och kostnaderna för att kommunicera med en tjänst när tjänsten föredrar att undvika överdriven användning.

- RE:07 Självbevarande

Om detta mönster inför kompromisser inom en pelare bör du överväga dem mot målen för de andra pelarna.

Example

I följande exempelprogram kan användare skicka poster av olika typer till ett API. Varje posttyp har en unik jobbprocessor som utför följande steg:

  1. Validation
  2. Berikning
  3. Infogning av posten i databasen

Alla komponenter i programmet (API, jobbprocessor A och jobbprocessor B) är separata processer som kan skalas separat. Processerna kommunicerar inte direkt med varandra.

Diagram som visar ett flöde med flera köer och flera processorer, med partitionerad leaselagring som skriver till en databas med begränsad genomströmning.

Diagrammet visar två användare som skickar poster via ett delat API. Varje posttyp dirigeras till en separat kö, bearbetas av en dedikerad jobbprocessor och skrivs in i en databas med begränsad genomströmning i en kontrollerad takt som styrs av blobpartitionsarrenden i Azure Storage. De två användarna skickar varsin batch med meddelanden till API-komponenten. En användare skickar 10 000 meddelanden och den andra skickar 5 000 meddelanden. API:t dirigerar 10 000 meddelanden till kö A och 5 000 meddelanden till kö B. Kö A är ansluten till jobbprocessor A, och kö B är ansluten till jobbprocessor B. Jobbprocessor A skriver till databasen med en hastighet av 300 poster per sekund. Jobbprocessor B skriver till databasen med 500 poster per sekund. Båda jobbprocessorerna ansluter till en databaskomponent som är markerad med "begränsad till 800 poster per sekund". Under de två jobbprocessorerna innehåller Azure Storage åtta blobpartitioner märkta 0 till och med 7. En anmärkning anger att varje partition motsvarar 100 poster per sekund. Flera pilar ansluter jobbprocessorerna till blobpartitionerna. Gröna pilar pekar från jobbprocessor A till partitionerna 0, 4 och 6, vilket indikerar att processorn har lån på dessa tre partitioner. Dessa leasingavtal förklarar hastigheten på 300 poster per sekund. Gröna pilar pekar från jobbprocessor B till partitionerna 1, 2, 3, 5 och 7. De här pilarna visar att den här processorn har leaseavtal för fem partitioner. Dessa leasingavtal står för dess hastighet på 500 poster per sekund. Misslyckade leaseförsök visas som röda pilar som korsar över till partitioner som redan innehas av den andra processorn. Dessa pilar visar konfliktlösning. Diagrammet visar att summan av alla partitioner i båda processorerna är lika med 800 poster per sekund, vilket matchar databasens begränsningsgräns.

I det här exemplet representerar varje blob-lease en fast andel av databasens tillåtna genomströmning. En processor kan bara ta från kön och skriva i en sammanlagd takt som motsvarar de leasar som den för närvarande har. När processorer över tid får eller förlorar leasingar ändras deras tillåtna skrivhastighet, vilket håller den totala databastrafiken inom den konfigurerade gränsen samtidigt som allt arbete i kön ändå kan fortskrida.

Det här diagrammet innehåller följande arbetsflöde:

  1. En användare skickar 10 000 poster av typen A till API:et.
  2. API:t lägger de 10 000 posterna i kö A.
  3. En användare skickar 5 000 poster av typen B till API:et.
  4. API:t lägger de 5 000 posterna i kö B.
  5. jobbprocessor A ser att kö A innehåller poster och försöker få ett exklusivt arrende på blob 2.
  6. Jobbprocessor B ser att kön B har poster och försöker få ett exklusivt lån på blob 2.
  7. Jobbprocessor A misslyckas med att erhålla leasen.
  8. Jobbprocessor B erhåller ett lease på blob 2 i 15 sekunder. Den kan nu hastighetsgränsa begäranden till databasen med en hastighet av 100 per sekund.
  9. Jobbprocessor B hämtar 100 poster från kö B och skriver dem.
  10. En sekund går.
  11. Jobbprocessorn A ser att kö A har fler poster och försöker få exklusiv åtkomst till blob 6.
  12. jobbprocessor B ser att kö B har fler poster och försöker få ensamrätt till blob 3.
  13. Jobbprocessor A får leasen på blobben 6 i 15 sekunder. Den kan nu hastighetsgränsa begäranden till databasen med en hastighet av 100 per sekund.
  14. Jobbprocessor B får ett lån på blobben 3 i 15 sekunder. Den kan nu hastighetsgränsa begäranden till databasen med en hastighet av 200 per sekund. (Den har även leasen för blob 2.)
  15. Jobbprocessor A hämtar 100 poster från kö A och skriver dem.
  16. Jobbprocessorn B tar ut 200 poster från kö B och skriver dem.
  17. En sekund går.
  18. Jobbprocessor A ser att kön A har fler poster och försöker få ett exklusivt lån på blob 0.
  19. Jobbprocessor B ser att kö B har fler poster och försöker få ett exklusivt arrende på blob 1.
  20. Jobbprocessor A hämtar lånet på blob 0 i 15 sekunder. Den kan nu hastighetsgränsa begäranden till databasen med en hastighet av 200 per sekund. (Den har även leasen för blob 6.)
  21. Jobbprocessor B erhåller leasen för blob 1 i 15 sekunder. Den kan nu hastighetsgränsa begäranden till databasen med en hastighet av 300 per sekund. (Det har även arrendet för blobbarna 2 och 3.)
  22. Jobbprocessor A avköar 200 poster från kö A och skriver dem.
  23. Jobbprocessor B hämtar 300 poster från kön B och skriver dem.
  24. Och så vidare.

Efter 15 sekunder slutförs fortfarande inte ett eller båda jobben. När leasar löper ut bör en processor också minska antalet begäranden som den avköar och skriver.

Implementeringar av det här mönstret finns på olika programmeringsspråk:

  • Go-implementering är tillgängligt på GitHub.
  • Java-implementering är tillgängligt på GitHub.

Nästa steg

Följande vägledning kan också vara relevant när du implementerar det här mönstret:

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

  • Throttling. Mönstret för hastighetsbegränsning implementeras vanligtvis som svar på en tjänst med hastighetsbegränsning.

  • Retry. När begäranden till en begränsad tjänst resulterar i begränsningsfel är det vanligtvis lämpligt att försöka igen efter ett lämpligt intervall.

  • Köbaserad belastningsutjämning liknar mönstret för hastighetsbegränsning men skiljer sig på flera viktiga sätt:

    • Hastighetsbegränsning behöver inte nödvändigtvis använda köer för att hantera belastningen, men den behöver använda en beständig meddelandetjänst. Ett frekvensbegränsningsmönster kan till exempel använda tjänster som Apache Kafka eller Event Hubs.

    • Mönstret för hastighetsbegränsning introducerar konceptet med ett distribuerat system för ömsesidig uteslutning över partitioner, vilket gör det möjligt att hantera kapacitet för flera okoordinerade processer som kommunicerar med samma tjänst med begränsad genomströmning.

    • Ett köbaserat mönster för belastningsutjämning är tillämpligt när det finns en skillnad i prestanda mellan tjänster eller när du vill förbättra motståndskraften. Därför är det ett bredare mönster än hastighetsbegränsning, som mer specifikt handlar om att effektivt komma åt en begränsad tjänst.