Houston, wir haben ein Problem. Systemdesignfehler

1970 starteten amerikanische Ingenieure das Raumschiff Apollo 13 zum Mond. An Bord befinden sich drei Batterien mit Brennstoffzellen, es gibt keinen Grund zur Sorge, alles wird zuverlässig und wiederholt dupliziert. Aber niemand hätte sich vorstellen können, dass die Explosion einer Sauerstoffflasche zwei der drei Batterien deaktivieren würde. Tragödie! Die Astronauten kehrten nach Hause zurück, es wurde ein Spielfilm über das Ereignis mit Tom Hanks gedreht und der Satz des Astronauten Jack Swigert: „Houston, wir haben ein Problem!“, Ging in die Geschichte ein.



Die Geschichte von Apollo 13 ist ein weiterer Beweis für die bekannte Tatsache, dass Sie sich nicht auf alle möglichen Probleme vorbereiten können. Dies ist eine natürliche Eigenschaft der Welt: Eisen bricht regelmäßig, Code stürzt ab und Menschen machen Fehler. Es ist unmöglich, dies vollständig zu beseitigen.

Bei großen verteilten Systemen ist dieses Verhalten normal und eine Folge von Skaleneffekten und Statistiken. Aus diesem Grund ist Design for Failure (AWS) das grundlegende Entwurfsprinzip für AWS Cloud Services. Die Systeme sind zunächst so aufgebaut, dass der Vollzeitbetrieb so schnell wie möglich wiederhergestellt und Schäden durch bekannte und noch unbekannte Fehler minimiert werden. Bei HighLoad ++ zeigte Vasily Pantyukhin am Beispiel realer Probleme mit Kampfdiensten Entwurfsmuster für verteilte Systeme, die von AWS-Entwicklern verwendet werden.

Vasily Pantyukhin (Henne) Ist der Architekt von Amazon Web Services in Europa, dem Nahen Osten und Afrika. Er begann als Unix-Administrator, arbeitete 6 Jahre bei Sun Microsystem, unterrichtete technische Kurse und 11 Jahre unterrichtete er die weltweite Datenzentrierung bei EMC. In einem internationalen Team entwarf und realisierte er Projekte von Kapstadt bis Oslo. Jetzt hilft es großen und kleinen Unternehmen, in öffentlichen Clouds zu arbeiten.


1949 wurde ein Flugzeugunfall auf einem Luftwaffenstützpunkt in Kalifornien untersucht. Einer der Ingenieure, die dies taten, war Edward Murphy. Er beschrieb die Arbeit lokaler Techniker wie folgt: „Wenn es zwei Möglichkeiten gibt, etwas zu tun, und eine davon zu einer Katastrophe führt, wird jemand diese Methode wählen.“

Dank Arthur Bloch ging die Aussage später als eines von Murphys Gesetzen in die Geschichte ein. Auf Russisch - das Gesetz der Gemeinheit. Sein Kern ist, dass es nicht möglich sein wird, Zusammenbrüche und menschliche Fehler zu vermeiden, und irgendwie damit leben muss. Deshalb setzen wir beim Entwerfen sofort Fehler und Ausfälle einzelner Komponenten in unsere Systeme ein.

Fehlerdesign


Beim Design for Failure versuchen wir drei Eigenschaften zu verbessern:

  • Zugänglichkeit (dieselben "Neunen");
  • Zuverlässigkeit - die Eigenschaft des Systems, das erforderliche Serviceniveau bereitzustellen;
  • Fehlertoleranz - eine Eigenschaft des Systems, um das Auftreten von Problemen zu verhindern und diese schnell zu beheben.

Zuverlässigkeit hat die Eigenschaft „ bekannter Unbekannter“. Wir schützen uns vor bekannten Problemen, wissen aber nicht, wann sie auftreten werden.

Un bekannt Unbekannte“ werden hinzugefügt Toleranz zu bemängeln - diese Überraschung Probleme sind , dass wir nichts wissen. Viele dieser Probleme in der Cloud hängen mit Skaleneffekten zusammen: Das System wächst auf die Größe, wenn neue, erstaunliche und unerwartete Effekte auftreten.

