https://d226lax1qjow5r.cloudfront.net/blog/blogposts/respect-api-rate-limits-with-a-backoff-dr/Social_API-Rate-Limits_Backoff_1200x627.png

API-Ratenbegrenzungen mit einem Backoff einhalten

Zuletzt aktualisiert am October 22, 2020

Lesedauer: 13 Minuten

Dieser Artikel wurde im April 2025 aktualisiert.

Bei der Arbeit mit den Vonage Kommunikations-APIs-oder wirklich jeder API-sollten Sie deren Ratenbeschränkungen beachten. Ratenbeschränkungen sind eine der Möglichkeiten, mit denen Dienstanbieter die Last auf ihren Servern reduzieren, böswillige Aktivitäten verhindern oder sicherstellen können, dass ein einzelner Benutzer die verfügbaren Ressourcen nicht monopolisiert.

In diesem Artikel werden wir uns ansehen, wie Sie Ihre API-Aufrufe am besten verwalten können, um sicherzustellen, dass Sie ein "guter API-Bürger" sind. Wir werden uns ansehen, wie Sie die Vonage Communication API einhalten können Tarifgrenzen einhalten und gleichzeitig effizient sein und Ihre API-Aufrufe so schnell wie möglich abwickeln können.

Vonage API-Konto

Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.

In diesem Lernprogramm wird auch eine virtuelle Telefonnummer verwendet. Um eine zu erwerben, gehen Sie zu Rufnummern > Rufnummern kaufen und suchen Sie nach einer Nummer, die Ihren Anforderungen entspricht.

Um eine virtuelle Rufnummer zu kaufen, gehen Sie zu Ihrem API-Dashboard und befolgen Sie die unten aufgeführten Schritte.

Steps on how to purchase a phone number from the dashboard, from selecting the number and confirming the selection.Purchase a phone number

  1. Gehen Sie zu Ihrem API-Dashboard

  2. Navigieren Sie zu BUILD & MANAGE > Numbers > Buy Numbers.

  3. Wählen Sie die gewünschten Attribute und klicken Sie dann auf Suchen

  4. Klicken Sie auf die Schaltfläche Kaufen neben der gewünschten Nummer und bestätigen Sie Ihren Kauf.

  5. Um zu bestätigen, dass Sie die virtuelle Nummer erworben haben, gehen Sie im linken Navigationsmenü unter BUILD & MANAGE auf Numbers und dann auf Your Numbers

Was bedeutet es, ein guter API-Bürger zu sein?

Wenn wir mit externen APIs arbeiten, sollten wir immer versuchen, unseren Durchsatz auf einem akzeptablen Niveau zu halten. Aber es können Fehler passieren, es kann ein plötzlicher Anstieg der Nutzung auftreten, und wir überschreiten schließlich die Durchsatzgrenze, was dazu führt, dass unser API-Aufruf fehlschlägt.

Wenn das passiert, kann es verlockend sein, es sofort wieder zu versuchen, aber das ist kontraproduktiv. Wenn Ihre API-Aufrufe fehlschlagen, weil Sie das Ratenlimit erreicht haben, bedeutet dies, dass der Dienst Sie auffordert, langsamer zu werden. Wenn Sie dieselbe Anfrage sofort noch einmal versuchen, ist das keine Verlangsamung und kann dazu führen, dass Sie von einigen Diensten ausgeschlossen werden. Stattdessen sollten Sie sich zurückhalten und eine Pause einlegen, bevor Sie es erneut versuchen.

API-Aufrufe mit Backoff verzögern

Bei einem Backoff wartet man, bevor man eine Handlung vornimmt. Die Wartezeit kann mit vielen verschiedenen Strategien berechnet werden, aber einige der gängigsten sind:

  • Konstant: eine konstante Zeitspanne zwischen den einzelnen Versuchen abwarten. Wenn wir zum Beispiel eine konstante Verzögerung von 1 Sekunde haben, werden unsere Versuche in Abständen von 1s, 2s, 3s, 4s, 5s, 6s, 7s, usw. stattfinden.

  • Fibonaccial: Hier wird die Fibonacci-Zahl verwendet, die dem aktuellen Versuch entspricht, so dass die Verzögerungen 1s, 1s, 2s, 3s, 5s, 8s, 13s usw. betragen.

  • Exponentiell: Die Verzögerung wird berechnet als 2 hoch der Anzahl der erfolglosen Versuche, die unternommen wurden. Zum Beispiel:

  • 2^1 = 2 = 2

  • 2^2 = 2 * 2 = 4

  • 2^3 = 2 2 2 = 8

  • 2^4 = 2 2 2 * 2 = 16

  • 2^5 = 2 2 2 2 2 = 32

  • 2^6 = 2 2 2 2 2 * 2 = 64

  • 2^7 = 2 2 2 2 2 2 2 = 128

