Kapitel 1. Einführung

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

In der Vergangenheit war jede Anwendung ein einzelnes Programm, das auf einem einzigen Computer mit einer einzigen CPU lief. Heute haben sich die Dinge geändert. In der Welt von Big Data und Cloud Computing bestehen die Anwendungen aus vielen unabhängigen Programmen, die auf einer sich ständig verändernden Anzahl von Computern laufen.

Die Koordination der Aktionen dieser unabhängigen Programme ist viel schwieriger, als ein einzelnes Programm zu schreiben, das auf einem einzelnen Computer läuft. Es kann leicht passieren, dass Entwickler sich in der Koordinationslogik verlieren und keine Zeit haben, ihre Anwendungslogik richtig zu schreiben - oder umgekehrt, dass sie wenig Zeit für die Koordinationslogik aufwenden und einfach einen schnellen und einfachen Hauptkoordinator schreiben, der anfällig ist und zu einem unzuverlässigen Single Point of Failure wird.

ZooKeeper wurde als robuster Dienst konzipiert, der es Anwendungsentwicklern ermöglicht, sich hauptsächlich auf ihre Anwendungslogik und nicht auf die Koordination zu konzentrieren. ZooKeeper stellt eine einfache API zur Verfügung, die sich an der Dateisystem-API orientiert und es Entwicklern ermöglicht, gängige Koordinationsaufgaben wie die Wahl eines Master-Servers, die Verwaltung von Gruppenmitgliedschaften und die Verwaltung von Metadaten zu implementieren. ZooKeeper ist eine Anwendungsbibliothek mit zwei Hauptimplementierungen der APIs - Java und C - und einer in Java implementierten Dienstkomponente, die auf einem Ensemble von dedizierten Servern läuft. Mit einem Ensemble von Servern ist ZooKeeper in der Lage, Fehler zu tolerieren und den Durchsatz zu erhöhen.

Wenn du eine Anwendung mit ZooKeeper entwickelst, trennst du idealerweise Anwendungsdaten von Kontroll- oder Koordinationsdaten. Die Nutzer eines E-Mail-Dienstes interessieren sich zum Beispiel für den Inhalt ihrer Postfächer, aber nicht dafür, welcher Server die Anfragen eines bestimmten Postfachs bearbeitet. Der Inhalt des Postfachs gehört zu den Anwendungsdaten, während die Zuordnung des Postfachs zu einem bestimmten Mailserver Teil der Koordinationsdaten (oder Metadaten) ist. Letztere werden von einem ZooKeeper-Ensemble verwaltet.

Die ZooKeeper-Mission

Der Versuch zu erklären, was ZooKeeper für uns tut, ist wie der Versuch zu erklären, was ein Schraubenzieher für uns tun kann. Ganz einfach gesagt, können wir mit einem Schraubenzieher Schrauben drehen oder antreiben, aber das drückt nicht wirklich die Kraft dieses Werkzeugs aus. Mit einem Schraubenzieher können wir Möbelstücke und elektronische Geräte zusammenbauen und in manchen Fällen sogar Bilder an die Wand hängen. Mit einigen Beispielen geben wir einen Einblick, was er für uns tun kann, aber das ist sicher nicht erschöpfend.

Das Argument, was ein System wie ZooKeeper für uns tun kann, geht in die gleiche Richtung: Es ermöglicht Koordinationsaufgaben für verteilte Systeme. Eine Koordinierungsaufgabe ist eine Aufgabe, an der mehrere Prozesse beteiligt sind. Eine solche Aufgabe kann dem Zweck der Zusammenarbeit oder der Regulierung von Konflikten dienen. Zusammenarbeit bedeutet, dass Prozesse etwas gemeinsam tun müssen und Prozesse Maßnahmen ergreifen, um anderen Prozessen das Vorankommen zu ermöglichen. In typischen Master-Worker-Architekturen teilt der Worker dem Master beispielsweise mit, dass er für die Arbeit zur Verfügung steht. Daraufhin weist der Master dem Worker Aufgaben zu. Anders verhält es sich bei Contention: Hier geht es um Situationen, in denen zwei Prozesse nicht gleichzeitig vorankommen können, so dass einer auf den anderen warten muss. Wenn wir das gleiche Master-Worker-Beispiel verwenden, wollen wir eigentlich einen einzigen Master haben, aber mehrere Prozesse können versuchen, der Master zu werden. Die verschiedenen Prozesse müssen sich also gegenseitig ausschließen. Wir können uns den Erwerb der Masterschaft wie den Erwerb einer Sperre vorstellen: Der Prozess, der die Sperre der Masterschaft erwirbt, übernimmt die Rolle des Masters.