Ein Ausfall ist normalerweise kein binäres Phänomen. Seine Haupteigenschaft ist der "Explosionsradius" oder der Grad der Verschlechterung des Dienstes, der Radius der Zerstörung. Unsere Aufgabe ist es, den "Explosionsradius" von Systemen zu reduzieren.

Wenn wir erkennen, dass Probleme nicht vermieden werden können, müssen wir uns proaktiv darauf vorbereiten. Dies bedeutet, dass wir Services so gestalten, dass wir im Falle von Problemen (dies wird sicherlich der Fall sein) Probleme kontrollieren und nicht umgekehrt.
Wenn wir auf ein Problem reagieren, kontrolliert es uns.

Datenebene und Steuerebene


Sicherlich haben Sie zu Hause eine Elektronik, die über eine Fernbedienung wie einen Fernseher gesteuert wird. Der Fernsehbildschirm ist Teil der Datenebene, die die Funktion direkt ausführt. Die Fernbedienung ist die Benutzeroberfläche - Steuerebene. Es wird zum Verwalten und Konfigurieren des Dienstes verwendet. In der Cloud versuchen wir, die Datenebene und die Steuerebene zum Testen und Entwickeln zu trennen.

Benutzer sehen die Komplexität der Steuerebene meistens nicht. Fehler in Design und Implementierung sind jedoch die häufigsten Ursachen für Massenversagen. Deshalb konzentriert sich mein Rat auf die Kontrollebene - manchmal explizit, manchmal nicht.

Die Geschichte eines Problems


Im Juli 2012 kam es in Nord-Virginia zu einem schweren Sturm. Das Rechenzentrum verfügt über Schutz, Dieselgeneratoren und mehr. In einem der Rechenzentren einer der Verfügbarkeitszonen (Availability Zone, AZ) der Region North Virginia ging jedoch Strom aus. Die Stromversorgung wurde schnell wiederhergestellt, aber die Wiederherstellung der Dienste zog sich stundenlang hin.



Ich werde Ihnen am Beispiel eines der Basisdienste - CLB (Classic Load Balancer) - die Gründe erläutern. Es funktioniert einfach: Wenn Sie in jeder Verfügbarkeitszone einen neuen Balancer starten, werden separate Instanzen erstellt, deren IP den DNS auflöst.



Wenn eine der Instanzen fehlschlägt, wird eine entsprechende Nachricht an eine spezielle Datenbank gesendet.



Als Reaktion darauf beginnen die Prozeduren: Löschen von Einträgen aus DNS, Starten einer neuen Instanz und Hinzufügen einer neuen IP zu DNS.

Hinweis: So hat das System in der Vergangenheit funktioniert, jetzt ist alles grundlegend anders.

Alles ist einfach - es gibt nichts zu brechen. Während eines Massenfehlers, wenn Tausende von Instanzen gleichzeitig zusammenbrechen, wird in der Datenbank ein riesiger Rückstand von Nachrichten zur Verarbeitung angezeigt .



Aber es wurde schlimmer. Die Steuerebene ist ein verteiltes System. Aufgrund eines Fehlers haben wir Duplikate erhalten und Tausende von Datensätzen in der Datenbank sind auf Hunderttausende angewachsen. Es wurde sehr schwierig, damit zu arbeiten.

Wenn in einer der Instanzen ein Fehler auftritt, wird der gesamte Datenverkehr fast sofort auf die überlebenden Maschinen umgeschaltet, und die Last verdoppelt sich (im Beispiel gibt es der Einfachheit halber nur zwei Zugriffszonen).



Es sind nicht genügend Ressourcen vorhanden. Eine Live-Instanz wird automatisch skaliert. Der Vorgang dauert relativ lange. Alles geschieht zu Spitzenzeiten und gleichzeitig mit einer großen Anzahl von Instanzen - die freien Ressourcen der Verfügbarkeitszone gehen zur Neige. Der "Kampf" um Ressourcen beginnt.

In Nord-Virginia konnte die Automatisierung den massiven Fehler nicht bewältigen, und die Ingenieure stellten die Dienste manuell (mithilfe von Skripten) wieder her. Solche Probleme sind selten. Während der Nachbesprechung stellten sich Fragen zu den Ursachen des Fehlers, sie entschieden, dass die Situation nicht mehr wiederholt und der gesamte Service geändert werden sollte.

