Absicherung gegenüber externen Diensten mit Hystrix

Seite 2: Entkopplung, Konfiguration, Kalibrierung, Überwachung

Inhaltsverzeichnis

Durch die Kapselung als HystrixCommand ist der Programmablauf unverändert, solange der Dienst erreichbar ist und schnell antwortet. Gleichzeitig wird die Standardkonfiguration zur Entkopplung aktiviert:

  1. Benötigt der Dienst für eine Antwort länger als eine Sekunde, bekommt der Aufrufer eine Timeout-Exception.
  2. Es sind maximal zehn parallele Aufrufe erlaubt.
  3. Schlagen in einem Zeitraum von zehn Sekunden und einer Aktivität von mindestens zwanzig Aufrufen mehr als 50 Prozent der Aufrufe fehl, diagnostiziert Hystrix einen dauerhaften Fehler.
  4. Wird ein dauerhafter Fehler erkannt, beantwortet das Tool alle Anfragen direkt mit einer Exception. Alle fünf Sekunden wird eine einzelne Anfrage weitergeleitet. Wenn sie erfolgreich ist, wertet Hystrix das als neuen Normalzustand, und alle Aufrufe werden wieder durchgelassen.

Netflix beschreibt diese Werte als die Standardkonfiguration innerhalb ihrer eigenen serviceorientierten Architektur. Sie sind deswegen für den spezifischen Einsatz anzupassen. Die Standardkonfiguration und schnittstellenspezifischen Werte lassen sich auf drei Arten setzen:

  • als Teil der Initialisierung im HystrixCommand über eine Fluent API innerhalb des Java-Codes,
  • als interne Konfiguration in einer Properties-Datei im Classpath und
  • als externe Konfiguration zur Laufzeit.

Die Konfiguration innerhalb des Java-Codes ist die unflexibelste und sollte nur grundsätzliche Entscheidungen treffen – zum Beispiel, ob Hystrix die Absicherung über Thread Pools (geringer Overhead, Timeout möglich) oder Semaphoren (kein Overhead, aber auch kein Timeout möglich) durchführen soll.

Deutlich übersichtlicher ist die Konfiguration über eine Properties-Datei. Diese legt Hystrix im Classpath ab. Dort lassen sich an zentraler Stelle sowohl Standard- als auch individuelle Werte für einzelne Schnittstellen setzen:

# config.properties
# Standardwert für Timeout auf 2 Sekunden setzen
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=2000
# Individuellen Wert für Timeout bei IBAN/BIC-Validierung
# auf 300 Millisekunden setzen
hystrix.command.IbanBic.execution.isolation.thread.timeoutInMilliseconds=300

Auf diese Weise sind alle Konfigurationseinstellungen an einer Stelle zusammengefasst. Liefert man diese Datei zum Beispiel in einer Webanwendung mit, ist sie zur Laufzeit nicht änderbar. Für die Konfiguration zur Laufzeit greift Hystrix auf Archaius zurück, eine ebenfalls von Netflix bereitgestellte Open-Source-Bibliothek. Über Umgebungsparameter lassen sich mehrere Dateien angeben, die zur Laufzeit zusammengeführt werden. Diese Angaben kann das Projekt beispielsweise beim Start des Application Server mitgeben:

java -Darchaius.configurationSource.additionalUrls=
file:///apps/myapp/application.properties,
http://my.configuration.host/application.properties ...

Die dort hinterlegten Werte werden alle zehn Sekunden abgerufen. Archaius bietet unter anderem auch eine Ablage der Konfigurationen in einer Datenbank mit Zugriff über JDBC und die Rekonfiguration über JMX.

Doch wie sind nun die richtigen Werte für den Timeout oder die maximale Anzahl gleichzeitiger Anfragen (umgesetzt als Anzahl der Threads im Hystrix Thread Pool)? Netflix empfiehlt hier, zunächst mit einer Sekunde Timeout und zehn Threads einzusteigen, es sei denn, der Dienst ist deutlich langsamer. Wem das zu offensiv ist, insbesondere wenn nur wenige oder keine Informationen zum Antwortzeitverhalten der Dienste vorliegen, der kann als Zwischenschritt zum Beispiel über perf4j Antwortzeiten und Anzahl der Zugriffe messen. Nach einem Ausrollen dieser Zwischenversion lassen sich echte Werte, die sich über einen Tag hinweg ergeben, auswerten.