Wenn du Erfahrung mit Multithreading-Programmen hast, wirst du feststellen, dass es eine Menge ähnlicher Probleme gibt. Tatsächlich unterscheiden sich mehrere Prozesse, die auf demselben Computer oder auf verschiedenen Computern laufen, konzeptionell überhaupt nicht. Synchronisationsprimitive, die im Zusammenhang mit mehreren Threads nützlich sind, sind auch im Zusammenhang mit verteilten Systemen nützlich. Ein wichtiger Unterschied ergibt sich jedoch aus der Tatsache, dass verschiedene Computer in einer typischen Shared-Nothing-Architektur nichts anderes als das Netzwerk gemeinsam nutzen. Es gibt zwar eine Reihe von Message-Passing-Algorithmen zur Implementierung von Synchronisationsprimitiven, aber in der Regel ist es viel einfacher, sich auf eine Komponente zu verlassen, die einen gemeinsamen Speicher mit einigen speziellen Ordnungseigenschaften bereitstellt, wie ZooKeeper es tut.

Die Koordinierung erfolgt nicht immer in Form von Synchronisationsprimitiven wie Leaderwahl oder Sperren. Konfigurations-Metadaten werden oft dazu verwendet, einem Prozess mitzuteilen, was andere tun sollen. In einem Master-Worker-System müssen die Arbeiter zum Beispiel wissen, welche Aufgaben ihnen zugewiesen wurden, und diese Informationen müssen auch dann verfügbar sein, wenn der Master ausfällt.

Schauen wir uns einige Beispiele an, bei denen ZooKeeper nützlich war, um ein besseres Gefühl dafür zu bekommen, wo er eingesetzt werden kann:

Apache HBase

HBase ist ein Datenspeicher, der normalerweise zusammen mit Hadoop verwendet wird. In HBase wird ZooKeeper verwendet, um einen Cluster-Master zu wählen, die verfügbaren Server im Auge zu behalten und die Cluster-Metadaten zu speichern.

Apache Kafka

Kafka ist ein Pub-Sub-Nachrichtensystem. Es nutzt ZooKeeper, um Abstürze zu erkennen, Topic Discovery zu implementieren und den Produktions- und Verbrauchsstatus von Topics zu erhalten.

Apache Solr

Solr ist eine Suchplattform für Unternehmen. In ihrer verteilten Form, genannt SolrCloud, verwendet sie ZooKeeper, um Metadaten über den Cluster zu speichern und die Aktualisierungen dieser Metadaten zu koordinieren.

Yahoo! Abrufdienst

Als Teil einer Crawler-Implementierung holt der Fetching Service Webseiten effizient ab, indem er Inhalte zwischenspeichert und gleichzeitig sicherstellt, dass die Richtlinien des Webservers, z. B. in robots.txt-Dateien, eingehalten werden. Dieser Dienst nutzt ZooKeeper für Aufgaben wie die Masterwahl, die Erkennung von Abstürzen und die Speicherung von Metadaten.

Facebook-Nachrichten

Dies ist eine Facebook-Anwendung, die Kommunikationskanäle integriert: E-Mail, SMS, Facebook Chat und die bestehende Facebook Inbox. Sie nutzt ZooKeeper als Controller für die Implementierung von Sharding und Failover sowie für die Dienstsuche.

Es gibt noch viel mehr Beispiele; dies ist nur ein Beispiel. Anhand dieses Beispiels wollen wir die Diskussion nun auf eine abstraktere Ebene bringen. Bei der Programmierung mit ZooKeeper entwerfen Entwickler ihre Anwendungen als eine Reihe von Clients, die sich mit ZooKeeper-Servern verbinden und über die ZooKeeper-Client-API Operationen auf ihnen aufrufen. Zu den Stärken der ZooKeeper-API gehört, dass sie

  • Starke Garantien für Konsistenz, Ordnung und Haltbarkeit

  • Die Fähigkeit, typische Synchronisationsprimitive zu implementieren

  • Eine einfachere Art, mit vielen Aspekten der Gleichzeitigkeit umzugehen, die in realen verteilten Systemen oft zu falschem Verhalten führen

ZooKeeper ist jedoch keine Zauberei; er wird nicht alle Probleme sofort lösen. Es ist wichtig zu verstehen, was ZooKeeper bietet, und sich seiner kniffligen Aspekte bewusst zu sein. Eines der Ziele dieses Buches ist es, Wege aufzuzeigen, wie man mit diesen Problemen umgehen kann. Wir behandeln das grundlegende Material, das der Leser braucht, um zu verstehen, was ZooKeeper für Entwickler tatsächlich leistet. Außerdem gehen wir auf verschiedene Probleme ein, auf die wir bei der Implementierung von Anwendungen mit ZooKeeper gestoßen sind, und helfen Entwicklern, die ZooKeeper noch nicht kennen.

Wie die Welt ohne ZooKeeper überlebte

