Für alle Arten von Services verwenden wir die gleichen technischen Prinzipien, um Ausfallsicherheit und Verfügbarkeit zu erreichen, da die grundlegenden technischen Herausforderungen beim Aufbau fehlertoleranter, skalierbarer, verteilter Systeme für alle Arten von Services gleich sind.
Um Resilienz und kontinuierliche Verfügbarkeit zu erreichen, ist es notwendig, alle Ursachen der Nichtverfügbarkeit – verminderte Leistung und unbehandelte Ausfälle – in cloudbasierten Systemen zu verstehen und dann zu behandeln. Es gibt eine Vielzahl solcher Ursachen, also teilen wir sie entsprechend ihrer Art in Kategorien ein
Traditionell wurde zur Bestimmung der Verfügbarkeit von Unternehmens-IT-Systemen in erster Linie die Kategorie Hardwareausfall analysiert. Bei Cloud-Systemen ist ein Hardwareausfall jedoch ein relativ geringfügiges und gut verstandenes Problem. Es ist jetzt relativ einfach, die meisten Hardware-Ausfallstellen zu vermeiden oder zu entschärfen. Beispielsweise können Racks über zwei Stromeinspeisungen und zugehörige Stromverteilungseinheiten verfügen, und viele Komponenten sind Hot-Swap-fähig. Größere Hardwareausfälle und -verluste sind natürlich möglich – zum Beispiel aufgrund von Naturkatastrophen. Unsere Erfahrung und Berichte in öffentlichen Post-Mortems von anderen Cloud-Anbietern zeigen jedoch, dass der Ausfall oder Verlust eines gesamten Data Centers im Vergleich zu den anderen Ursachen der Nichtverfügbarkeit extrem selten vorkommt. Weitreichende Hardwareausfälle müssen weiterhin behandelt werden (z. B. mit Disaster Recovery und anderen Mechanismen), sind aber bei Weitem nicht das häufigste Verfügbarkeitsproblem.
Die Hauptgründe für die Nichtverfügbarkeit in Cloud-Scale-Systemen sind wie folgt:
- Softwarefehler
- Konfigurationsfehler
- Menschlicher Bedienerfehler
Anmerkung: Die wichtigste Lehre der Branche ist, dass diese drei Formen menschlicher Fehler bei Weitem die größten Ursachen für die Nichtverfügbarkeit sind. Sie können zwar durch Tools, Automatisierung und Training reduziert, aber nicht ganz beseitigt werden. Daher müssen sie bei Architektur, Design und Implementierung des Systems als Hauptanliegen betrachtet werden.
- Inakzeptable Leistungsabweichungen (Latenz oder Durchsatz) aus beliebigen Gründen, einschließlich der folgenden:
- „Noisy Neighbours“ mit mehreren Mandanten (Versagen von QoS-Mechanismen)
- Unfähigkeit, eine (zufällige oder böswillige) Überlastung wirksam abzuwehren und gleichzeitig nützliche Arbeit zu verrichten
- Verteilter Thrash, Message Storms, Retry Storms und andere teure „auftauchende“ Interaktionen
- Kälteschock (leere Caches) nach Power-Cycle, insbesondere gleichzeitiger Power-Cycle mehrerer Systeme
- Overhead bei Skalierung des Systems (z. B. erneutes Sharding)
- Versäumnis, den „Explosionsradius“ (Anzahl der betroffenen Kunden und Systeme) eines der vorstehenden Probleme zu begrenzen
Diese Herausforderungen sind universell – sie sind Teil der „Gesetze der Physik“ für verteilte Systeme im Cloud-Maßstab.
Wir nutzen für jede der oben genannten Kategorien bewährte Entwicklungsstrategien, um das Problem zu lösen. Die wichtigsten davon sind:
- Prinzipien von Architektur- und Systemdesign
- Neue Architekturkonzepte (die normalerweise aus der Anwendung der Prinzipien entstehen)
- Service-Engineering-Verfahren
Prinzipien von Architektur- und Systemdesign
Viele dieser Prinzipien existieren, aber wir konzentrieren uns auf die, die für Resilienz und Verfügbarkeit am relevantesten sind.
Wiederherstellungsorientiertes Computing
Um Softwarefehler und Fehler von Operatoren mit relativ lokalisierten Auswirkungen zu handhaben, folgen wir den Prinzipien des wiederherstellungsorientierten Computing1. Auf hohem Niveau bedeutet dies, dass wir uns darauf konzentrieren, alle Probleme unauffällig und auf eine Weise zu behandeln, anstatt zu garantieren, dass wir nie ein Problem haben (was unmöglich zu testen ist). Insbesondere konzentrieren wir uns auf die Minimierung der mittleren Zeit bis zur Wiederherstellung (MTTR), die eine Kombination aus mittlerer Zeit bis zur Erkennung, mittlerer Zeit bis zur Diagnose und mittlerer Zeit bis zur Minderung ist.
Unser Ziel ist es, das Problem so schnell wiederherzustellen, dass menschliche Benutzer nicht durch das Problem belästigt werden. Die folgenden Punkte helfen uns, dieses Ziel zu erreichen:
- Erkennen Sie schnell und automatisch die Symptome von Fehlern und Fehlern durch Bediener, durch umfangreiche Assertion-Verwendung im Code sowie aktive Überwachung und Alarmierung auf allen Ebenen.
- Packen Sie die Funktionalität in viele separate, feinkörnige Isolationseinheiten (Threads, Prozesse, Fibers, Zustandsmaschinen usw.), die lose gekoppelt sind – das heißt, sie teilen keinen direkten Arbeitsspeicher, der beschädigt werden könnte.
- Bei Feststellung der Symptome eines Fehlers oder eines Fehlers durch einen Bediener ist die umschließende Isoliereinheit so schnell wie möglich automatisch neu zu starten. Der Neustart ist ein praktischer Weg, um zu versuchen, sich von einem willkürlichen Fehler zu erholen, weil er versucht, einen Zustand wiederherzustellen, der gut getestet wurde, und so Invarianten wiederherstellt.
- Wenn die Wiederherstellung auf der feinkörnigen Isolationsebene nicht funktioniert (z. B. Assertionen werden auf dieser Ebene zu häufig ausgelöst, was zu einem Spin-Crash führt), eskalieren Sie zur nächstgrößeren Einheit (Prozess, Laufzeit, Host, logisches Data Center, Paging eines menschlichen Operators).
- Erstellen Sie Mechanismen, um ein „systemweites Rückgängigmachen“ zu ermöglichen, einschließlich der Versionierung aller persistenten Zustände und Konfigurationen, um fehlerhafte Commits schnell zu identifizieren und rückgängig zu machen.
Auswirkungen von Problemen minimieren
Wir entwickeln Mechanismen, um die Auswirkungen von Bugs und Fehlern zu minimieren, die weitreichende Auswirkungen haben könnten. Das heißt, wir konzentrieren uns darauf, die Anzahl der Kunden, Systeme oder Ressourcen zu minimieren, die von Problemen betroffen sind, einschließlich der besonders herausfordernden Probleme von mandantenfähigen „Noisy Neighbors“, angebotener Überlastung, eingeschränkter Kapazität und verteiltem Thrash. Dazu verwenden wir verschiedene Isolationsgrenzen und Änderungsmanagementverfahren (siehe folgende Abschnitte).
Architekturkonzepte, die aus Designprinzipien entstehen
Es gibt viele dieser Konzepte, aber wir werden uns auf Konzepte zur Begrenzung des Explosionsradius konzentrieren.
In unserer öffentlichen API verankerte Platzierungskonzepte: Regionen, Availability-Domain und Faultdomains
Da Faultdomains noch relativ neu sind, gehen wir näher auf diese ein.
Mit Faultdomains werden die Auswirkungen von Problemen bei einer aktiven Änderung des Systems begrenzt, wie z. B. bei Deployments, Patching, Hypervisor-Neustarts und der physischen Wartung.
Die Garantie besteht darin, dass in einer gegebenen Availability-Domain zu jedem Zeitpunkt Ressourcen in höchstens einer Faultdomain geändert werden. Wenn ein Problem beim Änderungsprozess auftritt, kann es vorkommen, dass einige oder alle Ressourcen in dieser Faultdomain eine Weile nicht verfügbar sind. Die anderen Faultdomains in der Availability-Domain sind jedoch nicht betroffen. Jede Availability-Domain enthält mindestens drei Faultdomains, damit quorumbasierte Replikationssysteme (z. B. Oracle Data Guard) mit High Availability in einer einzelnen Availability-Domain gehostet werden können.
Infolgedessen fungiert jede Faultdomain für eine vorherrschende Kategorie von Verfügbarkeitsproblemen – Softwarefehler, Konfigurationsfehler, Fehler von Bedienern und Leistungsprobleme, die während eines Änderungsverfahrens auftreten – als separates logisches Data Center innerhalb einer Availability-Domain.
Faultdomains schützen auch vor einigen Arten lokalisierter Hardwarefehler. Mit den Eigenschaften von Faultdomains wird sichergestellt, dass in unterschiedlichen Faultdomains platzierte Ressourcen so praktisch keine gemeinsamen Single Point of Hardwareausfälle innerhalb der Availability-Domain teilen. Zum Beispiel teilen sich Ressourcen in verschiedenen Faultdomains nicht denselben „Top-of-Rack“-Netzwerk-Switch, weil dem Standarddesign solcher Switches die Redundanz fehlt.
Die Fähigkeit von Faultdomains, sich gegen Probleme in der Hardware oder in der physischen Umgebung zu schützen, endet jedoch auf dieser lokalen Ebene. Im Gegensatz zu Availability-Domains und Regionen stellen Faultdomains keine umfangreiche physische Infrastrukturisolierung bereit. In den seltenen Fällen einer Naturkatastrophe oder eines Availability-Domain-weiten Infrastrukturausfalls sind Ressourcen in mehreren Faultdomains wahrscheinlich gleichzeitig betroffen.
Unsere internen Services nutzen Faultdomains genauso, wie sie auch von Kunden verwendet werden sollten. Beispiel: Block Volumes, Object Storage und File Storage speichern Replikate von Daten in drei separaten Faultdomains. Alle Komponenten aller Control Planes und Data Planes werden in allen drei Faultdomains (bzw. in einer Region mit mehreren Availability-Domains) gehostet.
Servicezellen
Mit Servicezellen werden die Auswirkungen von Problemen begrenzt, die auftreten, obwohl ein System nicht aktiv geändert wird. Derartige Probleme können entstehen, weil die Workload eines mehrmandantenfähigen Cloud-Systems sich jederzeit von Grund auf ändern kann und weil komplexe Teilfehler in jedem großen verteilten System zu jeder Zeit auftreten können. Diese Szenarios können verdeckte Bugs oder neue Performanceprobleme auslösen.
Darüber hinaus begrenzen Servicezellen in einigen seltenen, aber schwierigen Szenarien, in denen das System aktiv verändert wird, auch den Sprengradius. Ein klassisches Beispiel ist, wenn die Bereitstellung in einer einzelnen Faultdomain erfolgreich erscheint – keine Fehler oder Leistungsänderungen –, aber sobald die zweite oder letzte Faultdomain aktualisiert wurde, neue Interaktionen innerhalb des Systems (bei voller Cloud-Skalierung mit Produktions-Workload) ein Leistungsproblem verursachen.
Beachten Sie, dass die Nutzung von Servicezellen ein Architekturmuster ist und kein explizit in der Oracle Cloud-API oder dem SDK benanntes Konzept. Jedes beliebige Mehrmandantensystem kann dieses Architekturmuster verwenden. Es erfordert keinen besonderen Support von der Cloud-Plattform.
Servicezellen funktionieren wie folgt:
- Jede Instanz des Services (z. B. in einer bestimmten Region oder in einer bestimmten Availability-Domain für lokale Services der Availability-Domain) besteht aus mehreren separaten Deployments des Software-Stacks des Services. Jedes separate Deployment wird als Zelle bezeichnet. Jede Zelle verfügt über eine eigene Infrastruktur, soweit das sinnvoll ist. Zumindest teilen sich Zellen keine Hosts oder VMs.
- Ein Service kann mit ein paar Zellen in jeder Availability-Domain oder Region beginnen. Wenn der Service entsprechend dem zunehmenden Bedarf skaliert wird, werden weitere Zellen hinzugefügt, um die Auswirkungen eventueller Probleme weiterhin zu begrenzen. Bei einem großen, gängigen Service können viele Zellen verwendet werden. Mit anderen Worten, Zellen bieten n-zu-m-Multiplexing von Kunden-Workloads in separaten Hosting-Umgebungen – separate Inseln der Ressourcenisolation. Zellen verfügen nicht über eine offensichtliche Kardinalität, wie sie bei Fehlerdomänen vorhanden ist. (Wie bereits erwähnt werden als primäre Kardinalität drei Faultdomains pro Availability-Domain gewählt, damit quorumbasierte Replikationssysteme mit High Availability in einer Availability-Domain gehostet werden können.)
- Jede „natürliche Einheit“ eines Kunden-Workloads wird einer bestimmten Zelle zugeordnet. Die Definition der „natürlichen Einheit“ hängt von der Art des jeweiligen Services ab. Beispielsweise könnte die natürliche Einheit für unseren internen gemeinsam genutzten Workflow-Service (später beschrieben) „alle Workflows in dieser Availability-Domain oder Region für eine bestimmte Control Plane“ sein.
- Vor jeder Gruppe von Zellen befindet sich entweder eine minimalistische Routing-Schicht oder eine API zum Erkennen von Zellenendpunkten. Beispielsweise verfügt das Streaming-/Messaging-System über eine API, um den aktuellen Endpunkt der Data Plane für ein bestimmtes Thema zu ermitteln, und der interne Metadatenspeicher verfügt über einen separaten Endpunkt pro Zelle. Andere zellbasierte Dienste haben jedoch einen einzigen Endpunkt auf der Data Plane und eine gemeinsame Routing-Schicht. Der Routinglayer ist eine potenzielle Ursache für den korrelierten Ausfall mehrerer Zellen, aber dies wird abgemildert, indem der Routinglayer extrem einfach, vorhersehbar und leistungsfähig (keine teuren Operationen) gehalten und mit einer großen Menge an Headroom-Kapazität und einem ausgeklügelten QoS-Kontingent und Drosselmechanismen ausgestattet wird.
- Service-Owner können eine Workload nach Bedarf von einer Zelle in eine andere verschieben. Hier einige Beispielszenarios:
- Zur Vermeidung des Multi-Tenant-Problems „Noisy Neighbor“ durch Verschieben einer hohen Workload, sodass andere Benutzer einer Zelle nicht beeinträchtigt werden.
- Zur Wiederherstellung nach einer Überlastung oder einem Brownout, der möglicherweise durch einen Distributed-Denial-of-Service-Angriff verursacht wurde. Wir haben Quota- und Throttling-Mechanismen, um uns gegen solche Angriffe zu verteidigen, aber manchmal treten Grenzfälle auf, in denen ein bestimmter Anwendungsfall (API, Zugriffsmuster) für den Service anstrengender ist, als das Quota- bzw. Throttling-System derzeit versteht. Die Zellen bieten einen Mechanismus zur kurzfristigen Milderung.
- Um kritische Workloads in verschiedene Zellen aufzuteilen, um die Wahrscheinlichkeit korrelierter Ausfälle deutlich zu reduzieren. Beispielsweise werden für unseren internen gemeinsamen Workflow für Control Planes die „kritischen Kern“-Control Planes (z. B. Plattform, Compute, Netzwerk und Block-Volumes) jeweils unterschiedlichen Zellen zugewiesen. Somit besteht weitaus weniger Fehlerkorrelation als in nicht verwendeten Zellen oder aber in derselben Zelle.
Hinweis: Diese Verwendung von Zellen nimmt den Kunden die Notwendigkeit, die internen Abhängigkeiten von Diensten zu berücksichtigen, um belastbare Anwendungen zu erstellen. Das Abhängigkeitsdiagramm sollte weiterhin berücksichtigt werden (siehe weiter unten). Wenn bereits ein Dekorrelationsmechanismus aktiv ist, ist es aber weniger erforderlich.
Das Ergebnis ist, dass jede Servicezelle eine weitere Art von „logischem Data Center“ ist – eine logische Gruppierung von Performanceisolation und Fehlerisolation – innerhalb einer einzelnen Availability-Domain oder Region.
Zusammenfassend lässt sich sagen, dass Servicezellen und Faultdomains sich auf folgende Arten ergänzen:
- Faultdomains schützen vor Problemen, wenn ein System aktiv geändert wird.
- Servicezellen begrenzen den Explosionsradius, wenn ein System potenziell schwerwiegende Probleme hat – unabhängig davon, ob es aktiv geändert wird oder nicht.
Bei der Ausführung von Deployments und dem Patching werden die Eigenschaften von Faultdomains und Servicezellen in einer einheitlichen Strategie kombiniert.
Service-Engineering-Verfahren
Da sowohl das Testen als auch der Betrieb für die Zuverlässigkeit von Cloud-Systemen von entscheidender Bedeutung sind, verfügen wir über eine große Anzahl von technischen Verfahren. Einige der wichtigeren Verfahren unter Verwendung der weiter oben beschriebenen Konzepte:
- Services werden inkrementell bereitgestellt. Dabei erfolgt eine vorsichtige Validierung zwischen den einzelnen Schritten und ein reflexives Rollback bei unerwarteten Ereignissen. Konkret sieht der Prozess wie folgt aus:
- In jeder Availability-Domain wird jeweils eine Servicezelle bereitgestellt. Für jede Zelle wird eine Faultdomain nach der anderen eingerichtet, bis alle Faultdomains für diese Zelle abgeschlossen sind. Dann fahren wir mit der nächsten Zelle in dieser Availability-Domain fort.
- Nach jedem Schritt der Bereitstellung (nach jeder Faultdomain und Zelle) überprüfen wir, ob die Änderung wie beabsichtigt funktioniert – das heißt, wir haben die Leistung nicht beeinträchtigt oder Fehler eingeführt, weder intern noch extern. Wenn etwas falsch oder unerwartet erscheint, machen wir die Änderung reflexartig rückgängig. Wir legen großen Wert auf die Vorbereitung und das Testen, einschließlich automatisierter Tests, von Rollback-Verfahren, einschließlich Änderungen, die sich auf den persistenten Zustand oder Schemas auswirken.
- So stellen wir die Änderung nacheinander in den Availability-Domains jeder Region bereit. Wir stellen in allen Regionen in einem Bereich so bereit, dass wir nicht gleichzeitig ein Regionspaar ändern, das ein Kunde möglicherweise für primäre und Disaster Recovery-Standorte verwendet.
- Wir überprüfen regelmäßig, ob Fehlerbehandlungsmechanismen und andere Minderungsmaßnahmen wie erwartet funktionieren und das Problem im großen Maßstab nicht verschlimmern. Ohne derartige Tests kommt es oft vor, dass Fehlerbehandlungsmechanismen (wie Wiederholungen, Algorithmen für die Wiederherstellung nach Abstürzen und Algorithmen für die Neukonfiguration von State Machines) Bugs aufweisen, zu kostspielig sind oder unerwartete Folgen haben, sodass Distributed Thrash oder andere schwerwiegende Performanceprobleme entstehen.
- Wir stellen wie oben beschrieben sicher, dass wir schnell und sicher auf die letzte bekannte gute Software und Konfiguration zurücksetzen können, einschließlich persistentem Status und Schema.