Die acht Muster, die ich behandeln werde, sind Antworten auf einige der Fragen.

Hinweis. Dies ist unsere Erfahrung im Service-Design, keine universelle Weisheit für den weit verbreiteten Einsatz. Muster werden in bestimmten Situationen verwendet.

— . AWS . — , . . — . !


Um die Auswirkungen von Fehlern zu minimieren, gibt es viele Ansätze. Eine davon ist die Beantwortung der Frage: "Wie kann ich sicherstellen, dass Benutzer, die kein Problem kennen, während eines Fehlers und während der Wiederherstellung nichts darüber wissen?"

Unser riesiges Backlog erhält nicht nur Absturzmeldungen, sondern auch andere, zum Beispiel über Skalierung oder dass jemand einen neuen Balancer startet. Solche Nachrichten müssen voneinander isoliert und funktional gruppiert werden: Eine separate Gruppe von Wiederherstellungsnachrichten nach einem Fehler, die einen neuen Balancer separat starten.

Angenommen, zehn Benutzer haben ein Problem bemerkt - einer der Knoten ihrer Balancer ist gefallen. Services arbeiten irgendwie an den verbleibenden Ressourcen, aber das Problem ist zu spüren.



Wir haben zehn frustrierte Benutzer. Der elfte erscheint - er weiß nichts über das Problem, sondern will einfach einen neuen Balancer. Wenn er die Warteschlange zur Verarbeitung ablegen möchte, wird er höchstwahrscheinlich nicht warten. Während andere Verarbeitungsvorgänge abgeschlossen sind, endet die Anforderungszeit. Anstelle von zehn frustrierten Benutzern werden wir elf haben.

Um dies zu verhindern, priorisieren wir einige Anforderungen - wir stellen die Warteschlangen ganz oben, z. B. Anforderungen für neue Ressourcen. Bei einem Massenausfall wirkt sich eine relativ geringe Anzahl solcher Anforderungen nicht auf die Wiederherstellungszeit der Ressourcen anderer Kunden aus. Während des Wiederherstellungsprozesses werden wir jedoch die Anzahl der an dem Problem beteiligten Benutzer einschränken.

Vollzeitstelle


Die Antwort auf Problemberichte ist der Start von Wiederherstellungsverfahren, insbesondere die Arbeit mit DNS. Massenausfälle sind enorme Spitzenlasten auf der Steuerebene. Das zweite Muster hilft der Steuerebene, in einer solchen Situation stabiler und vorhersagbarer zu sein.



Wir verwenden einen Ansatz namens Konstante Arbeit - Dauerarbeit .



Beispielsweise kann DNS etwas intelligenter gestaltet werden: Es überprüft ständig die Instanzen des Balancers, ob sie aktiv sind oder nicht. Das Ergebnis ist jedes Mal eine Bitmap: Die Instanz antwortet - 1, tot - 0.

DNS überprüft die Instanzen alle paar Sekunden, unabhängig davon, ob das System nach einem Massenausfall wiederhergestellt wird oder normal funktioniert. Er macht den gleichen Job - keine Spitzen, alles ist vorhersehbar und stabil.

Ein weiteres vereinfachtes Beispiel: Wir möchten die Konfiguration einer großen Flotte ändern. In unserer Terminologie ist eine Flotte eine Gruppe virtueller Maschinen , die zusammen einige Arbeiten ausführen.



Wir platzieren die Konfigurationsänderungen im S3-Bucket und übertragen diese Konfiguration beispielsweise alle 10 Sekunden auf unsere Flotte virtueller Maschinen. Zwei wichtige Punkte.

  • Wir machen das regelmäßig und brechen nie die Regel. Wenn Sie ein Segment von 10 Sekunden wählen, drücken Sie unabhängig von der Situation nur auf diese Weise.
  • Wir geben immer die gesamte Konfiguration an , unabhängig davon, ob sie sich geändert hat oder nicht. Die Datenebene (virtuelle Maschinen) selbst entscheiden, was damit zu tun ist. Wir schieben das Delta nicht. Es kann bei massiven Störungen oder Veränderungen sehr groß werden. Dies kann möglicherweise zu Instabilität und Unvorhersehbarkeit führen.