Hat ZooKeeper die Entwicklung einer ganz neuen Klasse von Anwendungen ermöglicht? Das scheint nicht der Fall zu sein. ZooKeeper vereinfacht vielmehr den Entwicklungsprozess, macht ihn agiler und ermöglicht robustere Implementierungen.

Frühere Systeme haben Komponenten wie verteilte Sperrmanager implementiert oder verteilte Datenbanken für die Koordination verwendet. ZooKeeper leiht sich eine Reihe von Konzepten von diesen früheren Systemen. Er verfügt jedoch weder über eine Schnittstelle für Sperren noch über eine allgemeine Schnittstelle für die Speicherung von Daten. Das Design von ZooKeeper ist spezialisiert und sehr auf Koordinationsaufgaben ausgerichtet. Gleichzeitig versucht ZooKeeper nicht, dem Entwickler einen bestimmten Satz von Synchronisationsprimitiven aufzuzwingen, sondern ist sehr flexibel in Bezug auf das, was implementiert werden kann.

Es ist sicherlich möglich, verteilte Systeme zu bauen, ohne ZooKeeper zu verwenden. ZooKeeper bietet Entwicklern jedoch die Möglichkeit, sich mehr auf die Anwendungslogik zu konzentrieren als auf obskure Konzepte verteilter Systeme. Die Programmierung verteilter Systeme ohne ZooKeeper ist möglich, aber schwieriger.

Was ZooKeeper nicht kann

Das Ensemble der ZooKeeper-Server verwaltet kritische Anwendungsdaten, die mit der Koordination zusammenhängen. ZooKeeper ist nicht für die Speicherung großer Mengen geeignet. Für die Massenspeicherung von Anwendungsdaten gibt es eine Reihe von Optionen, wie Datenbanken und verteilte Dateisysteme. Wenn du eine Anwendung mit ZooKeeper entwickelst, trennst du idealerweise die Anwendungsdaten von den Kontroll- oder Koordinationsdaten. Sie haben oft unterschiedliche Anforderungen, zum Beispiel in Bezug auf Konsistenz und Haltbarkeit.

ZooKeeper implementiert einen Kernsatz von Operationen, die die Durchführung von Aufgaben ermöglichen, die bei vielen verteilten Anwendungen üblich sind. Wie viele Anwendungen kennst du, die einen Master haben oder verfolgen müssen, welche Prozesse ansprechbar sind? ZooKeeper implementiert diese Aufgaben jedoch nicht für dich. ZooKeeper wählt keinen Master und verfolgt auch nicht die aktiven Prozesse der Anwendung. Stattdessen stellt ZooKeeper die Werkzeuge für die Umsetzung solcher Aufgaben zur Verfügung. Der Entwickler entscheidet, welche Koordinationsaufgaben er implementieren möchte.

Das Apache Projekt

ZooKeeper ist ein Open-Source-Projekt, das von der Apache Software Foundation betreut wird. Es hat ein Project Management Committee (PMC), das für die Verwaltung und Überwachung des Projekts verantwortlich ist. Nur Committer können Patches einchecken, aber jeder Entwickler kann einen Patch beisteuern. Entwickler/innen können Committer werden, nachdem sie zum Projekt beigetragen haben. Beiträge zum Projekt sind nicht auf Patches beschränkt - sie können auch in anderer Form und durch Interaktion mit anderen Mitgliedern der Gemeinschaft erfolgen. Auf den Mailinglisten gibt es viele Diskussionen über neue Funktionen, Fragen von neuen Nutzern usw. Wir ermutigen Entwickler, die sich an der Community beteiligen möchten, sich auf den Mailinglisten einzutragen und an den Diskussionen teilzunehmen. Wenn du durch ein Projekt eine langfristige Beziehung zu ZooKeeper aufbauen möchtest, kann es sich auch lohnen, Committer zu werden.

Verteilte Systeme mit ZooKeeper aufbauen

Es gibt mehrere Definitionen für ein verteiltes System, aber für die Zwecke dieses Buches definieren wir es als ein System, das aus mehreren Softwarekomponenten besteht, die unabhängig und gleichzeitig auf mehreren physischen Rechnern laufen. Es gibt eine Reihe von Gründen, ein System auf verteilte Weise zu entwickeln. Ein verteiltes System ist in der Lage, die Kapazität mehrerer Prozessoren auszunutzen, indem es Komponenten parallel laufen lässt, vielleicht sogar repliziert. Ein System kann aus strategischen Gründen geografisch verteilt sein, z. B. wenn Server an mehreren Standorten an einer einzigen Anwendung beteiligt sind.