Es gibt noch andere Strategien - fest, linear, polynomial -, aber für diesen Artikel bleiben wir bei der Exponential-Backoff-Strategie, die von dem Python-Backoff-Paket.

Versuch der Rückabwicklung

Ich möchte nicht die API-Tarifgrenze von Vonage auslösen, nur um das Backoff-Paket zu demonstrieren. Stattdessen erstellen wir etwas Mock-Code mit asyncio.

import asyncio
from datetime import datetime
 
import backoff
import uvloop
 
start_time = datetime.now().timestamp()
 
 
@backoff.on_predicate(backoff.constant, max_time=300, jitter=lambda x: x)
async def slow_operation():
   with open("./attempts.log", "a") as f:
       f.write(f"{datetime.now().timestamp() - start_time}\n")
   return False
 
 
async def main(loop):
   for x in range(0, 500):
       asyncio.ensure_future(slow_operation(), loop=loop)
 
 
if __name__ == "__main__":
   loop = uvloop.new_event_loop()
   loop.create_task(main(loop))
   loop.run_forever()

In diesem Beispiel protokolliert die slow_operation() Funktion die Millisekunden seit der Epoche auf und gibt dann Falsezurück und stellt sicher, dass der Backoff-Dekorator bei jedem Aufruf der Funktion ausgeführt wird. Backoff wird so lange ausgeführt slow_operation() bis die Verzögerung den Wert max_time von 300 Sekunden erreicht hat, woraufhin sie aufgibt.

Um viele Datenpunkte für das Diagramm zu erzeugen, stellen wir die slow_operation() Funktion 500 Mal innerhalb unserer asyncio-Schleife.

Wenn wir die Anzahl der versuchten Funktionsaufrufe pro Sekunde grafisch darstellen, sieht es so aus, wenn wir eine konstante Strategie verwenden:

A constant traffic pattern visualisedA constant traffic pattern visualised

Es gibt ein dickes Band im Bereich von 40 bis 60 Funktionsaufrufen, so dass ein konstanter Backoff für unsere Bedürfnisse nicht geeignet ist. Jede Sekunde wird die API mit Anfragen überflutet, was den Durchsatz viel zu hoch hält, und wir werden wahrscheinlich weiterhin eine Ratenbeschränkung haben.

Wenn wir jedoch den gleichen Code mit der Exponentialstrategie ausführen, erhalten wir ein ganz anderes Diagramm.

A visualisation of a traffic pattern using exponential backoffA visualisation of a traffic pattern using exponential backoff

Dieses Diagramm ist viel besser. Wir können sehen, wo die Backoff-Strategie die Verzögerung erhöht hat, was den Durchsatz verringert und uns hoffentlich genug Zeit gibt, die Ratenbegrenzung zu beenden. Aber jetzt haben wir ein anderes Problem.

Aus dem Diagramm geht hervor, dass sich die Anrufe am Ende der Verzögerungen bündeln. Dies könnte dazu führen, dass diese Bündel die Ratenbegrenzung erneut auslösen. Um die Bildung dieser Bündel zu verhindern, verwenden wir Jitter.

Schaffung einer gleichmäßiger verteilten Arbeitslast mit Zufallsgenerator

Jitter fügt der Berechnung der Verzögerungsdauer in unserem Backoff einen Zufallsfaktor hinzu.

sleep = random.uniform(0, delay)

Das Python-Backoff-Paket enthält diesen Jitter standardmäßig. In den obigen Codebeispielen entferne ich ihn mit einer Lambda-Funktion. Erzeugen wir also erneut den Exponentialgraphen, aber diesmal mit Jitter.

An exponential backoff traffic pattern visualisedAn exponential backoff traffic pattern visualised

Da wir immer noch eine exponentielle Strategie verwenden, können wir sehen, dass die Anzahl der Aufrufe sehr schnell abfällt, aber dank der zusätzlichen Zufälligkeit des Jitters sehen wir keine Häufung. Stattdessen sind die Funktionsaufrufe pro Sekunde gering und gleichmäßiger verteilt.

Verarbeitung von SMS-Warteschlangen mit Backoff