Wenn wir eine dauerhafte Arbeit verrichten, zahlen wir mehr dafür. Beispielsweise fordern 100 virtuelle Maschinen jede Sekunde eine Konfiguration an. Es kostet ungefähr 1200 Dollar pro Jahr. Dieser Betrag ist wesentlich geringer als das Gehalt eines Programmierers, dem wir die Entwicklung einer Steuerebene mit einem klassischen Ansatz anvertrauen können - eine Reaktion auf einen Fehler und die Verteilung nur von Konfigurationsänderungen.

Wenn Sie die Konfiguration wie im Beispiel alle paar Sekunden ändern, ist dies langsam. In vielen Fällen dauert eine Konfigurationsänderung oder der Start von Diensten jedoch Minuten - einige Sekunden lösen nichts.

Sekunden sind wichtig für Dienste, bei denen sich die Konfiguration sofort ändern muss, z. B. beim Ändern der VPC-Einstellungen. Hier gilt "Dauerarbeit" nicht. Dies ist nur ein Muster, keine Regel. Wenn dies in Ihrem Fall nicht funktioniert, verwenden Sie es nicht.

Vorläufige Skalierung


In unserem Beispiel erhält die zweite überlebende Instanz beim Absturz der Balancer-Instanz die Verdoppelung der Last fast sofort und beginnt zu skalieren. Bei einem massiven Ausfall verbraucht es eine große Menge an Ressourcen. Das dritte Muster hilft, diesen Prozess zu steuern - im Voraus zu skalieren .

Bei zwei Verfügbarkeitszonen skalieren wir, wenn weniger als 50% entsorgt werden.



Wenn alles im Voraus erledigt ist, sind die überlebenden Instanzen des Balancers im Falle eines Fehlers bereit, doppelten Datenverkehr zu akzeptieren.

Bisher haben wir nur mit hoher Auslastung skaliert, z. B. 80% und jetzt mit 45%. Das System ist die meiste Zeit im Leerlauf und wird teurer. Aber wir sind bereit, dies zu ertragen und das Muster aktiv zu nutzen, denn dies ist eine Versicherung. Sie müssen für die Versicherung bezahlen, aber bei ernsthaften Problemen deckt der Gewinn alle Kosten. Wenn Sie sich für das Muster entscheiden, berechnen Sie alle Risiken und den Preis im Voraus.

Zelluläre Architektur


Es gibt zwei Möglichkeiten, Dienste zu erstellen und zu skalieren: den Monolithen und die Wabenstruktur (zellbasiert).



Der Monolith entwickelt sich und wächst als ein einziger großer Behälter. Wir fügen Ressourcen hinzu, das System schwillt an, wir stoßen an unterschiedliche Grenzen, lineare Eigenschaften werden nichtlinear und gesättigt und der „Explosionsradius“ des Systems - das gesamte System.

Wenn der Monolith schlecht getestet wird, erhöht sich die Wahrscheinlichkeit von Überraschungen - „unbekannten Unbekannten“. Ein großer Monolith kann jedoch nicht vollständig getestet werden. Manchmal müssen Sie dafür eine separate Zugriffszone erstellen, z. B. für einen beliebten Dienst, der als Monolith innerhalb der Zugriffszone erstellt wird (dies sind viele Rechenzentren). Dies schafft nicht nur eine enorme Testlast, die der Gegenwart ähnelt, sondern ist auch aus finanzieller Sicht unmöglich.

Daher verwenden wir in den meisten Fällen eine zellulare Architektur - eine Konfiguration, bei der ein System aus Zellen fester Größe aufgebaut wird. Durch Hinzufügen von Zellen skalieren wir es.

Zelluläre Architektur ist in der AWS-Cloud beliebt. Es hilft, Störungen zu isolieren und den Explosionsradius zu verringern.zu einer oder mehreren Zellen. Wir können mittelgroße Zellen vollständig testen, was die Risiken erheblich reduziert.