Eine separate Koordinierungskomponente hat einige wichtige Vorteile. Erstens kann die Komponente unabhängig entworfen und implementiert werden. Eine solche unabhängige Komponente kann in vielen Anwendungen eingesetzt werden. Zweitens kann ein Systemarchitekt leichter über den Koordinationsaspekt nachdenken, der nicht trivial ist (wie dieses Buch zu zeigen versucht). Und schließlich kann ein System die Koordinationskomponente separat ausführen und verwalten. Wenn eine solche Komponente separat ausgeführt wird, ist es einfacher, Probleme in der Produktion zu lösen.

Softwarekomponenten werden in Betriebssystemprozessen ausgeführt, in vielen Fällen mit mehreren Threads. ZooKeeper-Server und -Clients sind also Prozesse. Oft läuft auf einem physischen Server (egal ob es sich um einen eigenständigen Rechner oder ein Betriebssystem in einer virtuellen Umgebung handelt) nur ein einziger Anwendungsprozess, obwohl der Prozess mehrere Threads ausführen kann, um die Multicore-Kapazität moderner Prozessoren auszunutzen.

Prozesse in einem verteilten System haben zwei Möglichkeiten der Kommunikation: Sie können Nachrichten direkt über ein Netzwerk austauschen oder in einer gemeinsamen Speicherung lesen und schreiben. ZooKeeper nutzt das Modell der gemeinsamen Speicherung, um Anwendungen die Möglichkeit zu geben, Koordinations- und Synchronisationsprimitive zu implementieren. Die gemeinsame Speicherung selbst erfordert jedoch eine Netzwerkkommunikation zwischen den Prozessen und der Speicherung. Es ist wichtig, die Rolle der Netzwerkkommunikation zu betonen, denn sie ist eine wichtige Quelle für Komplikationen bei der Entwicklung eines verteilten Systems.

In realen Systemen ist es wichtig, auf die folgenden Punkte zu achten:

Nachricht Verzögerungen

Nachrichten können willkürlich verzögert werden, z. B. durch eine Überlastung des Netzwerks. Solche willkürlichen Verzögerungen können zu unerwünschten Situationen führen. So kann zum Beispiel ein Prozess P eine Nachricht senden, bevor ein anderer Prozess Q seine Nachricht nach einer Referenzuhr sendet, aber die Nachricht von Qwird vielleicht zuerst zugestellt.

Prozessorgeschwindigkeit

Die Zeitplanung des Betriebssystems und die Überlastung können zu willkürlichen Verzögerungen bei der Nachrichtenverarbeitung führen.Wenn ein Prozess eine Nachricht an einen anderen sendet, ist die Gesamtlatenz dieser Nachricht ungefähr die Summe aus der Verarbeitungszeit beim Sender, der Übertragungszeit und der Verarbeitungszeit beim Empfänger. Wenn der sendende oder empfangende Prozess Zeit benötigt, um für die Verarbeitung eingeplant zu werden, dann ist die Latenzzeit der Nachricht höher.

Taktdrift

Es ist nicht ungewöhnlich, dass Systeme eine gewisse Vorstellung von Zeit verwenden, z. B. wenn es darum geht, den Zeitpunkt von Ereignissen im System zu bestimmen. Die Uhren von Prozessoren sind nicht zuverlässig und können beliebig auseinanderdriften. Daher kann es zu falschen Entscheidungen führen, wenn man sich auf Prozessoruhren verlässt.

Eine wichtige Folge dieser Probleme ist, dass es in der Praxis sehr schwer ist, festzustellen, ob ein Prozess abgestürzt ist oder ob einer dieser Faktoren eine willkürliche Verzögerung verursacht. Wenn ein Prozess keine Nachricht erhält, kann das bedeuten, dass er abgestürzt ist, dass das Netzwerk seine letzte Nachricht willkürlich verzögert, dass etwas den Prozess verzögert oder dass die Prozessuhr abdriftet. Ein System, in dem eine solche Unterscheidung nicht getroffen werden kann, wird als asynchron bezeichnet.

Rechenzentren werden in der Regel mit großen Stapeln meist einheitlicher Hardware gebaut. Aber selbst in Rechenzentren haben wir beobachtet, wie sich all diese Probleme auf die Anwendungen auswirken, weil mehrere Hardware-Generationen in einer einzigen Anwendung zum Einsatz kommen und selbst innerhalb derselben Hardware-Charge subtile, aber erhebliche Leistungsunterschiede bestehen. All diese Dinge erschweren das Leben eines Designers für verteilte Systeme.

ZooKeeper wurde genau dafür entwickelt, den Umgang mit diesen Problemen zu vereinfachen. ZooKeeper lässt die Probleme nicht verschwinden oder macht sie für Anwendungen völlig transparent, aber er macht die Probleme nachvollziehbarer. ZooKeeper implementiert Lösungen für wichtige Probleme des verteilten Rechnens und verpackt diese Implementierungen auf eine Weise, die für Entwickler intuitiv ist... zumindest war das die ganze Zeit unsere Hoffnung.