Die Tarifgrenzen hängen von der von Ihnen verwendeten Vonage Kommunikations-API ab. Zum Beispiel haben die Redact und Applications APIs haben ein Ratenlimit von 170 Anfragen pro Sekunde. Aufgrund von Beschränkungen des Netzbetreibers kann jedoch die Ratenbegrenzung für ausgehende SMS bis zu einer Anfrage pro Sekunde betragen.. Damit ist SMS der perfekte Kandidat für die Anwendung der oben beschriebenen Backoff-Techniken.

Aufgaben-Warteschlangen und Makler

Python verfügt über eine große Anzahl von Aufgaben-Warteschlangen, aus denen man wählen kann -Celery, [huey²], RQ, Kuyruk, Taskmaster, Dramatiq, WorQ-und fast ebenso viele Makler-MongoDB, Redis, RabbitMQ, SQS. Einige dieser Task-Warteschlangen verfügen über eine integrierte Unterstützung für Backoff, sind aber auch sehr komplex, so dass sie in diesem Artikel nicht behandelt werden können.

Wenn Sie sich jedoch mit den zugrundeliegenden Techniken und den Gründen für die Verwendung einer Task-Warteschlange, Backoff, Jitter usw. vertraut gemacht haben, empfehle ich Ihnen, die Links zu den Task-Warteschlangen oben noch einmal zu lesen. Die Code-Beispiele, die wir uns im weiteren Verlauf dieses Artikels ansehen werden, sind absichtlich kurz gehalten, damit wir uns nur auf die Durchsatzverwaltung konzentrieren können, während die obigen Pakete viel robuster und produktionsreif sind.

Asynchroner Versand von SMS mit Vonage Kommunikations-APIs

Um sicherzustellen, dass die Netzwerklatenz bei einer einzelnen Anfrage nicht unsere gesamte Anwendung blockiert, werden wir unsere SMS asynchron versenden. Das bedeutet aber auch wir können nicht das Vonage Python SDK verwenden können, um unsere SMS zu senden. Das Python SDK ist nicht asynchron, denn es verwendet Requests verwendet, die blockieren.

Wir können einen Blick auf die Messages API Beispielanforderung aus der Dokumentation ansehen, um eine Vorstellung davon zu bekommen, was das Vonage Python SDK für uns tut:

curl -X POST https://api.nexmo.com/v0.1/messages \ -H 'Authorization: Bearer '$JWT\ -H 'Content-Type: application/json' \ -H 'Accept: application/json' \ -d $'{ "from": { "type": "sms", "number": "'$FROM_NUMBER'" }, "to": { "type": "sms", "number": "'$TO_NUMBER'" }, "message": { "content": { "type": "text", "text": "This is an SMS sent from the Messages API" } } }'

In diesem Codeschnipsel sehen wir, dass wir eine POST-Anfrage an den Endpunkt stellen https://api.nexmo.com/v0.1/message. Die Anfrage enthält einige Informationen über die Art des Inhalts, den wir senden und als Antwort erwarten. Die wichtigsten Teile sind jedoch der Authorization-Header und die Option data (-d).

Die Anfrage wird autorisiert durch JSON-Web-Token (JWT). JWT ist ein offener Industriestandard, und es gibt mehrere Python-Pakete, die bei der Erstellung helfen. Praktischerweise verfügt das Python-SDK von Vonage bereits über eine Funktion, die wir aufrufen können, um ein gültiges JWT für die Anfrage zu erstellen. Da die JWT-Generierung schnell geht, keine Netzwerk-E/A erfordert und nur einmal zu Beginn des Skripts ausgeführt wird, spielt es keine Rolle, dass sie nicht asynchron ist.

Erstellen Ihrer Bewerbung

Installieren Sie die Vonage CLI global mit diesem Befehl:

npm install @vonage/cli -g

Als nächstes konfigurieren Sie die CLI mit Ihrem Vonage-API-Schlüssel und -Geheimnis. Sie finden diese Informationen im Entwickler-Dashboard.

vonage config:set --apiKey=VONAGE_API_KEY --apiSecret=VONAGE_API_SECRET

Erstellen Sie ein neues Verzeichnis für Ihr Projekt und legen Sie eine CD darin ab:

mkdir my_project
CD my_project

Verwenden Sie nun die CLI, um eine Vonage-Anwendung zu erstellen.

vonage apps:create
✔ Application Name … my_project
✔ Select App Capabilities › Messages
✔ Create messages webhooks? … no
✔ Allow use of data for AI training? no