Ein ähnlicher Ansatz wird im Schiffbau angewendet: Der Rumpf eines Schiffes oder Schiffes ist durch Trennwände in Abteile unterteilt. Im Falle eines Lochs werden ein oder mehrere Abteile überflutet, aber das Schiff sinkt nicht. Ja, es hat der Titanic nicht geholfen, aber wir stoßen selten auf Eisbergprobleme.

Ich werde die Anwendung des Mesh-Ansatzes am Beispiel des Simple Shapes Service veranschaulichen. Dies ist kein AWS-Service, ich habe ihn mir selbst ausgedacht. Dies ist eine Reihe einfacher APIs zum Arbeiten mit einfachen geometrischen Formen. Sie können eine Instanz einer geometrischen Form erstellen, den Typ einer Form anhand ihrer ID anfordern oder alle Instanzen eines bestimmten Typs zählen. Beispielsweise put(triangle)wird bei einer Anforderung ein "Dreieck" -Objekt mit einer ID erstellt.getShape(id)Gibt den Typ "Dreieck", "Kreis" oder "Raute" zurück.



Um einen Dienst trübe zu machen, muss er von verschiedenen Benutzern gleichzeitig verwendet werden. Machen wir es mandantenfähig.



Als nächstes müssen Sie einen Weg finden, um die Figuren in Zellen zu unterteilen. Es gibt verschiedene Möglichkeiten, einen Partitionsschlüssel auszuwählen. Die einfachste ist die geometrische Form : alle Rauten in der ersten Zelle, Kreise in der zweiten, Dreiecke in der dritten.

Diese Methode hat Vor- und Nachteile.

  • Wenn es deutlich weniger Kreise als andere Figuren gibt, bleibt die entsprechende Zelle nicht ausgelastet (ungleichmäßige Verteilung).
  • Einige API-Anforderungen sind einfach zu implementieren. Wenn wir beispielsweise alle Objekte in der zweiten Zelle zählen, ermitteln wir die Anzahl der Kreise im System.
  • Andere Abfragen sind komplizierter. Um beispielsweise eine geometrische Form anhand der ID zu finden, müssen Sie alle Zellen durchgehen.

Die zweite Möglichkeit besteht darin, die ID von Objekten nach Bereichen zu verwenden : die ersten tausend Objekte in der ersten Zelle, die zweiten in der zweiten. Die Verteilung ist also gleichmäßiger, aber es gibt andere Schwierigkeiten. Um beispielsweise alle Dreiecke zu zählen, müssen Sie die folgende Methode verwenden scatter/gather: Wir verteilen die Anforderungen in jede Zelle, zählen die Dreiecke in sich selbst, sammeln dann die Antworten, fassen zusammen und erstellen das Ergebnis.

Der dritte Weg - Mieterdivision(zu Benutzern). Hier stehen wir vor einem klassischen Problem. In der Cloud gibt es normalerweise viele „kleine“ Benutzer, die etwas ausprobieren und den Dienst praktisch nicht laden. Es gibt Mastodon-Benutzer. Es gibt nur wenige, aber sie verbrauchen eine Menge Ressourcen. Solche Benutzer passen niemals in eine Zelle. Sie müssen sich knifflige Wege einfallen lassen, um sie auf viele Zellen zu verteilen.



Es gibt keinen idealen Weg, jeder Service ist individuell. Die gute Nachricht ist, dass hier weltliche Weisheit funktioniert - Brennholz zu hacken ist entlang der Fasern bequemer, als sie zu hacken. In vielen Diensten sind diese „Fasern“ mehr oder weniger offensichtlich. Dann können Sie experimentieren und den optimalen Partitionsschlüssel finden.

Zellen sind miteinander verbunden (wenn auch schwach). Daher muss eine Verbindungsebene vorhanden sein. Oft wird es als Routing- oder Mapping-Schicht bezeichnet. Es ist erforderlich zu verstehen, an welche Zellen bestimmte Anforderungen gesendet werden sollen. Diese Stufe sollte so einfach wie möglich sein. Versuchen Sie, keine Geschäftslogik hineinzulegen.



Es stellt sich die Frage nach der Größe der Zellen: klein - schlecht, groß - auch schlecht. Es gibt keinen universellen Rat - entscheiden Sie je nach Situation.