Beispiel: Master-Worker Bewerbung

Wir haben abstrakt über verteilte Systeme gesprochen, aber jetzt ist es an der Zeit, das Ganze etwas konkreter zu machen. Betrachten wir eine gängige Architektur, die bei der Entwicklung verteilter Systeme häufig verwendet wird: eine Master-Worker-Architektur(Abbildung 1-1). Ein wichtiges Beispiel für ein System, das dieser Architektur folgt, ist HBase, ein Klon von Bigtable von Google. Auf einer sehr hohen Ebene ist der Master-Server (HMaster) dafür verantwortlich, den Überblick über die verfügbaren Regionsserver (HRegionServer) zu behalten und die Regionen den Servern zuzuweisen. Da wir hier nicht darauf eingehen, empfehlen wir dir, in der HBase-Dokumentation nachzulesen, wie ZooKeeper verwendet wird. Unsere Diskussion konzentriert sich stattdessen auf eine generische Master-Worker-Architektur.

Master–worker example.
Abbildung 1-1. Master-Worker-Beispiel

Im Allgemeinen ist in einer solchen Architektur ein Master-Prozess dafür verantwortlich, den Überblick über die verfügbaren Worker und Aufgaben zu behalten und den Workern Aufgaben zuzuweisen. Für ZooKeeper ist dieser Architekturstil repräsentativ, weil er eine Reihe gängiger Aufgaben abbildet, wie z. B. die Wahl eines Masters, das Verfolgen der verfügbaren Worker und die Pflege der Anwendungsmetadaten.

Um ein Master-Worker-System einzuführen, müssen wir drei zentrale Probleme lösen:

Meister stürzt ab

Wenn der Master fehlerhaft ist und nicht mehr zur Verfügung steht, kann das System keine neuen Aufgaben zuweisen oder Aufgaben von Arbeitern, die ebenfalls fehlgeschlagen sind, neu zuweisen.

Abstürze von Arbeitern

Wenn ein Arbeiter abstürzt, können die ihm zugewiesenen Aufgaben nicht erledigt werden.

Fehler in der Kommunikation

Wenn der Master und ein Arbeiter keine Nachrichten austauschen können, erfährt der Arbeiter möglicherweise nicht, dass ihm neue Aufgaben zugewiesen wurden.

Um diese Probleme zu bewältigen, muss das System in der Lage sein, zuverlässig einen neuen Master zu wählen, wenn der vorherige fehlerhaft ist, festzustellen, welche Worker verfügbar sind, und zu entscheiden, wann der Zustand eines Workers in Bezug auf den Rest des Systems veraltet ist. In den folgenden Abschnitten gehen wir kurz auf die einzelnen Aufgaben ein.

Master-Ausfälle

Um Master-Abstürze zu maskieren, brauchen wir einen Backup-Master. Wenn der primäre Master ausfällt, übernimmt der Backup-Master die Rolle des primären Masters. Ein Failing Over ist jedoch nicht so einfach, wie die Bearbeitung von Anfragen, die beim Master eingehen. Der neue Primary Master muss in der Lage sein, den Zustand des Systems wiederherzustellen, der zum Zeitpunkt des Absturzes des alten Primary Masters bestand. Um den Zustand des Masters wiederherstellen zu können, können wir uns nicht darauf verlassen, dass wir ihn vom fehlerhaften Master abrufen können, weil dieser abgestürzt ist; wir müssen ihn irgendwo anders haben. Dieser andere Ort ist ZooKeeper.

Die Wiederherstellung des Status ist nicht das einzige wichtige Thema. Angenommen, der primäre Master ist in Betrieb, aber der Backup-Master vermutet, dass der primäre Master abgestürzt ist. Dieser falsche Verdacht kann z. B. entstehen, weil der primäre Master stark belastet ist und seine Nachrichten willkürlich verzögert werden (siehe die Diskussion in "Verteilte Systeme mit ZooKeeper aufbauen"). Der Backup-Master führt alle notwendigen Prozeduren aus, um die Rolle des Primary Masters zu übernehmen, und kann schließlich damit beginnen, die Rolle des Primary Masters auszuführen und so zu einem zweiten Primary Master zu werden. Noch schlimmer ist es, wenn einige Worker nicht mit dem Primary Master kommunizieren können, z. B. wegen einer Netzwerkpartition, dann folgen sie dem zweiten Primary Master. Dieses Szenario führt zu einem Problem, das gemeinhin als Split-Brain bezeichnet wird: Zwei oder mehr Teile des Systems machen unabhängig voneinander Fortschritte, was zu inkonsistentem Verhalten führt. Bei der Suche nach einem Weg, mit Master-Ausfällen umzugehen, ist es wichtig, Split-Brain-Szenarien zu vermeiden.

Ausfälle von Arbeitern

