Microservices sind Verteilte Systeme: Asynchronität und Eventual Consistency
Bei der Reaktionsfähigkeit auf dynamische Änderungsanforderungen konnten Microservices überzeugen (s. Teil 1 & Teil 2 unserer Artikelserie), aber in ihrer fachlichen Ausrichtung hatten sie gegenüber SOA nur bedingt Neues beizutragen (s. Teil 3).
Eine spannende Frage ist, ob nicht schon allein die Servicegröße eine neue Qualität erzeugt. Dass dem tatsächlich so ist, zeigt sich beim Anblick einer großen Microservice-Architektur. Es ist schlicht beeindruckend, den Kommunikationsfluss in der Netflix-Architektur zu beobachten (s. Abb. 1) [1].
Microservices sind verteilte Systeme
Wir haben es in einer Microservice-Architektur mit einem sehr großen, sehr verteilten System zu tun. Entsprechend benötigen wir verschiedene Maßnahmen, um dessen Robustheit sicherzustellen.
Wer Microservices einführt, muss sich automatisch mit den Problemen verteilter Systeme befassen, die die bekannten "Fallacies of Distributed Computing" schön zusammenfassen [2]. Derlei Probleme gibt es natürlich auch ohne Microservices, denn auch monolithische Anwendungen beziehen Daten von externen Systemen und liefern die Ergebnisse übers Netzwerk bei den Abnehmern ab.
Aber eine einfache Rechnung zeigt die Notwendigkeit zusätzlicher Maßnahmen in einer Microservice-Architektur auf: Ist der große Monolith zu 99,9 Prozent verfügbar (d. h. knapp 9 Stunden Ausfallzeit pro Jahr), dann führt die naive Aufteilung in 500 Microservices zu einer Gesamtverfügbarkeit von knapp 60 Prozent (99,9 Prozent ^ 500, d. h. eine jährliche Teilausfallzeit von knapp 3.448 Stunden oder 143 Tagen). Schon 200 Microservices bedeuten eine Gesamtverfügbarkeit von knapp 82 Prozent, also eine jährliche Teilausfallzeit von ca. 66 Tagen.
Solcherlei Verschlechterungen sind nicht akzeptabel. Maßnahmen zur Fehlerbehandlung müssen also fein auf die Umgebung abgestimmt sein, um Fehlersituationen nicht noch zu verschlimmern. Daher sind zusätzliche Mechanismen wie Timeouts, Circuit-Breaker und Bulkheads erforderlich [3]. Beachtenswert sind die Netflix-Maßnahmen zum automatisierten Destructive Testing [4]: Doch welcher Kunde hat schon den Mut zu Fault Injection – mit einem Chaos Monkey, der zufällig ausgewählte Knoten ausfallen lässt, oder einem Latency Monkey, der zufällig Latenzen erzeugt – wohlgemerkt losgelassen auf das Produktivsystem?!
Netflix' offensive Art, betriebliche Risiken anzugehen, ist eindrucksvoll und lehrreich. Allerdings bringt dieses Vorgehen die betriebliche Infrastruktur dicht zu jedem einzelnen Entwickler. Das kann natürlich im DevOps-Sinne "You build it, you run it" auch gewollt sein. Aber man muss sich bewusst sein, dass jede Stunde Arbeitszeit, die ein Entwickler mit betrieblichen Problemen verbringt, weniger Zeit für die Umsetzung von Fachanforderungen zulässt.
Der Abschied von der strengen Konsistenz
In allen Beiträgen über Microservices findet sich der Hinweis auf die gelockerten Konsistenzregeln. Statt der strengen Konsistenz im Sinne von ACID (Atomicity, Consistency, Isolation, Durability) sichert das Gesamtsystem lediglich Eventual Consistency zu, d. h. den allmählichen Übergang zur strengen Konsistenz mit – gemessen an den Eigenschaften der strengen Konsistenz – zwischenzeitlich beobachtbaren Inkonsistenzen. Dadurch kann der einzelne Microservice unabhängig von anderen arbeitsfähig sein. In manchen Fällen kann Eventual Consistency auch die Konsequenz einer Performance-Verbesserung durch Trennung von Schreib-Änderungen und Lese-Abfragen sein (Command Query Responsibility Segregation CQRS, [5]). Eventual Consistency wird jedoch v. a. durch die Eigenschaften verteilter Systeme bedingt: Gemäß des CAP-Theorems (Consistency, Availability, Partition Tolerance) müssen wir uns bei den unvermeidbaren Teilausfällen entweder für Konsistenz oder für Verfügbarkeit entscheiden – denn nur zwei von drei Eigenschaften sind gemäß CAP-Theorem erreichbar. Vielen Auftraggebern ist die Verfügbarkeit besonders wichtig, insbesondere wenn die Anwendung auch von Endanwendern z. B. im Internet genutzt werden soll.
Und auch wenn die Anwendung in solchen Fällen den Auftrag nicht vollständig bearbeiten kann, so können wir dem Nutzer Alternativen anbieten, anstatt den Auftrag einfach wegen möglicher Inkonsistenzen abzulehnen. Dieses Vorgehen, in Problemsituationen mit eingeschränkter Funktionalität zu arbeiten, ist als Graceful Degradation bekannt. Ein vollständiger und durchgängiger Auftragsabbruch ist in einer Microservice-Architektur sowieso unmöglich, denn jeder Service stellt für sich, ohne Einbezug der verwendeten Services, eine eigene Transaktionseinheit dar – damit verbieten sich verteilte Transaktionen von selbst. Das System zeigt fachlich zu definierende Zwischenzustände, die mit der wachsenden Service-Anzahl zunehmen. Beispielsweise könnte ein Warenkorb-Microservice den Warenkorb nach der Bestellung bereits geleert haben, obwohl der Rechnungs-Microservice die Bestellung noch nicht verarbeitet hat. Das System muss für diesen Zwischenzustand die passende Außensicht vorsehen. Bei der Nachbearbeitung von teilfertigen Aufträgen sind ebenfalls fachlich festzulegende Aktivitäten durchzuführen, für die das System die Aktivitätsquelle ist. Zwischenzustände sind daher in diesem Sinne keine Inkonsistenzen und müssen aus Nutzersicht auch nicht sichtbar sein, erschweren aber leider den Systemüberblick im Monitoring.
Asynchronität als Prinzip: Choreographie statt Orchestrierung
Unabhängig von Fehlersituationen liegen die Latenzzeiten in verteilten Systemen um Größenordnungen über denen innerhalb eines Monolithen [6]. Vor allem die Akkumulation von Latenzen wegen transitiver Abhängigkeiten führt zu unschönen Effekten [7]. Maßnahmen zum Umgang mit Latenzzeiten und zum Nachbearbeiten von Teilfertigstellungen sind also nötig. Dies führt in zunehmendem Maße dazu, dass wir die bisher verinnerlichte Request/Response-Denkweise aufgeben müssen: Es gibt nur noch zum Teil synchrone Abläufe, deren direkter Ursprung eine Nutzeranfrage ist. Der Großteil der Aktivitäten muss asynchron erfolgen. Diese Umkehr zeigt [3b] als Orchestration versus Choreography: Es gibt keine zentral steuernde Einheit (schwergewichtige Orchestrierung, s. Abb. 2), sondern nur noch auf Stimuli geeignet reagierende Komponenten (leichtgewichtige Choreographie, s. Abb. 3).
Letztendlich bedeutet die lose Kopplung einer Microservice-Architektur die Einführung eines ereignisgesteuerten Systems mit Eventual Consistency. Martin Fowler erläutert sehr plastisch, warum die Einführung solch eines Systems gut überlegt sein will [8]. Sam Newman sieht darin das Spannungsfeld zwischen Standardisierung und Freiheit, ersteres mit den Eigenschaften Konsistenz und Sicherheit, letzteres mit Autonomie und Reaktionsfähigkeit [9]. Erst auf den zweiten Blick zeigt sich ein wesentlicher Nachteil solcher ereignisgesteuerten Systeme: Es ist schwierig und aufwändig, die Ursache eines Effekts zu bestimmen (viel Vergnügen bei der Fehleranalyse über alle beteiligten Anwendungen hinweg, z. B. auf Basis von Correlation-IDs!), und es bedarf zusätzlicher fachlicher Verfahren, um den ordnungsgemäßen Abschluss eines Auftrags sicherzustellen (z. B. per Monitoring und Berichtswesen).
Zusammenfassung
Microservices führen mit Macht in die Problematik verteilter Systeme. Allen möglichen Ausfallszenarien muss der einzelne Service auf technischer Ebene entgegenwirken. Das reicht jedoch nicht:
Teilausfälle sind in einem Microservices-Umfeld trotz aller Umsicht die Regel, nicht die Ausnahme. In dieser Situation helfen nur Handlungsalternativen auf fachlicher Ebene weiter (z. B. durch fachliche Kompensationen oder durch manuelle Nachbearbeitung); das System arbeitet an den betroffenen Stellen mit eingeschränkter Funktionalität. Die Akzeptanz des Systems hängt somit wesentlich von den Handlungsalternativen des Geschäftsfeldes ab. Da das System mit nur zum Teil fertiggestellten Aufträgen arbeiten muss, entstehen erkennbare Zwischenzustände. Es bedarf passender Maßnahmen, wenn solche Zwischenzustände für den Nutzer nicht sichtbar sein sollen. Erst die Nachbearbeitung der teilfertigen Aufträge sorgt dafür, dass eine Systemkonvergenz in den vom Nutzer erwarteten Zielzustand erreicht wird. Damit haben wir einen Paradigmen-Wechsel, da der Nutzer auf mögliche Ergebnisse warten muss – verursacht durch mehr zu berücksichtigende Zwischenzustände als in einem gesamt-transaktionalen Alles-oder-Nichts-Ablauf.
Die im Verhältnis zum Monolithen hohen Latenzzeiten sind ein inhärentes Problem verteilter Systeme. Dem kann ein Microservice nur durch das Caching und die asynchrone Aktualisierung externer Daten begegnen. Das System muss somit bestmöglich in der Lage sein, auch veraltete Daten zu verarbeiten. Außerdem können die erhöhten Latenzzeiten zur Ablaufparallelisierung zwingen und damit weitere Fehlerquellen öffnen.
Wenn Nacharbeiten und Datenaktualisierung zur Regel werden, löst sich das gut verstandene synchrone Request/Response-Schema auf. Stattdessen entsteht ein asynchrones, ereignisgesteuertes System mit vielen kleinen Akteuren, das zunächst ein Umdenken erfordert [10]. Die Korrektheit solch eines Systems ist nur schwer sicherzustellen, eine vollständige Kontrolle unmöglich.
Und Test, Dokumentation, Monitoring, Fehlersuche und -behebung sind in derart entkoppelten Systemen eine eigene Herausforderung: Jeder dieser Aspekte wäre einen eigenen Artikel wert.
Lesen Sie jetzt Teil 5 der Artikelserie:
- J. Evans (Netflix); 2016: Mastering Chaos - A Netflix Guide to Microservices (QCon 2016) (02:20)
- Wikipedia: Fallacies of Distributed Computing
- a) S. Newman; 2015: Principles Of Microservices (34:50)
b) S. Newman; 2015: Building Microservices: Designing Fine-Grained Systems, Kap. 11 - R. Meshenberg (Netflix); 2016: Microservices at Netflix Scale: Principles, Tradeoffs & Lessons Learned (GOTO 2016) (10:43)
J. Evans (Netflix); 2014: Embracing Failure: Fault Injection and Service Resilience at Netflix (NANOG 61) (07:30) - M. Fowler; 2011: CQRS
- Dr. G. Starke; 2018: Hände waschen, Zähne putzen: (manchmal) vergessene Grundlagen von Software-Engineering, (Java Forum Stuttgart 2018)
DZone.com: The Scale of Computer Latencies - J. Bogard; 2016: Avoiding Microservice Megadisaster (Øredev Conference)
- M. Fowler; 2017: The Many Meanings of Event-Driven Architecture (GOTO 2017) (21:00)
- S. Newman; 2014: The Practical Implications Of Microservices (GeeCON 2014) (08:55)
- T. Flohre; 2015: Wer Microservices richtig macht, braucht keine Workflow Engine und kein BPMN