In der AWS-Cloud verwenden wir logische und physische Zellen unterschiedlicher Größe. Es gibt regionale Dienste mit einer großen Zellengröße, es gibt Zonendienste, bei denen die Zellen kleiner sind.



Hinweis. Ich habe Anfang April dieses Jahres bei Saint Highload ++ Online über Mikrozellen gesprochen. Dort habe ich ein Beispiel für die spezifische Verwendung dieses Musters in unserem Amazon EBS-Kerndienst ausführlich besprochen.

Mandantenfähig


Wenn ein Benutzer einen neuen Balancer startet, erhält er Instanzen in jeder Verfügbarkeitszone. Unabhängig davon, ob Ressourcen verwendet werden oder nicht, werden sie zugewiesen und gehören ausschließlich diesem Mandanten der Cloud.

Für AWS ist dieser Ansatz ineffizient, da die Auslastung der Serviceressourcen im Durchschnitt sehr gering ist. Dies wirkt sich auf die Kosten aus. Für Cloud-Benutzer ist dies keine flexible Lösung. Es kann sich nicht an sich schnell ändernde Bedingungen anpassen, um beispielsweise Ressourcen in kürzester Zeit mit einer unerwartet erhöhten Last zu versorgen.



CLB war der erste Balancer in der Amazon Cloud. Services verwenden heute einen mandantenfähigen Ansatz, z. B. NLB (Network Load Balancer). Die Basis, die "Engine" solcher Netzwerkdienste, ist HyperPlane. Dies ist eine interne, für Endbenutzer unsichtbare, riesige Flotte virtueller Maschinen (Knoten).



Vorteile des mandantenfähigen Ansatzes oder des fünften Musters.

  • Die Fehlertoleranz ist grundsätzlich höher . In HyperPlane wird bereits eine große Anzahl von Knoten ausgeführt und wartet auf das Laden. Knoten kennen den Status voneinander - wenn einige Ressourcen ausfallen, wird die Last sofort auf die verbleibenden verteilt. Benutzer bemerken nicht einmal Massenabstürze.
  • Spitzenlastschutz . Mieter leben ihr eigenes Leben und ihre Belastungen korrelieren normalerweise nicht miteinander. Die durchschnittliche Gesamtlast von HyperPlane ist recht gleichmäßig.
  • Die Nutzung solcher Dienste ist grundsätzlich besser . Daher sind sie für eine bessere Leistung billiger.

Das klingt cool! Der Multitenant-Ansatz weist jedoch Nachteile auf. In der Abbildung die HyperPlane-Flotte mit drei Mandanten (Rauten, Kreise und Dreiecke), die auf alle Knoten verteilt sind .



Dies wirft das klassische Problem mit lauten Nachbarn auf: Die zerstörerische Aktion des Mandanten, die extrem hohen oder schlechten Datenverkehr erzeugt, wirkt sich möglicherweise auf alle Benutzer aus.



"Explosionsradius" in einem solchen System sind alle Mieter. Die Wahrscheinlichkeit eines zerstörerischen „lauten Nachbarn“ in der realen AWS-Verfügbarkeitszone ist nicht hoch. Aber wir müssen immer auf das Schlimmste vorbereitet sein. Wir verteidigen uns mit einem Mesh-Ansatz - wir wählen Gruppen von Knoten als Zellen aus. In diesem Zusammenhang nennen wir sie Scherben. Zellen, Scherben, Trennwände - hier ist es ein und dasselbe.



In diesem Beispiel betrifft eine Raute als "lauter Nachbar" nur einen Mandanten - ein Dreieck. Aber das Dreieck wird sehr schmerzhaft sein. Um den Effekt auszugleichen, wenden Sie das sechste Muster an - das Mischen von Scherben.

Sharding mischen


Wir verteilen Mandanten zufällig auf Knoten. Zum Beispiel landet eine Raute auf 1, 3 und 6 Knoten und ein Dreieck auf 2, 6 und 8. Wir haben 8 Knoten und einen Splitter der Größe 3.



Hier funktioniert einfache Kombinatorik. Mit einer Wahrscheinlichkeit von 54% gibt es nur einen Schnittpunkt zwischen den Mietern.