Die Kunden reichen ihre Aufgaben beim Master ein, der die Aufgaben den verfügbaren Arbeitern zuweist. Die Arbeiter/innen erhalten die zugewiesenen Aufgaben und melden den Status der Ausführung, sobald diese Aufgaben ausgeführt wurden. Der Master informiert dann die Kunden über die Ergebnisse der Ausführung.

Wenn ein Arbeiter abstürzt, müssen alle Aufgaben, die ihm zugewiesen und nicht erledigt wurden, neu zugewiesen werden. Die erste Voraussetzung dafür ist, dass der Master in der Lage ist, den Ausfall eines Workers zu erkennen. Der Master muss in der Lage sein, den Absturz eines Workers zu erkennen und festzustellen, welche anderen Worker verfügbar sind, um seine Aufgaben auszuführen. Wenn ein Worker abstürzt, kann es passieren, dass er Aufgaben nur teilweise oder sogar vollständig ausführt, aber die Ergebnisse nicht meldet. Wenn die Berechnung Nebeneffekte hat, kann ein Wiederherstellungsverfahren erforderlich sein, um den Zustand zu bereinigen.

Fehler in der Kommunikation

Wenn die Verbindung eines Arbeiters zum Master unterbrochen wird, z. B. aufgrund einer Netzwerkpartition, könnte die Neuzuweisung einer Aufgabe dazu führen, dass zwei Arbeiter dieselbe Aufgabe ausführen. Wenn es akzeptabel ist, eine Aufgabe mehr als einmal auszuführen, können wir sie neu zuweisen, ohne zu überprüfen, ob der erste Arbeiter die Aufgabe ausgeführt hat. Wenn dies nicht akzeptabel ist, muss die Anwendung die Möglichkeit berücksichtigen, dass mehrere Arbeiter/innen versuchen, die Aufgabe auszuführen.

Ein weiteres wichtiges Problem bei Kommunikationsfehlern sind die Auswirkungen auf Synchronisationsprimitive wie Sperren. Da Knoten abstürzen können und Systeme anfällig für Netzwerkpartitionen sind, können Sperren problematisch sein: Wenn ein Knoten abstürzt oder weggeteilt wird, kann die Sperre andere daran hindern, Fortschritte zu machen. ZooKeeper muss daher Mechanismen implementieren, um mit solchen Szenarien umzugehen. Erstens können die Clients sagen, dass einige Daten im ZooKeeper-Status flüchtig sind. Zweitens verlangt das ZooKeeper-Ensemble, dass die Clients in regelmäßigen Abständen mitteilen, dass sie aktiv sind. Wenn ein Client dem Ensemble nicht rechtzeitig Bescheid gibt, werden alle ephemeren Daten dieses Clients gelöscht. Mit diesen beiden Mechanismen können wir verhindern, dass einzelne Clients die Anwendung bei Abstürzen und Kommunikationsfehlern zum Stillstand bringen.

Wir haben argumentiert, dass es in Systemen, in denen wir die Verzögerung von Nachrichten nicht kontrollieren können, nicht möglich ist, festzustellen, ob ein Client abgestürzt ist oder ob er nur langsam ist. Wenn wir also den Verdacht haben, dass ein Client abgestürzt ist, müssen wir davon ausgehen, dass er nur langsam ist und in Zukunft andere Aktionen ausführen könnte.

Zusammenfassung der Aufgaben

Aus den vorangegangenen Beschreibungen können wir die folgenden Anforderungen für unsere Master-Worker Architektur ableiten:

Meisterwahl

Für den Fortschritt ist es wichtig, dass ein Meister zur Verfügung steht, der den Beschäftigten Aufgaben zuweist.

Crash-Erkennung

Der Master muss in der Lage sein, zu erkennen, wenn Arbeiter/innen abstürzen oder die Verbindung unterbrechen.

Verwaltung der Gruppenmitgliedschaft

Der Master muss in der Lage sein, herauszufinden, welche Arbeiter für die Ausführung von Aufgaben zur Verfügung stehen.

Metadaten-Management

Der Master und die Arbeiter müssen in der Lage sein, Aufträge und Ausführungsstände zuverlässig zu speichern.

Im Idealfall wird jede dieser Aufgaben der Anwendung in Form eines Primitivs zur Verfügung gestellt, sodass die Implementierungsdetails vor dem Anwendungsentwickler vollständig verborgen bleiben. ZooKeeper bietet wichtige Mechanismen zur Implementierung solcher Primitive, damit Entwickler die Aufgaben implementieren können, die ihren Anforderungen am besten entsprechen, und sich auf die Anwendungslogik konzentrieren können. In diesem Buch bezeichnen wir Implementierungen von Aufgaben wie der Masterwahl oder der Absturzerkennung oft als Primitive, da dies konkrete Aufgaben sind, auf denen verteilte Anwendungen aufbauen.

