Read in English · Leer en español · Lire en français
Abschnitt 6.1 von 6.8 · Mittelstufe · 24 Min. Lesezeit
Warum verteilen? Die Fallacies of Distributed Computing
Videolektion
5 Min. · auf Englisch gesprochen, mit UntertitelnNur anhören (Audio)
Transkript lesen
Ein System auf viele Rechner aufzuteilen bringt echten Nutzen und Probleme, die innerhalb eines Programms nicht auftreten können. In dieser Lektion erfahren Sie, wann es sich lohnt und wie Sie den Preis bestimmen, bevor Sie zahlen.
Stellen Sie sich ein kleines Restaurant vor, in dem ein Koch in einer Küche alles macht. Das ist einfach. Ist der Koch krank, bleibt das Restaurant geschlossen, aber zwischen Menschen geht nichts verloren. Stellen Sie sich nun eine Kette vor, mit einer zentralen Bäckerei, einer Saucenküche und vielen Filialen, alle verbunden durch Lieferwagen. Sie kann weit mehr Gäste bedienen, und eine Küche kann schließen, ohne dass die anderen schließen müssen. Aber Lieferwagen stehen im Stau, und Bestellungen gehen verloren. Kommt eine Lieferung nicht an, weiß niemand, ob die Bäckerei die Bestellung nie bekommen hat oder der Wagen liegen geblieben ist. Verteilte Software funktioniert wie die Kette.
Ein verteiltes System ist eine Menge von Programmen auf verschiedenen Rechnern, die durch Nachrichten über ein Netzwerk zusammenarbeiten, um etwas bereitzustellen, das Nutzer als einen Dienst wahrnehmen. Eine Essensliefer-App ist eines. Die Kernidee dieses Abschnitts: Verteilung ist eine Entscheidung mit einem Preis, kein Zeichen von Reife.
Es gibt eine Handvoll guter Gründe, ein System aufzuteilen. Der erste ist Skalierung: Der Feed der Kurierstandorte erhält Tausende Updates pro Sekunde, die Anmeldemasken für Restaurants ein paar pro Stunde. Der zweite ist Fehlerisolation. Ein Speicherleck in den Empfehlungen kann den Checkout nicht zum Absturz bringen, weil beide auf verschiedenen Rechnern laufen. Dann unabhängige Releases und Teamautonomie. Diese organisatorischen Gründe sind oft die stärksten. Weitere sind Geografie und Datenresidenz, besondere Technologieanforderungen und Sicherheitsisolation, etwa Kartendaten in einem kleinen, gehärteten Service. Doch für jeden gibt es innerhalb eines Monolithen einen günstigeren Teilersatz, etwa mehr Kopien hinter einem Load Balancer oder Caches. Prüfen Sie zuerst den Ersatz.
Das grundlegende Problem ist der Teilausfall: Einige Teile funktionieren, während andere ausgefallen, langsam oder nicht erreichbar sind. Hier bittet der Bestellservice den Zahlungsservice, vierundzwanzig Pfund fünfzig zu autorisieren, und wartet. Kommt vor dem Timeout keine Antwort, ging vielleicht die Anfrage verloren, und es wurde nichts abgebucht. Vielleicht ist der Zahlungsservice abgestürzt oder hängen geblieben, und niemand weiß, ob die Karte belastet wurde. Oder vielleicht wurde die Karte belastet, und nur die Antwort ging verloren. Für den Aufrufer sehen alle drei gleich aus. Deshalb muss jede Operation, die wiederholt werden kann, idempotent sein: Sie zweimal auszuführen hat dieselbe Wirkung wie einmal. Meist trägt jede Anfrage einen eindeutigen Schlüssel, den sich der Empfänger merkt.
Fachleute bei Sun Microsystems listeten acht falsche Annahmen auf, die Neulinge treffen. Die ersten beiden lauten, dass das Netzwerk zuverlässig und die Latenz null ist. Setzen Sie also für jeden entfernten Aufruf ein Timeout, und ziehen Sie wenige grobe Aufrufe vielen kleinen vor. Die Bandbreite ist nicht unendlich, und Transport ist nicht kostenlos. Senden Sie nur die Felder, die Consumer brauchen. Das Netzwerk ist nicht sicher, nicht einmal in Ihrem Rechenzentrum. Authentifizieren Sie jeden Aufruf zwischen Services. Die Topologie ändert sich, also verwenden Sie Service Discovery statt fest eingetragener Adressen. Und es gibt mehr als einen Administrator: Ein Partner kann eine Regel ändern und Ihre Aufrufe lahmlegen. Schließlich ist das Netzwerk nicht homogen, also verwenden Sie standardisierte, versionierte Formate. Jeder dieser Punkte verursacht bis heute echte Ausfälle.
Jeder Grund für eine Verteilung wird mit derselben Währung bezahlt. Erstens: Latenz. Ein Aufruf innerhalb eines Prozesses dauert Nanosekunden. Ein Roundtrip zwischen Rechnern in einem Rechenzentrum dauert einige hundert Mikrosekunden bis zu einer Millisekunde oder mehr. Zweitens: Observability. Die Geschichte einer Anfrage ist über viele Logs verstreut, daher brauchen Sie Korrelations-IDs und Distributed Tracing. Drittens: Konsistenz. Mit getrennten Datenbanken gibt es keine gemeinsame Transaktion, also stimmen die Daten eine Weile nicht überein und müssen konvergieren. Viertens, und am meisten unterschätzt: Betriebsaufwand. Jeder Service braucht eine Pipeline, Monitoring und jemanden in Bereitschaft. Diese Kosten wachsen mit der Zahl der deploybaren Einheiten.
Einen Teil davon können Sie auf dem Papier berechnen. Hier durchläuft eine Bestellung eine synchrone Kette aus fünf Services. Jeder wartet auf den nächsten, und jeder für sich ist zu neunundneunzig Komma neun Prozent der Zeit verfügbar. Aber Verfügbarkeiten multiplizieren sich. Zusammen ergeben sie nur etwa neunundneunzig Komma fünf Prozent: rund drei Komma sechs Stunden Ausfallzeit im Monat. Und Latenzen addieren sich. Mit zwanzig Millisekunden Arbeit in jedem Service plus etwa zwei pro Hop liegt der Median bei etwa hundertzehn Millisekunden. Diese Abhängigkeit davon, dass die Gegenseite im selben Moment läuft, heißt zeitliche Kopplung. Zählen Sie also synchrone Hops, nicht Services. Rufen Sie unabhängige Services parallel auf, cachen Sie stabile Daten, und lassen Sie Teile, die nicht sofort antworten müssen, aus einer Queue arbeiten.
Der vollständige Abschnitt enthält die kompletten Tabellen und eine Checkliste für die Zeit, bevor Sie verteilen. Als Nächstes, in Abschnitt sechs Punkt zwei, geht es um die servicebasierte Architektur und die klassische serviceorientierte Architektur. Bis gleich.
Was Sie wissen müssen
- Ein verteiltes System ist eine Menge von Programmen auf verschiedenen Rechnern, die durch das Senden von Nachrichten über ein Netzwerk zusammenarbeiten, um etwas bereitzustellen, das Nutzer als einen einzigen Dienst wahrnehmen.
- Berechtigte Gründe für eine Verteilung sind Skalierung, Fehlerisolation, unabhängige Deploybarkeit, Teamautonomie, Geografie und Datenresidenz, unterschiedliche Technologieanforderungen und Sicherheitsisolation; für jeden gibt es innerhalb eines Monolithen einen günstigeren Teilersatz, den man zuerst prüfen sollte.
- Der Teilausfall ist das grundlegende Problem: Nach einem Timeout kann der Aufrufer nicht erkennen, ob die Anfrage verloren ging, der Aufgerufene ausfiel oder nur die Antwort verloren ging; deshalb müssen wiederholte Operationen idempotent sein.
- Die acht Fallacies of Distributed Computing (Deutsch und Kollegen bei Sun, mit Goslings achtem Punkt) sind falsche Annahmen über Zuverlässigkeit, Latenz, Bandbreite, Sicherheit, Topologie, Administration, Transportkosten und Homogenität.
- Verteilung kostet Latenz, Observability, Konsistenz und Betriebsaufwand, und diese Kosten wachsen ungefähr mit der Zahl der deploybaren Einheiten.
- In einer synchronen Kette multiplizieren sich die Verfügbarkeiten und addieren sich die Latenzen: Fünf Services mit 99,9 % ergeben etwa 99,5 %, rund 3,6 Stunden Ausfallzeit im Monat.
- Synchrone Aufrufe binden Services zur Laufzeit aneinander (zeitliche Kopplung); daher zählt die Zahl synchroner Hops in wichtigen User Journeys mehr als die Zahl der Services.
Was ein System zu einem verteilten System macht
Ein verteiltes System ist eine Menge von Programmen auf verschiedenen Computern, die durch das Senden von Nachrichten über ein Netzwerk zusammenarbeiten, um etwas bereitzustellen, das Nutzer als einen einzigen Dienst wahrnehmen. Ihre Essensliefer-App ist eines: Das Smartphone, die Bestell- und Zahlungsservices des Unternehmens, das Tablet des Restaurants, das Smartphone des Kuriers und ein Kartenzahlungsabwickler wirken alle an einer einzigen Bestellung mit. Kapitel 5 hat Stile beschrieben, die als eine Einheit deployt werden (Monolithen); in diesem Kapitel geht es um Stile, deren Teile separat deployt werden und über das Netzwerk kommunizieren.
Leslie Lamport, der mehr als jeder andere dazu beigetragen hat, das Gebiet auf ein mathematisches Fundament zu stellen, fasste seine wesentliche Schwierigkeit in einer internen E-Mail vom Mai 1987 zusammen: Ein verteiltes System ist eines, in dem der Ausfall eines Computers, von dessen Existenz Sie nicht einmal wussten
, Ihren eigenen Computer unbrauchbar machen kann. Der Witz funktioniert, weil er wahr ist. Sobald ein System von Rechnern abhängt, die es weder kontrolliert noch sieht, tritt eine ganze Klasse von Ausfällen auf, die innerhalb eines Prozesses schlicht nicht vorkommen können.
Deshalb ist Verteilung eine Entscheidung mit einem Preis, kein Zeichen von Reife. Der Rest dieses Abschnitts legt die Gründe dar, die den Preis rechtfertigen, die falschen Annahmen, die dazu führen, dass man ihn unterschätzt, und einen Weg, einen Teil davon zu berechnen, bevor Sie zahlen.
Warum überhaupt verteilen
Es gibt eine Handvoll berechtigter Gründe, ein System auf Prozesse und Rechner aufzuteilen. Jeder ist ein Qualitätsmerkmal aus Kapitel 2, das eine einzelne Deployment-Einheit ab einer gewissen Größe nur schwer liefern kann. Für jeden gibt es außerdem einen günstigeren Teilersatz innerhalb eines Monolithen, und wer gut architektiert, prüft zuerst diesen Ersatz.
Gründe für eine Verteilung und was ein gut gebauter Monolith bereits leisten kann
| Grund | Was die Verteilung bietet | Was ein einzelnes Deployment bereits leisten kann |
|---|---|---|
| Skalierung | Teile mit sehr unterschiedlicher Last lassen sich getrennt skalieren: Der Feed der Kurierstandorte erhält Tausende Updates pro Sekunde, die Onboarding-Masken für Restaurants ein paar pro Stunde. | Viele identische Replikate hinter einem Load Balancer betreiben, Read Replicas und Caches hinzufügen. Das genügt für die meisten Systeme und ist weit günstiger. |
| Fehlerisolation | Ein Speicherleck in den Empfehlungen kann den Checkout nicht zum Absturz bringen, weil beide in verschiedenen Prozessen auf verschiedenen Rechnern laufen. | Getrennte Thread-Pools und Bulkheads innerhalb eines Prozesses bieten teilweise Isolation; ein Absturz reißt trotzdem alles mit. |
| Unabhängige Deploybarkeit | Ein Team kann seinen Teil zwanzigmal am Tag ausliefern, ohne ein Release mit irgendjemand anderem abzustimmen. | Ein modularer Monolith mit guter Automatisierung kann ebenfalls viele Male am Tag ausliefern, aber jedes Release liefert alles aus. |
| Teamautonomie | Teams verantworten einen Service von Ende zu Ende, bestimmen ihr Tempo und haben Bereitschaft dafür; das Gesetz von Conway arbeitet für Sie statt gegen Sie. | Moduleigentümerschaft funktioniert gut bis zu einigen wenigen Teams; darüber hinaus werden gemeinsamer Build und gemeinsames Release zur Warteschlange. |
| Geografie und Datenresidenz | Teile nahe bei den Nutzern betreiben, um die Latenz zu senken, oder personenbezogene Daten in einer Rechtsordnung halten, weil ein Gesetz oder ein Vertrag es verlangt. | Pro Region eine vollständige Kopie deployen (eine Zelle). Das ist selbst eine Form der Verteilung, aber eine einfache. |
| Unterschiedliche Technologieanforderungen | Ein Empfehlungsmodell braucht GPUs, die Suche eine Suchmaschine, eine Altsystem-Integration eine bestimmte Laufzeitumgebung. | Die spezialisierte Komponente aufrufen und alles andere beisammenhalten. |
| Sicherheitsisolation | Kartendaten, die von einem kleinen, separat gehärteten und auditierten Service verarbeitet werden, verkleinern den Umfang einer PCI-DSS-Prüfung. | Eigentlich nicht: Alles innerhalb des Prozesses kann im Speicher auf alles andere zugreifen. |
Aus der Tabelle folgen zwei Beobachtungen. Erstens sind die stärksten Gründe oft organisatorischer Natur: Unabhängige Deploybarkeit und Teamautonomie zählen am meisten, wenn viele Teams an einem Produkt arbeiten; deshalb taucht das Gesetz von Conway (Kapitel 3) in diesem Kapitel immer wieder auf. Zweitens ist Verteilung selten eine Frage von alles oder nichts. Viele erfolgreiche Systeme sind ein modularer Monolith plus zwei oder drei separate Services, die jeweils aus einem bestimmten, benannten Grund abgespalten wurden.
Teilausfall: das grundlegende Problem
Innerhalb eines Prozesses kehrt ein Funktionsaufruf entweder zurück, oder der ganze Prozess stirbt mit ihm. Über ein Netzwerk gibt es ein drittes Ergebnis, und es ändert alles: den Teilausfall, bei dem einige Teile des Systems funktionieren, während andere ausgefallen, langsam oder nicht erreichbar sind. Schlimmer noch: Der Aufrufer kann oft nicht erkennen, welcher Fall vorliegt.
Nehmen Sie den Bestellservice, der den Zahlungsservice bittet, £24,50 zu autorisieren. Kommt vor dem Timeout keine Antwort, weiß der Bestellservice nicht, ob die Anfrage unterwegs verloren ging, ob der Zahlungsservice vor oder nach der Belastung der Karte abstürzte oder hängen blieb, oder ob die Karte belastet wurde und nur die Antwort verloren ging. Aus Sicht des Aufrufers sehen alle drei Fälle gleich aus.
Drei Ausfälle, die gleich aussehen
Die praktischen Folgen prägen jedes verteilte Design. Eine Wiederholung könnte den Kunden doppelt belasten; deshalb müssen Operationen, die wiederholt werden können, gefahrlos wiederholbar sein. Diese Eigenschaft heißt Idempotenz und wird meist erreicht, indem jede Anfrage einen eindeutigen Schlüssel erhält, den sich der Empfänger merkt. Eine langsame Abhängigkeit ist oft schlimmer als eine tote, weil Aufrufer weiter warten und Threads und Verbindungen blockieren; wird ein Service langsam, gehen seinen Aufrufern die Ressourcen aus, und sie werden ihrerseits langsam: ein kaskadierender Ausfall. Und weil ein Netzwerk so zerfallen kann, dass zwei Gruppen von Rechnern einander nicht erreichen, während beide weiterlaufen, können Daten, die an beiden Orten gehalten werden, auseinanderlaufen: das Thema des CAP-Abschnitts später in diesem Kapitel.
Eine globale Uhr gibt es ebenfalls nicht. Die Uhr jedes Rechners driftet und wird separat korrigiert, sodass sich Zeitstempel zweier Services nicht mit völliger Sicherheit vergleichen lassen und „Was ist zuerst passiert?“ eine schwierige Frage sein kann. Lamports Artikel von 1978 über Zeit und Uhren in verteilten Systemen zeigte, wie man Ereignisse ordnet, ohne sich auf physische Uhren zu verlassen; für Architekturfachleute lautet die Lehre einfach, Korrektheit nicht auf den Vergleich von Uhrzeiten verschiedener Rechner zu bauen.
Die acht Fallacies of Distributed Computing
Die Fallacies of Distributed Computing sind acht Annahmen, die Neulinge bei verteilten Systemen treffen; alle sind falsch, und alle verursachen früher oder später Ärger. Die Liste stammt von Sun Microsystems: L Peter Deutsch stellte 1994 sieben davon zusammen, aufbauend auf vier, die Kollegen wie Bill Joy bereits notiert hatten, und James Gosling, der Schöpfer von Java, fügte um 1997 die achte hinzu. Drei Jahrzehnte später beschreibt jeder Punkt noch immer irgendwo einen echten Ausfall in dieser Woche.
Die acht Trugschlüsse, jeweils mit einer Folge und einer Antwort im Design
| Trugschluss | Was schiefgeht, wenn Sie daran glauben | Was Sie stattdessen tun sollten |
|---|---|---|
| 1. Das Netzwerk ist zuverlässig | Ein Zahlungsaufruf hängt; der aufrufende Thread wartet ewig; unter Last warten bald alle Threads, und der Bestellservice antwortet niemandem mehr. | Für jeden entfernten Aufruf ein Timeout setzen, nur idempotente Operationen wiederholen, Queues dort einsetzen, wo Arbeit warten kann (Resilienzmuster). |
| 2. Die Latenz ist null | Eine Warenkorbseite, die 40 prozessinterne Aufrufe machte, macht jetzt 40 Netzwerkaufrufe zu je 2 ms; 80 ms entstehen aus dem Nichts, und im Tail weit mehr. | Grobgranulare Schnittstellen entwerfen, Anfragen bündeln, unabhängige Services parallel aufrufen, stabile Daten cachen. |
| 3. Die Bandbreite ist unendlich | Jedes Event MenuUpdated enthält alle Fotos der Gerichte; ein geschäftiger Abend sättigt die Leitung und verzögert die Standort-Updates, die sie mitnutzen. | Kennungen und die Felder senden, die Consumer brauchen, paginieren, komprimieren, Massendaten neben dem Code halten, der sie verarbeitet. |
| 4. Das Netzwerk ist sicher | Services im Rechenzentrum akzeptieren jede Anfrage ohne Authentifizierung; ein kompromittierter Container kann die Erstattungs-API aufrufen. | Datenverkehr zwischen Services verschlüsseln und authentifizieren (zum Beispiel mit Mutual TLS) und jeden Aufruf autorisieren: Zero Trust. |
| 5. Die Topologie ändert sich nicht | Eine in der Konfiguration fest eingetragene IP-Adresse funktioniert nicht mehr, wenn der Container auf einen anderen Rechner verlegt wird. | Service Discovery und DNS-Namen verwenden und damit rechnen, dass Instanzen jede Minute kommen und gehen. |
| 6. Es gibt einen Administrator | Ein Cloud-Anbieter, das Firewall-Team eines Partners oder eine andere Abteilung ändert eine Regel, tauscht ein Zertifikat aus oder senkt ein Rate Limit, und Ihre Aufrufe schlagen fehl. | Wissen, wer jeden Hop kontrolliert, die Abhängigkeiten überwachen, die Ihnen nicht gehören, Verträge und Ankündigungsfristen für Änderungen vereinbaren. |
| 7. Transportkosten sind null | Serialisierung verbraucht CPU, und Daten, die Availability Zones, Regionen oder das öffentliche Internet überqueren, werden pro Gigabyte abgerechnet; die Netzwerkrechnung wächst schneller als der Datenverkehr. | Die Kosten von Datenflüssen messen, gesprächigen Datenverkehr lokal halten, bei hohem Volumen kompakte Formate wählen. |
| 8. Das Netzwerk ist homogen | Ein Service geht davon aus, dass jeder Client dieselbe Protokollversion, Zeichenkodierung und Uhr verwendet; ein Mobilfunknetz, ein altes Partnersystem oder ein Proxy widerlegt die Annahme. | Standardisierte, explizit versionierte Formate und Protokolle verwenden und die Interoperabilität mit den echten Clients testen. |
Was Verteilung kostet
Jeder Grund für eine Verteilung wird mit derselben Währung bezahlt. Latenz ist der erste Kostenpunkt. Ein Funktionsaufruf innerhalb eines Prozesses dauert Nanosekunden; ein Netzwerk-Roundtrip zwischen Rechnern im selben Rechenzentrum dauert typischerweise einige hundert Mikrosekunden bis zu einer Millisekunde oder mehr, noch vor jeder Serialisierung oder Arbeit. Zwischen Kontinenten setzt die Physik die Untergrenze: Licht bewegt sich in Glasfaser mit rund 200.000 km pro Sekunde, sodass ein Roundtrip von London nach New York (etwa 5600 km pro Richtung) nicht weniger als etwa 56 ms dauern kann. Die Rechnung zur Tail-Latenz aus Kapitel 2 gilt für jeden Hop.
Observability ist der zweite. In einem Monolithen zeigt ein Stacktrace die ganze Geschichte einer fehlgeschlagenen Anfrage. In einem verteilten System ist die Geschichte über die Logs vieler Services auf vielen Rechnern verstreut. Observability (Beobachtbarkeit), die Fähigkeit, aus den Ausgaben eines Systems zu verstehen, was es tut, muss von vornherein eingeplant werden: Korrelations-IDs, die bei jedem Aufruf weitergegeben werden, zentralisierte Logs, Metriken pro Service und Distributed Tracing (Kapitel 9).
Konsistenz ist der dritte. Eine einzelne Datenbanktransaktion kann eine Bestellung und ihre Zahlung atomar aktualisieren. Liegen Bestellung und Zahlung in verschiedenen Services mit verschiedenen Datenbanken, gibt es keine gemeinsame Transaktion; das System muss eine Zeitspanne hinnehmen, in der beide nicht übereinstimmen, und dafür sorgen, dass sie konvergieren. Spätere Abschnitte behandeln die Werkzeuge: Konsistenzmodelle sowie Sagas und das Outbox-Muster.
Betriebsaufwand ist der vierte und der am häufigsten unterschätzte. Jeder Service braucht eine Build-Pipeline, ein Deployment, Konfiguration, Secrets, Monitoring, Alarme, Kapazitätsplanung, Sicherheitspatches und jemanden in Bereitschaft. Auch das Testen ändert sich: Eine Änderung ist nur dann sicher, wenn jeder Consumer ihrer Schnittstelle weiterhin funktioniert, und das erfordert Contract Tests und abwärtskompatible Änderungen. Diese Kosten sind ungefähr proportional zur Zahl der deploybaren Einheiten; deshalb kommt es auf die Zahl der Services an.
Nachgerechnet: synchrone Ketten
Der günstigste Moment, um festzustellen, dass ein verteiltes Design zu fragil oder zu langsam ist, ist auf dem Papier. Das Werkzeug ist die Reihenregel aus Kapitel 2: Braucht eine Anfrage jede Komponente einer Kette, ist die Verfügbarkeit der Kette das Produkt der Verfügbarkeiten der Komponenten, und ihre Latenz ist ungefähr die Summe ihrer Latenzen.
Eine synchrone Kette aus fünf Services
Ausgearbeitetes Beispiel
Ausgearbeitetes Beispiel: das Verfügbarkeits- und Latenzbudget einer Aufrufkette
Eine Essenslieferplattform wickelt Bestellen über eine synchrone Kette ab: Das API-Gateway ruft den Bestellservice auf, der den Speisekartenservice aufruft, der die Preisgestaltung aufruft, die die Zahlung aufruft. Jeder Service erreicht für sich 99,9 % Verfügbarkeit (zeitbasiert, 30-Tage-Monat mit 43.200 Minuten), antwortet im Median nach 20 ms eigener Arbeit und hat ein p99 von 250 ms. Jeder Netzwerk-Hop fügt etwa 2 ms hinzu. Nehmen Sie großzügig an, dass Ausfälle unabhängig sind und dass jeder Ausfall die ganze Anfrage scheitern lässt.
| Ein Service | Verfügbarkeit 0,999 = 99,9 %; zulässige Ausfallzeit 0,001 × 43.200 = 43,2 Minuten im Monat. |
|---|---|
| Zwei in Reihe | 0,999 × 0,999 = 0,998 (99,8 %); etwa 86 Minuten im Monat. |
| Fünf in Reihe | 0,9995 ≈ 0,99501 (99,50 %); nicht verfügbar etwa 0,005 × 43.200 ≈ 216 Minuten oder 3,6 Stunden im Monat. Fünf für sich genommen respektable Services haben ein System mit zweieinhalb Neunen ergeben. |
| Zehn in Reihe | 0,99910 ≈ 0,99004 (99,0 %); etwa 430 Minuten, über 7 Stunden, im Monat. |
| Median-Latenz, fünf in Reihe | Latenzen addieren sich: ungefähr 5 × (20 ms + 2 ms) ≈ 110 ms, gegenüber 20 ms für dieselbe Arbeit in einem Prozess. (Streng genommen addieren sich Mediane nicht exakt; die Summe ist eine gute erste Schätzung.) |
| Tail-Latenz, fünf in Reihe | Die Wahrscheinlichkeit, dass eine Anfrage bei mindestens einem Hop in dessen langsamstes 1 % fällt, beträgt 1 − 0,995 ≈ 4,9 %; also wird etwa 1 von 20 Anfragen durch mindestens einen Hop mit 250 ms verlangsamt. Bei zehn Hops sind es 1 − 0,9910 ≈ 9,6 %. |
| Was nötig ist, um 99,9 % von Ende zu Ende zu halten | Jeder der fünf Services bräuchte etwa 99,98 % (0,99985 ≈ 0,9990): rund 8,6 Minuten Ausfallzeit pro Monat und Service, was in der Praxis automatische Wiederherstellung bedeutet. |
| Antworten im Design | Speisekarte und Preisgestaltung parallel aufrufen (die Latenz ist dann die des langsameren der beiden, nicht die Summe); Speisekartendaten im Bestellservice cachen; die Zahlungsbestätigung asynchron machen, sodass die Bestellung angenommen und kurz danach bezahlt wird; Hops entfernen, die Anfragen nur weiterreichen. |
Kapitel 2 hat die Regel eingeführt, dass sich Verfügbarkeiten in Reihe multiplizieren; bei der Verteilung beginnt diese Regel zu schmerzen, weil jeder Netzwerkaufruf der Reihe eine Komponente hinzufügt.
Die Unabhängigkeitsannahme beschönigt das Ergebnis. Services, die sich eine Datenbank, einen Cluster, ein Konfigurationssystem oder einen Release Train teilen, fallen gemeinsam aus, und ein langsamer Aufgerufener kann die Threads des Aufrufers erschöpfen und den Ausfall nach oben ausbreiten.
Die Lösung besteht selten darin, jeden Service heldenhaft zuverlässig zu machen. Sie besteht darin, synchrone Ketten zu verkürzen und Teile, die nicht sofort antworten müssen, aus einer Queue arbeiten zu lassen.
Das Architekturquantum, noch einmal betrachtet
Kapitel 5 hat das Architekturquantum eingeführt: ein unabhängig deploybarer Teil mit hoher funktionaler Kohäsion, einschließlich seiner Daten, für den ein einziger Satz von Architekturmerkmalen gilt (Architekturstile, Muster und Taktiken). In Software Architecture: The Hard Parts (2021) schärfen Ford, Richards, Sadalage und Dehghani die Idee mit zwei Arten von Kopplung. Statische Kopplung ist das, was vorhanden sein muss, damit ein Teil deployt werden und laufen kann: sein Code, Bibliotheken, Datenbank und Infrastruktur. Dynamische Kopplung beschreibt, wie Teile zur Laufzeit voneinander abhängen: ob sie sich synchron oder asynchron aufrufen und wie sie sich koordinieren und konsistent bleiben.
Das ausgearbeitete Beispiel zeigt, warum die dynamische Seite zählt. Für die Dauer eines synchronen Aufrufs kann der Aufrufer seine Arbeit nur abschließen, wenn der Aufgerufene läuft und schnell ist; die beiden sind betrieblich aneinandergebunden, und ihre Verfügbarkeit und Latenz verbinden sich wie oben berechnet. Fünf separat deployte Services in einer synchronen Kette sind fünf Deployment-Einheiten, doch für die Verfügbarkeit von Bestellen verhalten sie sich wie eine fragile Einheit. Diese Abhängigkeit davon, dass die Gegenseite im selben Moment verfügbar ist, heißt zeitliche Kopplung. Asynchrones Messaging beseitigt einen Großteil davon: Legt der Bestellservice eine Nachricht in eine dauerhafte Queue, kann der Zahlungsservice eine Minute lang ausfallen und danach aufholen, und der Kunde erhält trotzdem eine Bestellnummer. Der Preis ist, dass der Kunde nicht sofort eine endgültige Antwort erhält, was ebenso eine Produktentscheidung wie eine technische ist.
Das ist die tiefste Lehre dieses Abschnitts. Service-Grenzen zu ziehen ist die Hälfte des Designs; zu entscheiden, welche Interaktionen synchron sein müssen, und diese Menge klein zu halten, ist die andere Hälfte.
Checkliste: bevor Sie verteilen
Stellen Sie sich diese Fragen, bevor Sie ein System oder einen Teil davon über das Netzwerk aufteilen.
- Den Treiber benennenWelcher Grund aus der Tabelle oben trifft zu, auf welchen Teil, mit welchen Belegen? „Microservices sind modern“ ist kein Treiber.
- Zuerst die günstigere Lösung versuchenKönnten Replikate, Caching, eine Read Replica, ein Background Worker oder eine klarere Modulgrenze innerhalb des Monolithen den Bedarf decken?
- Den Aufrufgraphen zeichnenZählen Sie die synchronen Hops in jeder wichtigen User Journey und berechnen Sie das Verfügbarkeits- und Latenzbudget wie im ausgearbeiteten Beispiel.
- Festlegen, wem welche Daten gehörenJedes Datenstück braucht einen verantwortlichen Service; alles, was sich atomar zusammen mit ihm ändern muss, gehört wahrscheinlich in denselben Service.
- Für Teilausfälle entwerfenTimeouts, Wiederholungen mit Idempotenz, Fallbacks und was der Nutzer sieht, wenn eine Abhängigkeit ausgefallen ist, entschieden vor dem ersten Ausfall.
- Die Voraussetzungen prüfenAutomatisierte Bereitstellung und automatisiertes Deployment, Monitoring, zentralisierte Logs und Distributed Tracing müssen vorhanden oder geplant und finanziert sein.
- Teams an Grenzen ausrichtenJeder Service sollte ein verantwortliches Team haben, das ihn ändern, deployen und betreuen kann, ohne andere zu fragen.
Prüfen Sie Ihr Verständnis
Ein Service ruft einen Lagerbestandsservice ohne Timeout auf. Der Lagerbestandsservice wird sehr langsam, statt auszufallen. Warum kann das schlimmer sein, als wenn er abgestürzt wäre?
Ein abgestürzter Service schlägt schnell fehl, und Aufrufer können reagieren; ein langsamer lässt Aufrufer warten, sodass sich ihre Threads und Verbindungen füllen, bis auch sie nicht mehr antworten und sich der Ausfall als kaskadierender Ausfall ausbreitet.
Drei Services haben jeweils 99,95 % Verfügbarkeit und werden bei jeder Anfrage in Reihe aufgerufen. Wie hoch ist ungefähr die Verfügbarkeit des Anfragepfads?
0,99953 ≈ 0,9985, etwa 99,85 %, unter der Annahme unabhängiger Ausfälle.
Warum ist der Trugschluss „Transportkosten sind null“ auch dann wichtig, wenn Latenz und Bandbreite in Ordnung sind?
Das Bewegen von Daten kostet CPU für die Serialisierung und Geld für den Datenverkehr zwischen Zonen, Regionen oder Anbietern; ein gesprächiges oder datenlastiges Design kann daher teuer sein, selbst wenn es schnell genug ist.
Warum können sich fünf separat deployte Services in Bezug auf die Verfügbarkeit trotzdem wie eine Einheit verhalten?
Ruft eine Anfrage sie synchron in einer Kette auf, müssen alle fünf im selben Moment laufen; ihre Verfügbarkeiten verbinden sich, sie sind also trotz getrennter Deployments zeitlich gekoppelt.
Quellen und weiterführende Literatur
- Leslie Lamport: the 1987 email defining a distributed system (lamport.org)
- The Eight Fallacies of Distributed Computing (James Gosling's page)
- Software Engineering Radio episode 470: L Peter Deutsch on the fallacies of distributed computing (2021)
- Martin Fowler: Microservices and the First Law of Distributed Objects
- Google SRE book: Addressing Cascading Failures
Haben Sie eine Frage zu diesem Abschnitt? ArchitectBot, der KI-Lernassistent des Kurses, antwortet zuerst aus dem Kurs.
Fragen: „Unser Checkout ruft sechs Services nacheinander auf. Wie finde ich heraus, ob das zu fragil ist, und was sollten wir zuerst ändern?“