"Lauter Nachbar" betrifft nur einen Mieter und nicht die gesamte Last, sondern nur 30 Prozent.

Stellen Sie sich eine Konfiguration nahe real - 100 Knoten mit Shard-Größe 5 vor. Mit einer Wahrscheinlichkeit von 77% gibt es überhaupt keine Schnittpunkte.



Shuffle Sharding kann den "Explosionsradius" erheblich reduzieren. Dieser Ansatz wird in vielen AWS-Diensten verwendet.

"Eine kleine Flotte verursacht eine große Flotte und nicht umgekehrt"


Bei der Wiederherstellung nach einem Massenfehler aktualisieren wir die Konfiguration vieler Komponenten. Eine typische Frage in diesem Fall ist, eine geänderte Konfiguration zu pushen oder aufzuschreiben. Wer ist der Initiator der Änderungen: die Quelle mit den Konfigurationsänderungen oder deren Verbraucher? Aber diese Fragen sind falsch. Die richtige Frage ist, welche Flotte größer ist?

Stellen Sie sich eine einfache Situation vor: eine große Flotte virtueller Front-End-Maschinen und eine bestimmte Anzahl von Back-Ends.



Wir verwenden einen Mesh-Ansatz - Gruppen von Front-End-Instanzen funktionieren mit bestimmten Backends. Bestimmen Sie dazu das Routing - Mapping von Backends und Frontends, die mit ihnen arbeiten.



Statisches Routing ist nicht geeignet. Hash-Algorithmen funktionieren bei Massenfehlern nicht gut, wenn Sie die meisten Routen schnell ändern müssen. Daher ist es besser zu verwendendynamisches Routing . Neben den großen Flotten von Front-End- und Back-End-Instanzen stellen wir einen kleinen Dienst bereit, der sich nur mit Routing befasst. Er wird jederzeit ein Backend- und Frontend-Mapping kennen und zuweisen.



Angenommen, wir hatten einen großen Absturz, viele Front-End-Instanzen sind gefallen. Sie beginnen sich massiv zu erholen und fordern fast gleichzeitig eine Mapping-Konfiguration vom Routing-Service an.



Ein kleiner Routing-Service wird mit einer Vielzahl von Anfragen bombardiert. Er wird mit der Last nicht fertig werden, im besten Fall wird sie sich verschlechtern und im schlimmsten Fall wird er sterben.

Daher ist es richtig, keine Konfigurationsänderungen von einem kleinen Dienst anzufordern, sondern Ihr System so aufzubauen, dass das „Baby“ selbstinitiierte Konfigurationsänderungen für eine große Flotte von Instanzen .



Wir verwenden das Muster der ständigen Arbeit. Ein kleiner Routing-Dienst sendet alle paar Sekunden eine Konfiguration an alle Front-End-Flotteninstanzen. Er wird niemals in der Lage sein, einen großartigen Service zu überlasten. Das siebte Muster trägt zur Verbesserung der Stabilität und Elastizität bei .

Die ersten sieben Muster verbessern das System. Das letztere Muster funktioniert anders.

Last fallen lassen


Unten sehen Sie ein klassisches Diagramm der Verzögerung gegenüber der Last. Auf der rechten Seite des Diagramms befindet sich das „Knie“, wenn bei extrem hohen Belastungen bereits eine geringe Erhöhung zu einer signifikanten Erhöhung der Latenz führt.


Im normalen Modus nehmen wir unsere Dienste niemals auf die rechte Seite des Zeitplans. Eine einfache Möglichkeit, dies zu steuern, besteht darin, Ressourcen rechtzeitig hinzuzufügen. Aber wir bereiten uns auf Probleme vor. Zum Beispiel können wir uns auf die rechte Seite des Diagramms bewegen, um uns von einem Massenversagen zu erholen.

Wir setzen das Client-Timeout auf das Diagramm. Jeder kann ein Kunde sein, zum Beispiel eine andere Komponente in unserem Service. Der Einfachheit halber zeichnen wir einen Verzögerungsgraphen von 50 Perzentilen.



Hier stehen wir vor einer Situation namens Brownout . Möglicherweise kennen Sie den Begriff Stromausfall, wenn in der Stadt der Strom abgeschaltet wird. Brownout ist, wenn etwas funktioniert, aber so schlecht und langsam ist, dass es überhaupt nicht funktioniert.

