9: Verteilte Algorithmen Verteiltes System: Zusammenschluss unabhängiger Computer ( Knoten ), das sich für den Benutzer als einzelnes System präsentiert. (Begriffsbildung nach A. Tanenbaum hatten wir schon) Wenn ein System deshalb fehlerhaft arbeitet, weil ein Computer ausfällt, von dessen Existenz der Benutzer gar nicht wusste, dann handelt es sich um ein Verteiltes System. (frei nach Leslie Lamport) 446
In diesem Kapitel: Algorithmen für Verteilte Systeme, und einer Betrachtung ihrer Zuverlässigkeit under widrigen Umständen, z.b. wenn Knoten ausfallen oder fehlerhaft arbeiten, oder viele gleichberechtigte Knoten das System verteilt organisieren müssen, oder... Konvention: Unser VS besteht aus maximal n Knoten K 1,..., K n. n ist bekannt, und jeder K i kennt seinen eigenen Index i. 447
Verteilte Systeme: Begriffsbildung Typische Architektur-Modelle: Client-Server, Proxy Server, Peer to Peer Interaktion Synchron (Ausführungszeit von Prozessen und Transmissionszeit von Nachrichten bekannt (bzw. nach oben beschränkt), synchronisierte Uhren) schwierig und teuer, wichtig für hard real-time applications Asynchron man kann den Ausfall eines Knotens nicht erkennen Asynchron, aber Ausfall-Erkennung durch Time-Out keine synchronen Uhren nötig aber Fähigkeit, Zeitdifferenzen ungefähr zu schätzen mögliche Fehler von Knoten in Verteilten Systemen Ausfall ( nix geht mehr ) beliebiges Fehlverhalten ( Byzantine Failures ) 448
9.1: Zeit in Verteilten Systemen Jede Maschine in einem VS hat eine eigene Uhr. Umwelteinflüsse, Produktionsgenauigkeit: Uhren laufen unterschiedlich schnell. Synchronisierte Systeme mit physikalischen Zeit : bekannte (maximale) Synchronisationsfehler, regelmäßiger Abgleich der Uhren Oft eine Alternative: logische Uhren 9.1: Zeit in Verteilten Systemen 449
Beispiel: Das Programm make erlaubt es, Abhängkeiten zwischen Daten zu spezifizieren, z.b., dass x.o von x.c abhängt; x wird dann (und nur dann) neu compiliert, wenn x.o entweder nicht existiert, oder älter als x.c ist. Wichtig: Zeitstempel von x.o und x.c! (Ada (z.b. gnatmake ): Abhängigkeiten automatisch aus with-klauseln; Zeitstempel bleiben wichtig!) 9.1: Zeit in Verteilten Systemen 450
Beispiel: Das Programm make (2) 33 34 35 x.o 29 30 x.c t (x.o)=33 t (x.c)=29 Eine Falle in Verteilten Systemen Zeit t0 : x.o wird auf Knoten C erzeugt; Zeitstempel: t 0. Zeit t1 > t 0 : x.c wird auf Knoten E erzeugt. Zeitstempel: t 1. C und E: unterschiedliche Uhren; t 0 > t 1, möglich! Zeitstempel mit logischer Zeit würden reichen! 9.1: Zeit in Verteilten Systemen 451
Kausaler Zusammenhang Für zwei Ereignisse a und b scheiben wir a b, wenn b kausal abhängig von a ist ( a happened before b ). Formal: 1. Sind a und b Ereignisse auf dem gleichen Knoten, gilt a b, wenn a vor b eingetreten ist. 2. Ist a das Ereignis, eine Nachricht M von einem Knoten A an einen Knoten B zu schicken, und ist b das zugehörige Empfangsereignis auf dem Knoten B, dann gilt a b. 3. Gibt es ein Ereignis z mit a z und z b, dann gilt auch a b (Transitivität von ). Sonst gilt a b nicht. Gilt weder a b noch b a, dann sind a und b nebenläufig, a b 9.1: Zeit in Verteilten Systemen 452
Beispiel a g i l c e h k b d f j In welchem Verhältnis stehen die Ereignisse a, b, c und d zueinander? 9.1: Zeit in Verteilten Systemen 453
Die logischen Uhren von Lamport Jeder Knoten K i : Zähler C i (Wertebereich groß genug, dass in der Lebenszeit des Systems kein Überlauf eintritt). Bei jedem Ereignis auf einem Knoten K i : C i := C i + 1. Wenn K i eine Nachricht mit dem Zeitstempel T erhält: C i := max{c i, T } + 1 (Zeitstempel f. d. Erhalt der Nachricht). Theorem 1 Wir schreiben L(x) für den Lamport-Zeitstempel eines Ereignisses x. Es gilt: (a b) (L(a) < L(b)). 9.1: Zeit in Verteilten Systemen 454
Das gleiche Beispiel noch einmal a g i l c e h k b d f j Drei Knoten, zu Beginn jeweils mit Lamport-Zähler 0. Wie verändern sich die Zähler durch die Ereignisse? 9.1: Zeit in Verteilten Systemen 455
Physikalische Zeit, Lamport-Zeit und Kausalität 1. Die Umkehrung des Theorems gilt leider nicht. Man überlege sich ein Gegenbeispiel, um die Aussage (L(a) < L(b)) (a b) zu widerlegen. 2. Sei das Ereignis b nach physikalischer Zeit vor dem Ereignis a eingetreten. 2.1 Kann a b gelten? 2.2 Kann L(a) < L(b) gelten? 3. Die Nebenläufigkeit ist kommutativ, d.h. a b b a. Ist die Nebenläufigkeit auch transitiv, d.h., gilt (a b b c) a c? 9.1: Zeit in Verteilten Systemen 456
Total geordnete Lamport-Zeit Die Lamport-Zeit L erlaubt die Existenz von Ereignissen a b mit identischen Zeitstempeln L(a) = L(b). Man beachte jedoch, dass das nur geht, wenn a und b auf verschiedenen Knoten eingetreten sind. Wenn a auf Knoten K i und b auf Knoten K j K i aufgetreten ist, definieren wir deshalb eine total geordnete Lamport-Zeit L (a) < L (b) { L(a) < L(b) (L(a) = L(b)) (i < j) 9.1: Zeit in Verteilten Systemen 457
Starke logische Uhren Wir suchen eine logische Uhr, die Kausalitätszusammenhänge tatsächlich wiedergibt. Für alle Zeitstempel T (x) von Ereignissen x soll nicht nur gelten, sondern auch (a b) (T (a) < T (b)) (T (a) < T (b)) (a b). 9.1: Zeit in Verteilten Systemen 458
Vektor-Uhren Jeder Knoten K q : Vektor von Zählern c q = (c q 1,..., cq n ): c q r : beste untere Schranke der logischen Zeit von K r, soweit K q bekannt c q q : genau bekannt, weil eigene Zeit Bei jedem Ereignis auf K q : c q r := c q r + 1. K q erhält Nachricht mit Zeitstempel (T 1,..., T n ): Für 1 r n: c q r := max{c q r, T r } + 1; c q r := c q r + 1 (Zeitstempel f. d. Erhalt der Nachricht). Vektor-Uhren sind starke logische Uhren. Mehr Infos: http://de.wikipedia.org/wiki/vektoruhr. 9.1: Zeit in Verteilten Systemen 459
9.2: Verteilter globaler Zustand Oft muss man den Gesamtzustand eines VS kennen. Der ist verteilt auf die Knoten und, ggf., auch auf die Kommunikationskanäle zwischen den Knoten (K i hat eine Nachricht an K j geschickt, die aber noch nicht angekommen ist). Distributed Snapshot (Chandy, Lamport): Ermittle einen Zustand, in dem das System vielleicht nie war, der aber garantiert konsistent ist. D.h., ausgehend von dem Kausalen Zusammenhang, könnte das System in dem Zustand gewesen sein. 9.2: Verteilter globaler Zustand 460
Der Cut eines Verteilten Systems In jedem Prozess K i : Serie von Ereignissen E i = (e i 1, ei 2,...). Zu jedem Zeitpunkt entspricht der lokale Zustand eines Prozesses einer endliche Serie von Ereignissen: E i k = (ei 1, ei 2,... ei k ). Ein Cut beschreibt den Verteilten globalen Zustand eines Verteilten Systems als Vereinigung von jeweils einem lokalen Zustand für jeden Knoten: E 1 k 1 E 2 k 2 E n k n. Die Vereinigung der jeweils aktuellsten Ereignisse ist die Front des Cuts: { e 1 k1, e 2 k 2,..., e n k n }. 9.2: Verteilter globaler Zustand 461
Konsistenz von Cut und Zustand Ein Cut C ist konsistent, dann und nur dann wenn wenn b C : (a b) (a C) gilt. Ein Cut ist stark konsistent, wenn für jedes a C, das das Senden einer Nachricht an einen andern Knoten repräsentiert, auch das Ereignis b, das dem Ankommen dieser Nachrichricht, in C ist. Ein globaler Zustand ist konsistent bzw. stark konsistent, er wird durch einen entsprechenden Cut definiert. 9.2: Verteilter globaler Zustand 462
Beispiel a g i l c e h k b d f j Ein inkonsistenter Cut mit der Front (l, h, j) und 2 konsistente Cuts mit den Fronten (a, c, b) und (i, c, j). Beide sind nicht stark konsistent. 9.2: Verteilter globaler Zustand 463
Der Lamport-Chandy Algorithmus Voraussetzungen: Zuverlässige Knoten (kein Ausfall) Verbindungen zwischen Knoten: Vollständiger gerichteter Graph FIFO-Arbeitsweise in jeder Richtung zuverlässig (jede Nachricht kommt genau einmal an) Ein beliebiger Prozess (der Observer ) initiert den Snapshot. Während der Snapshot genommen wird, kann das Verteilte System normal weiterarbeiten. Jeder Prozess zeichnet seinen eigenen lokalen Zustand auf und liest alle an ihn gerichteten Nachrichten selbst. 9.2: Verteilter globaler Zustand 464
Der Algorithmus (Idee) Der Observer und jeder Knoten, der erstmalig eine Snapshot-Nachricht erhält, zeichnet seinen internen Zustand auf und verschickt seinerseits eine Snapshot-Nachricht an alle Knoten. Wenn ein Knoten K i eine Snapshot-Nachricht von einem Knoten K j erhält, sind alle vor dem Snapshot von K j an K i geschickten Nachrichten bereits angekommen ( FIFO). Diese gehören in einen stark konsistenten Cut. Die Nachrichten danach gehören nicht dazu. 9.2: Verteilter globaler Zustand 465
Der Algorithmus (Anfang) Der Observer K o 1. speichert seinen eigenen lokalen Zustand in Z o 2. legt für jedes k {1,..., n}, k o eine Nachrichtenliste M(k) an, anfänglich leer, und 3. sendet eine Snapshot -Nachricht an alle K j, j o. Jeder Knoten K i K o, der erstmalig eine Snapshot-Nachricht von irgend einem Knoten K j erhält: 1. speichert seinen aktuellen Zustand in Z i 2. sendet eine Snapshot -Nachricht an alle K j, j i 3. und legt für jedes k {1,..., n}, k {i, j} eine (anfänglich leere) Nachrichtenliste an. 9.2: Verteilter globaler Zustand 466
Der Algorithmus (Haupt-Arbeit) K i habe bereits eine Snapshot -Nachricht verschickt hat. Nun ist eine Nachricht M von K m angekommen. M kann eine Snapshot-Nachricht sein, oder nicht. Die Liste M(m) kann existieren, oder nicht. Daraus ergeben sich vier Fälle: Kein Snapshot, keine Liste: K i tut nichts. Kein Snapshot, Liste: K i hängt M an M(m) an. Snapshot, keine Liste: Dieser Fall wäre ein Fehler! Snapshot, Liste: K i ergänzt Z i um die Ereignisse aus M(m) und K i löscht die Liste M(m). War M(m) die letzte Liste, dann schickt K i den finalen Zustand Z i an den Observer (oder markiert Z i als final, falls K i selbst der Observer ist). 9.2: Verteilter globaler Zustand 467
Der Algorithmus (Ende) Jeder Knoten K i K o ist fertig, wenn wer seine finale Zustandsliste Z i an den Observer K o geschickt hat. K o wartet auf n finale Zustandslisten (einschließlich seiner eigenen). Deren Vereinigung ist ein stark konsistenter Cut. Zum Nachdenken: Warum ist dieser Cut eigentlich stark konsistent? 9.2: Verteilter globaler Zustand 468
Bermerkung Einen globalen Snapshot braucht man, z.b., um Eine Garbage-Collection in einem VS zu duchzuführen, Einen Deadlock zu erkennen und, ggf., zu behenben,... 9.2: Verteilter globaler Zustand 469
9.3: Gemeinsam Genutzte Ressourcen in Verteilten Systemen Verteilte Systeme sind immer nebenläufig. Nebenläufigkeit impliziert, dass man den Zugriff auf gemeinsam genutzte Ressourcen regeln muss. Bereits bekannt: Jeweils nur ein Prozess darf in einer kritischen Region arbeiten. Auch bekannt: abstrakte Ansätze ( protected types,... ) und maschinennahe Methoden (Mutex-Variablen). In diesem Abschnitt: 1. Einsatz eines zentralen Koordinators 2. Ricart-Agrawala (ohne Token) Später noch Token-basierte Algorithmen 9.3: Gemeinsam genutzte Ressourcen 470
Zentraler Koordinator Drei Typen von Nachrichten: request X, um exklusiven Zugriff auf X zu bekommen, grant X, als Antwort des Koordinators, und release an den Koordinator, um die X wieder freizugeben. Kann ein request nicht sofort gewährt werden: Warteschlange. 9.3: Gemeinsam genutzte Ressourcen 471
Der zentrale Koordinator: Ein Zuverlässigkeitsproblem! Single point of failure : Wenn der Koordinator ausfällt, ist der Zugriff auf X nicht mehr möglich. Abhilfe: Zusätzliche Status-Abfragen bzw. Auskünfte, wenn ein request zu lange nicht mit einem grant beantwortet wird. Bei Bedarf (Time-Out) Neuwahl des Koordinators. Bis es dazu kommt, ist X leider unzugänglich. 9.3: Gemeinsam genutzte Ressourcen 472
Ricart-Agrawalka ohne Token Voraussetzungen: zentraler Koordinator wird nicht gebraucht vollständiger gerichteter Graph (jeder Knoten kann an jeden Knoten eine Nachricht schicken) total geordnete logische Zeitstempel Idee: Zustände: released(x), requested(x) und held(x) anfänglich released(x) auf allen Knoten für jedes X: höchstens ein Knoten mit held(x) wenn ein Knoten auf X zugreifen will: requested(x), Request an alle anderen Knoten Weiter erst, wenn alle anderen Knoten zugestimmt haben (kein held(x), kein eigener Request mit kleinerem Zeitstempel) 9.3: Gemeinsam genutzte Ressourcen 473
Der Algorithmus (K i will auf X zugreifen) State(X) := requested; Request(X) (mit Timestamp) an alle Knoten K j K i ; warte, bis von allen Knoten K j K i eine Antwort eingetroffen ist; State(X) := held; Benutze X (kritische Sektion) speichere einkommende Requests State(X) := released; Beantworte eingekommene Requests 9.3: Gemeinsam genutzte Ressourcen 474
Der Algorithmus (Eingang Requests (X)) Wenn State(X)= held, dann speichere Request ( vorige Folie). Wenn State(X)= released, dann beantworte Request. Wenn State(X)= requested, dann T i : Timestamp der eigenen Request; T j der der eingegangenen Request; wenn T i < T j, dann speichere Request sonst beantworte Request. 9.3: Gemeinsam genutzte Ressourcen 475
Ist der Ricart-Agrawalka Algorithmus zuverlässiger/besser als der zentrale Koordinator Kein zentraler Koordinator, dessen Ausfall den Zugriff auf die Ressource unmöglich machen würde (bis zur Neuwahl eines Koordinators) Aber: Fällt irgend einer der vorhandenen Knoten aus, ist der Zugriff auf die Ressource genauso unmöglich (bis zu einer Neubestimmung, welche Knoten noch aktiv sind) Außerdem: 2*(n-1) Nachrichten (statt nur 3) Der unechte verteilte Algorithmus ist in viellerlei Hinsicht besser als der echte... 9.3: Gemeinsam genutzte Ressourcen 476
9.4: Ein Auswahlalgorithmus Ein Knoten, der bestimmte Koordinierungsaufgaben löst, ist oft nützlich (haben wir ja gerade festgestellt). Was aber, wenn wir viele gleichartige Knoten haben, und kein Externer bestimmt, welcher davon der Koordinator sein soll? Es ist egal, wer die Aufgabe übernimmt wichtig ist aber, dass unmissverständlich für alle feststeht, wer es ist! 9.4: Ein Auswahlalgorithmus 477
Voraussetzungen Für den hier vorgestellten Bully-Algorithmus: jeder hat eine eindeutige Knoten-Nummer {1,..., n} keiner weiss, welche der K 1,..., K n gerade laufen einer von denen, die laufen, muss die Aufgabe übernehmen bei uns der mit der höchsten Knoten-Nummer 9.4: Ein Auswahlalgorithmus 478
Der Bully Algorithmus 1. Wenn K i feststellt, dass der augenblickliche Koodinator nicht mehr reagiert, schickt er eine Auswahl-Nachricht an alle Knoten mit höherer Nummer Bekommt er (in einem geg. Zeitintervall) keine Antwort, ist er der neue Koodinator und teilt allen anderen Knoten mit, das er der neue Koodinator ist Sonst wartet er auf die Nachricht des neuen Koordinators 2. Erhält ein Knoten eine Auswahlnachricht beantwortet er diese und verfährt wie unter 1. 9.4: Ein Auswahlalgorithmus 479
9.5: Token-basierte Algorithmen, um gemeinsam genutzte Ressourcen zu verwalten Zugriff auf Ressource X durch Token geregelt Genau ein Knoten besitzt das Token Dieser Knoten darf auf X zugreifen wenn er will Weitergabe des Tokens an Knoten, die die Freigabe von X warten 9.5: Token-basierte Algorithmen 480
Der Token-basierte Algorithmus von Ricart-Agrawalka Total geordnete Zeitstempel ( mal wieder) Anfang: Irgend ein Knoten bekommt das Token Wenn ein Knoten K i auf X zugreifen will und das Token nicht hat schickt er eine Request-Nachricht mit seinem aktuellen logischen Zeitstempel an alle Knoten und wartet auf das Token Alle Knoten füren eine Liste aller Request-Nachrichten Wenn der Token-Inhaber nicht (mehr) auf X zugreift, und die Liste der Requests nicht leer ist, gibt er das Token an den Prozess weiter, dessen Request den kleinsten Zeitstempel hat 9.5: Token-basierte Algorithmen 481
Der Token-Ring Algorithmus keine Zeitstempel logischer Ring (z.b. K 1 K 2 K n K 1 ) (hat nichts mit der physikalischen Anordnung der Knoten zu tun!) Wenn Knoten K i das Token erhält kann er entweder das Token an Ki+1 (bzw. f. i = n an K 1 ) sofort weitergeben oder X nutzen und das Token dann weitergeben 9.5: Token-basierte Algorithmen 482
Vor- und Nachteile der Token-basierten Algorithmen R.A.-Token: Ausfall eines Knotens schadet nur, wenn dieser Knoten zufällig das Token hat n Nachrichten, um den Zugriff zu erhalten jeder Prozess muss Request-Liste führen Token-Ring: Ausfall eines Knotens fürt zum Verlust des Tokens kein Zugriff mehr möglich Anzahl Nachrichten pro Request sehr klein, wenn viele Requests (minimal 1) groß, wenn wenige Requests (max. ) sehr einfach! 9.5: Token-basierte Algorithmen 483
9.6: Schlussbemerkungen Die Synchronisation von Uhren, der Zugriff auf gemeinsam genutzte Ressourcen und das Ermitteln eines globalen Zustandes stellen besondere Probleme in Verteilten Systemen dar. Zu den algorithmischen Methoden, die besonderen Probleme in Verteilten Systemen zu lösen, gehören der Einsatz zentraler Koordinatoren, Token und die logische Anordnung der Knoten in einem Ring 9.6: Schlussbemerkungen 484