Wichtig für die Auswertung sind dabei mittlere Antwortzeit (z. B. 40 ms), maximale Antwortzeit von 99 und 99,5 Prozent der Anfragen (z. B. 200 und 300 ms) und die Anzahl der Anfragen pro Zeiteinheit (z. B. 30 pro Sekunde). Die Anzahl der Threads ergibt sich als Produkt aus Anfragen pro Sekunde und maximaler Antwortzeit von 99 Prozent der Anfragen (30 * 0,2 = 6). Mit etwas zusätzlichem Puffer sind hier die standardmäßig konfigurierten zehn Threads richtig gewählt.

Der Timeout für Aufrufe sollte sich an der Antwortzeit orientieren, in der 99,5 Prozent der Anfragen beantwortet werden. Man sollte ihn daher von den standardmäßig verwendeten 1000 Millisekunden auf 300 verringern. Damit verhält sich das Kommando nun wie folgt:

  1. Dauert ein Aufruf länger als 300 Millisekunden, bekommt der Aufrufer nach 300 Millisekunden eine Exception. Der eigentliche Aufruf läuft in einem separaten Thread weiter, bis dort die Antwort eintrifft oder zum Beispiel ein Netzwerk-Timeout geschieht.
  2. Sind alle zehn Threads ausgelastet, erhält der Aufrufer sofort eine Exception; bei Bedarf lässt sich hier zusätzlich eine Warteschlange mit maximaler Länge definieren.

Dieses Modell mit den zwei Stellschrauben "Timeout" und "Thread-Anzahl" eignet sich für alle Schnittstellen unabhängig von der Zugriffsschicht und den Konfigurationsmöglichkeiten der eingesetzten Bibliothek. Im Gegensatz zu den Low-Level-Timeout-Parametern rund um Netzwerkzugriffe (DNS-Timeout, Connect- und Read-Timeouts) lassen sich hier garantierte Antwortzeiten konfigurieren. Gleiches gilt für Ressourcenbeschränkungen – sowohl für die Anzahl gleichzeitiger Zugriffe auf den Dienst als auch für Aufrufe in der Warteschlange –, die sich mit anderen Mitteln nur schwer oder gar nicht umsetzen lassen.

Um eine Langzeitüberwachung der Antwortzeiten zu ermöglichen, arbeitet Hystrix eng mit Servo, einem weiteren quelloffenen Netflix-Projekt, zusammen. Mit dem folgenden Code werden über 40 Attribute pro HystrixCommand und knapp 10 über die darunter liegenden Thread Pools per JMX bereitgestellt. So kann sie nahezu jedes Monitoring-Tool abgreifen.

HystrixPlugins.getInstance().registerMetricsPublisher(
HystrixServoMetricsPublisher.getInstance());

Weitere Integrationen, zum Beispiel in Yammer Metrics, Gangalia und Graphite, sind vorhanden; ebenso Erweiterungspunkte, um eigene Integrationen schreiben zu können. Mit den so gesammelten Informationen lassen sich die Parameter der HystrixCommands überwachen und justieren. Wenn Probleme in der Produktion signalisiert werden, die beispielsweise von längeren Antwortzeiten eines Dienstes verursacht werden, stehen alle notwendigen Daten zur Verfügung: Hat sich die Anzahl der Anfragen erhöht? Gibt es Tageszeiten, an denen die Antwortzeiten des Dienstes ansteigen? In der Regel ist hier weitere Ursachenforschung am Dienst, der Anbindung und auch an der eigenen Anwendung zu betreiben.

Eine Vergrößerung des Thread Pool oder eine Verlängerung der erlaubten Antwortzeit bedeuten immer eine größere Ressourcennutzung in der eigenen Anwendung und schlechtere Antwortzeiten für die eigenen Nutzer. Werden die Werte zu groß gewählt, setzen die Änderungen eventuell sogar die Schutzmechanismen von Hystrix außer Kraft. Daher darf hier nur eine Anpassung erfolgen, wenn sich das Nutzungsverhalten des Dienstes oder die Anzahl der Aufrufe durch Nutzer signifikant geändert hat.

Neben der Langzeitüberwachung ist die Darstellung des aktuellen Zustands wichtig, möglichst in Echtzeit und über den Cluster aggregiert. So lässt sich bei Problemen im produktiven System schnell reagieren. Hystrix kann den eigenen Status als JSON-Stream einmal pro Sekunde weitergeben. Turbine, ein weiteres Netflix-Projekt, aggregiert die Datenströme mehrerer Knoten. Hystrix Dashboard stellt den Datenstrom eines Knotens oder einen aggregierten Datenstrom im Webbrowser dar. Das Ergebnis kann sich sehen lassen: Die Daten sind auf HystrixCommand-Ebene einsehbar. Alle HystrixCommands sind auf einer Browser-Seite dargestellt (s. Abb.).

Darstellung einzelner Dienste im Hystrix Dashboard