Mit diesem Befehl wird Ihr privater Schlüssel in der Datei my_project gespeichert.key. Diesen benötigen wir bei der Erstellung unseres JWT zusammen mit der Anwendungs-ID. Die Anwendungs-ID wird im Terminal ausgegeben, wenn Sie den Befehl app:create ausführen, oder Sie finden sie in Ihrem Vonage Dashboard.

Jetzt brauchen Sie eine Nummer, damit Sie Anrufe empfangen können. Sie können eine Nummer mieten, indem Sie den folgenden Befehl verwenden (ersetzen Sie die Landesvorwahl durch Ihre Vorwahl). Wenn Sie sich zum Beispiel in den USA befinden, ersetzen Sie GB durch US:

vonage numbers:search US vonage numbers:buy [NUMBER] [COUNTRYCODE]

Verknüpfen Sie nun die Nummer mit Ihrer App:

vonage apps:link --number=VONAGE_NUMBER APP_ID

Versenden der SMS

import vonage


vonage_client = vonage.Client(
   application_id=os.environ["VONAGE_APPLICATION_ID"],
   private_key=os.environ["VONAGE_PRIVATE_KEY"],
)
jwt = vonage_client.generate_application_jwt()
 
@backoff.on_predicate(backoff.expo, max_time=300)
async def send_sms(recipient, message):
   async with httpx.AsyncClient() as httpx_client:
       response = await httpx_client.post(
           "https://api.nexmo.com/v0.1/messages",
           headers={
               "Authorization": b"Bearer " + jwt,
               "Content-Type": "application/json",
               "Accept": "application/json",
           },
           json={
               "from": {"type": "sms", "number": os.environ["VONAGE_NUMBER"]},
               "to": {"type": "sms", "number": recipient},
               "message": {"content": {"type": "text", "text": message,}},
           },
       )
 
        return response.status_code == 202

Am Anfang unseres Skripts, außerhalb der asynchronen Funktion, instanziieren wir unseren Vonage-Client mit der Anwendungs-ID und dem privaten Schlüssel. Ich habe diese in Umgebungsvariablen gespeichert, damit sie nicht fest in meinem Skript kodiert sind.

Die Funktion send_sms Funktion führt die API-Anforderung aus, also hat diese Funktion unseren Backoff-Dekorator. Wir verwenden on_predicateund wenn die Funktion Falsezurückgibt, wird sie es erneut versuchen. Ich habe die max_time bei 300 Sekunden gehalten, aber wir könnten auch ein max_attempt Grenze oder beides setzen!

Um die asynchrone POST-Anfrage zu stellen, verwenden wir httpx. httpx ist ein HTTP-Client für Python 3 mit einer sehr ähnlichen Schnittstelle wie Requests, aber es unterstützt async. Ich habe die httpx-Anfrage so gut wie möglich an das cURL-Beispiel angelehnt, das wir uns oben angesehen haben. Wir haben die Header mit den Content-Type-Informationen sowie den Authorization-Header, der das vom Vonage Python SDK für uns generierte JWT enthält.

Unsere Nutzlast ist eine JSON-Zeichenfolge, die die Absendernummer, die Nummer des Empfängers und unsere Nachricht enthält.

Schließlich überprüfen wir den HTTP-Statuscode, der von der Messages API auf die Anfrage zurückgegeben wird. Alles andere als 202 Accepted führt dazu, dass die Funktion False zurückgibt und einen weiteren Versuch auslöst.

SMS in einer Warteschleife einreihen

In meinem Beispielskript habe ich einfach eine Liste von Empfängern fest einprogrammiert.

async def main(loop):
   recipients = [
       "13055550157",
       "15615550134",
   ]
   message = "✨✨✨Hello! This is an SMS from the Vonage Communication APIs Messages API using exponential backoff and jitter 😄"
 
   for recipient in recipients:
       asyncio.ensure_future(send_sms(recipient, message), loop=loop)
 
 
if __name__ == "__main__":
   loop = uvloop.new_event_loop()
   loop.create_task(main(loop))
   loop.run_forever()

Aber hier könnte man eine Aufgabenwarteschlange oder einen Broker verwenden. Außerdem bin ich auch nicht ein guter API-Bürger! Ich weiß, dass die Nachrichten-API eine Ratenbeschränkung von 1 Nachricht pro Sekunde beim Senden von Nachrichten innerhalb der USA hat, aber ich habe keine Verzögerung in meiner Schleife!