Warum ist verteilte Koordination schwierig?

Einige der Komplikationen beim Schreiben von verteilten Anwendungen sind sofort ersichtlich. Wenn unsere Anwendung startet, müssen zum Beispiel alle verschiedenen Prozesse irgendwie die Anwendungskonfiguration finden. Im Laufe der Zeit kann sich diese Konfiguration ändern. Wir könnten alles herunterfahren, die Konfigurationsdateien neu verteilen und neu starten, aber das kann zu längeren Ausfallzeiten während der Neukonfiguration führen.

Mit dem Konfigurationsproblem verbunden ist das Problem der Gruppenzugehörigkeit. Wenn sich die Auslastung ändert, wollen wir in der Lage sein, neue Maschinen und Prozesse hinzuzufügen oder zu entfernen.

Die soeben beschriebenen Probleme sind funktionale Probleme, für die du bei der Implementierung deiner verteilten Anwendung Lösungen entwerfen kannst; du kannst deine Lösungen vor dem Einsatz testen und ziemlich sicher sein, dass du die Probleme richtig gelöst hast. Die wirklich schwierigen Probleme, auf die du bei der Entwicklung verteilter Anwendungen stoßen wirst, haben mit Fehlern zu tun - insbesonderemit Abstürzen und Kommunikationsfehlern. Diese Fehler können an jedem beliebigen Punkt auftreten, und es ist unmöglich, alle verschiedenen Eckfälle aufzuzählen, die behandelt werden müssen.

Byzantinische Verwerfungen

Byzantinische Fehler sind Fehler, die dazu führen können, dass sich eine Komponente auf eine willkürliche (und oft unvorhergesehene) Weise verhält. Eine solche fehlerhafte Komponente kann zum Beispiel den Anwendungsstatus beschädigen oder sich sogar böswillig verhalten. Systeme, die unter der Annahme gebaut werden, dass solche Fehler auftreten können, erfordern ein höheres Maß an Replikation und den Einsatz von Sicherheits-Primitiven. Obwohl wir anerkennen, dass es in der akademischen Literatur erhebliche Fortschritte bei der Entwicklung von Techniken zur Tolerierung byzantinischer Fehler gegeben hat, sahen wir keine Notwendigkeit, solche Techniken in ZooKeeper zu übernehmen, und haben daher die zusätzliche Komplexität in der Codebasis vermieden.

Ausfälle machen auch einen großen Unterschied zwischen Anwendungen, die auf einem einzelnen Rechner laufen, und verteilten Anwendungen deutlich: Bei verteilten Anwendungen kann es zu Teilausfällen kommen. Wenn ein einzelner Rechner abstürzt, schlagen alle Prozesse, die auf diesem Rechner laufen, fehl. Wenn mehrere Prozesse auf einem Rechner laufen und ein Prozess fehlschlägt, können die anderen Prozesse vom Betriebssystem über den Fehler informiert werden. Das Betriebssystem kann auch starke Garantien für den Nachrichtenaustausch zwischen den Prozessen bieten. All das ändert sich in einer verteilten Umgebung: Wenn ein Rechner oder ein Prozess fehlschlägt, laufen andere Rechner weiter und müssen eventuell für die fehlerhaften Prozesse einspringen. Um mit fehlerhaften Prozessen umgehen zu können, müssen die noch laufenden Prozesse in der Lage sein, den Ausfall zu erkennen; dabei können Nachrichten verloren gehen und es kann sogar zu Taktverschiebungen kommen.

Im Idealfall entwerfen wir unsere Systeme unter der Annahme, dass die Kommunikation asynchron ist: Die von uns verwendeten Maschinen können eine Taktdrift aufweisen und es kann zu Kommunikationsausfällen kommen. Wir gehen von dieser Annahme aus, weil diese Dinge tatsächlich passieren. Uhren driften ständig, wir alle haben schon gelegentlich Netzwerkprobleme erlebt, und leider gibt es auch Ausfälle. Was bedeutet das für die Grenzen unserer Möglichkeiten?

Nehmen wir den einfachsten Fall. Nehmen wir an, wir haben eine verteilte Konfiguration, die sich verändert hat. Diese Konfiguration ist so einfach, wie sie nur sein kann: ein Bit. Die Prozesse in unserer Anwendung können starten, sobald sich alle laufenden Prozesse auf den Wert des Konfigurationsbits geeinigt haben.