Schauen wir uns den Brownout in der braunen Zone an. Der Dienst hat eine Anfrage vom Client erhalten, diese verarbeitet und das Ergebnis zurückgegeben. In der Hälfte der Fälle haben die Kunden jedoch bereits eine Zeitüberschreitung und niemand wartet auf das Ergebnis. In der anderen Hälfte kehrt das Ergebnis schneller als das Timeout zurück, in einem langsamen System dauert es jedoch zu lange.

Wir hatten ein doppeltes Problem: Wir sind bereits überlastet und stehen auf der rechten Seite des Zeitplans, aber gleichzeitig „erwärmen“ wir immer noch die Luft und erledigen eine Menge nutzloser Arbeit. Wie vermeide ich das?

Finden Sie das "Knie" - den Wendepunkt auf der Karte . Wir messen oder schätzen theoretisch.

Lassen Sie den Verkehr fallen , der uns zwingt, rechts vom Wendepunkt zu fahren .



Wir sollten einfach einen Teil der Anfragen ignorieren. Wir versuchen nicht einmal, sie zu verarbeiten, sondern senden sofort einen Fehler an den Kunden zurück. Selbst bei Überlastung können wir uns diese Operation leisten - sie ist „billig“. Anfragen werden nicht erfüllt, die Gesamtverfügbarkeit des Dienstes wird reduziert. Obwohl die abgelehnten Anfragen früher oder später nach einem oder mehreren erneuten Versuchen der Clients verarbeitet werden.



Gleichzeitig wird ein anderer Teil der Anforderungen mit einer garantierten geringen Latenz verarbeitet. Infolgedessen leisten wir keine nutzlose Arbeit, und was wir tun, tun wir gut.

Kurzes Zusammendrücken von Mustern beim Entwerfen von Systemen für Fehler


Isolierung und Regulierung . Manchmal ist es sinnvoll, bestimmte Arten von Abfragen zu priorisieren. Beispielsweise können bei einer relativ geringen Anzahl von Anforderungen zum Erstellen neuer Ressourcen diese ganz oben in die Warteschlange gestellt werden. Es ist wichtig, dass dies andere Benutzer nicht verletzt. Bei einem massiven Ausfall werden Benutzer, die auf die Wiederherstellung ihrer Ressourcen warten, keinen signifikanten Unterschied spüren.

Vollzeitjob . Reduzieren oder eliminieren Sie das Umschalten der Servicemodi. Ein Modus, der unabhängig von Notfällen oder Arbeitssituationen stabil und konstant funktioniert, verbessert die Stabilität und Vorhersagbarkeit der Steuerebene grundlegend.

Vorläufige Skalierung. Mit niedrigeren Entsorgungswerten im Voraus skalieren. Sie müssen etwas mehr dafür bezahlen, aber dies ist eine Versicherung, die sich bei schwerwiegenden Systemausfällen auszahlt.

Zelluläre Architektur . Viele lose gekoppelte Zellen sind einem Monolithen vorzuziehen. Der Mesh-Ansatz reduziert den „Explosionsradius“ und die Wahrscheinlichkeit von Überraschungsfehlern.

Der Multitenant-Ansatz verbessert die Auslastung des Dienstes erheblich, reduziert seine Kosten und verringert den "Explosionsradius".

Sharding mischen . Dies ist ein Ansatz, der für mandantenfähige Dienste gilt. Außerdem können Sie den "Explosionsradius" steuern.

"Eine kleine Flotte verursacht eine große Flotte und nicht umgekehrt". Wir versuchen, Services so zu erstellen, dass kleine Services Änderungen an großen Konfigurationen initiieren. Wir verwenden es oft in Verbindung mit einem konstanten Lastmuster.

Die Last fallen lassen . In Notsituationen versuchen wir, nur nützliche Arbeit zu leisten und sie gut zu machen. Dazu verwerfen wir einen Teil der Last, den wir immer noch nicht verarbeiten können.

— , Saint HighLoad++ Online. -- , Q&A-, , . - . , - .

telegram- @HighLoadChannel — , .

All Articles