Mein Skript versucht, die API-Aufrufe ohne Verzögerung durchzuführen, wodurch die Ratenbegrenzung sehr schnell ausgelöst wird. Der Backoff hilft uns zwar dabei, die Überschreitung des maximal zulässigen Durchsatzes zu bewältigen, sollte aber nur ein letzter Ausweg sein. Um möglichst effizient zu sein, sollten wir so nah wie möglich an die Ratenbegrenzung herankommen, ohne sie jedoch zu überschreiten. Die Hinzufügung einer kurzen Ruhephase beim Hinzufügen von Aufgaben zur Schleife sollte dabei helfen.

for recipient in recipients:
       asyncio.ensure_future(send_sms(recipient, message), loop=loop)
       await asyncio.sleep(1)

Alles zusammenfügen

In dieser Aufzeichnung habe ich den Ruhezustand entfernt und das Beispiel so verändert, dass es versucht, mehrere hundert Anfragen auf einmal zu stellen, was dazu führt, dass die Drosselung durch Vonage fast sofort einsetzt. Aber sehen Sie sich an, was nach ein paar Sekunden passiert.

Traffic to an API being backed off over time until blocked requests endTraffic to an API being backed off over time until blocked requests end

Kaum hat das Skript begonnen, überschreitet es das Ratenlimit der Messages API, und der Endpunkt gibt den HTTP-Status 429 "Too Many Requests" zurück. Das Skript beginnt also mit einem Backoff. Zunächst scheint die Anzahl der fehlgeschlagenen Anfragen in etwa gleich zu bleiben, aber da die Verzögerung exponentiell zunimmt, sinkt die Anzahl der fehlgeschlagenen Anfragen innerhalb weniger Sekunden, und unser Skript kann wieder mit dem Senden beginnen.

Versuchen Sie es selbst

Ohne Produktionslast kann es recht schwierig sein, genügend Anfragen zu generieren, um die Ratenbegrenzung auszulösen. Sie können sich das Beispielskript aus diesem Tutorial sowie eine Anleitung zur Verwendung auf GitHub ansehen.

Bitte beachten Sie, dass das Versenden von Nachrichten Ihren Account belastet. Wenn Sie routinemäßig so viele Nachrichten versenden, dass Sie eine Tarifbeschränkung erhalten, könnten Sie gegen die Vonage-Nutzungsbedingungen verstoßen; außerdem werden die Netzbetreiber das hundertfache Versenden der gleichen Nachricht nicht als positiv ansehen! Ich empfehle daher, dass Sie, wenn Sie dies selbst ausprobieren möchten, es nicht mit der Messages API testen, sondern stattdessen simulieren Sie es. Es gibt mehrere Pakete für httpx, um diesen Prozess zu vereinfachen, darunter pytest-httpx und respx.

Was kommt als Nächstes?

Wir haben uns nur einige der Funktionen angesehen, die mit Python-Backoff verfügbar sind. In der Dokumentation finden Sie weitere Informationen über Bereitstellung verschiedener Backoff-Strategien für verschiedene Arten von Ausnahmenoder die verschiedenen Ereignisse, die Backoff ausgibt. Versuchen Sie, den Beispielcode so zu ändern, dass das Skript, wenn Backoff den on_giveup Handler ausführt, verwendet das Skript die Vonage Voice API verwendet, um den Bereitschaftsdiensttechniker anzurufen.

Haben Sie eine Frage oder möchten Sie etwas mitteilen? Beteiligen Sie sich am Gespräch auf dem Vonage Community Slackund bleiben Sie auf dem Laufenden mit dem Entwickler-Newsletter, folgen Sie uns auf X (früher Twitter), abonnieren Sie unseren YouTube-Kanal für Video-Tutorials, und folgen Sie der Vonage Entwickler-Seite auf LinkedInein Raum für Entwickler, um zu lernen und sich mit der Community zu vernetzen. Bleiben Sie in Verbindung, teilen Sie Ihre Fortschritte und halten Sie sich über die neuesten Nachrichten, Tipps und Veranstaltungen für Entwickler auf dem Laufenden!

Weitere Lektüre

Full Stack Python - Aufgabenwarteschlangen AWS Architektur-Blog - Exponentieller Backoff und Jitter

Teilen Sie:

https://a.storyblok.com/f/270183/150x150/a3d03a85fd/placeholder.svg
Aaron BassettVonage Ehemalige

Aaron war ein Entwickler-Befürworter bei Nexmo. Aaron ist ein erfahrener Software-Ingenieur und Möchtegern-Digitalkünstler, der häufig Dinge mit Code oder Elektronik entwickelt, manchmal auch beides. Wenn er an etwas Neuem arbeitet, erkennt man das in der Regel am Geruch von brennenden Bauteilen in der Luft.