Es stellt sich heraus, dass ein berühmtes Ergebnis im verteilten Rechnen, bekannt als FLP nach den Autoren Fischer, Lynch und Patterson, bewiesen hat, dass in einem verteilten System mit asynchroner Kommunikation und Prozessabstürzen die Prozesse sich nicht immer auf das eine Bit der Konfiguration einigen können.1 Ein ähnliches Ergebnis, bekannt als CAP, das für Konsistenz, Verfügbarkeit und Partitionstoleranz steht, besagt, dass wir bei der Entwicklung eines verteilten Systems alle drei Eigenschaften haben wollen, aber kein System alle drei beherrscht.2 Bei der Entwicklung von ZooKeeper wurde vor allem auf Konsistenz und Verfügbarkeit geachtet, obwohl das System auch die Möglichkeit bietet, bei Netzwerkpartitionen nur zu lesen.

OK, wir können also kein ideales fehlertolerantes, verteiltes System haben, das sich transparent um alle Probleme kümmert, die jemals auftreten könnten. Wir können aber ein etwas weniger ehrgeiziges Ziel anstreben. Zunächst müssen wir einige unserer Annahmen und/oder Ziele lockern. Wir können zum Beispiel davon ausgehen, dass die Uhr innerhalb bestimmter Grenzen synchronisiert ist; wir können uns dafür entscheiden, immer konsistent zu sein und die Fähigkeit zu opfern, einige Netzwerkpartitionen zu tolerieren; es kann Zeiten geben, in denen ein Prozess zwar läuft, aber so tun muss, als sei er fehlerhaft, weil er sich des Zustands des Systems nicht sicher sein kann. Das sind zwar Kompromisse, aber es sind Kompromisse, die es uns ermöglicht haben, einige beeindruckende verteilte Systeme zu bauen.

ZooKeeper ist ein Erfolg - mit Vorbehalten

Nachdem wir darauf hingewiesen haben, dass es keine perfekte Lösung gibt, können wir wiederholen, dass ZooKeeper nicht alle Probleme lösen kann, mit denen Entwickler von verteilten Anwendungen konfrontiert sind. Er gibt dem Entwickler aber ein gutes Gerüst an die Hand, um mit diesen Problemen umzugehen. Im Laufe der Jahre wurde viel im Bereich des verteilten Computings gearbeitet, auf dem ZooKeeper aufbaut. Paxos3 und virtuelle Synchronität4 waren besonders einflussreich bei der Entwicklung von ZooKeeper. Er geht so nahtlos wie möglich mit den Änderungen und Situationen um, die sich ergeben, und gibt den Entwicklern ein Gerüst an die Hand, um mit Situationen umzugehen, die sich nicht automatisch bewältigen lassen.

ZooKeeper wurde ursprünglich bei Yahoo! entwickelt, wo es eine Fülle großer verteilter Anwendungen gibt. Uns ist aufgefallen, dass die Aspekte der verteilten Koordination einiger Anwendungen nicht angemessen behandelt wurden, so dass Systeme mit Single Points of Failure eingesetzt wurden oder brüchig waren. Auf der anderen Seite verbrachten andere Entwickler so viel Zeit mit der verteilten Koordination, dass sie nicht genug Ressourcen hatten, um sich auf die Funktionalität der Anwendung zu konzentrieren. Wir stellten außerdem fest, dass diese Anwendungen alle einige grundlegende Koordinationsanforderungen gemeinsam hatten, also machten wir uns daran, eine allgemeine Lösung zu entwickeln, die einige Schlüsselelemente enthielt, die wir einmal implementieren und in vielen verschiedenen Anwendungen einsetzen konnten. ZooKeeper hat sich als viel allgemeiner und beliebter erwiesen, als wir es je für möglich gehalten hätten.

Im Laufe der Jahre haben wir festgestellt, dass es einfach ist, einen ZooKeeper-Cluster einzurichten und Anwendungen für ihn zu entwickeln - so einfach, dass manche Entwickler ihn nutzen, ohne einige der Fälle zu verstehen, in denen der Entwickler Entscheidungen treffen muss, die ZooKeeper nicht selbst treffen kann. Eines der Ziele dieses Buches ist es, dafür zu sorgen, dass Entwickler verstehen, was sie tun müssen, um ZooKeeper effektiv zu nutzen, und warum sie es auf diese Weise tun müssen.

1 Michael J. Fischer, Nancy A. Lynch, und Michael S. Paterson, "Impossibility of Distributed Consensus with One Faulty Process," Proceedings of the 2nd ACM SIGACT-SIGMOD Symposium on Principles of Database Systems (1983), doi:10.1145/588058.588060.

2 Seth Gilbert und Nancy Lynch, "Brewer's Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services", ACM SIGACT News 33:2 (2002), doi:10.1145/564585.564601.

3 Leslie Lamport, "The Part-Time Parliament", ACM Transactions on Computer Systems 16:2 (1998): 133-169.

4 K. Birman und T. Joseph, "Exploiting Virtual Synchrony in Distributed Systems", Proceedings of the 11th ACM Symposium on Operating Systems Principles (1987): 123-138.

Get ZooKeeper now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.