Business Technology 03/11

Cloud Computing: Umdenken für Architekten in der Cloud

Autoren: ,

In den letzten zehn bis fünfzehn Jahren haben sich eine Reihe von Architekturparadigmen etabliert, die heute die Grundlage für Unternehmensanwendungen definieren und in vielfältigen Standards, Frameworks und Best Practices so fest verankert sind, dass man kaum noch darüber nachdenkt. Wendet man diese Paradigmen unreflektiert auf Cloud-Anwendungen an, führt das in der Regel zu ernüchternden Resultaten. Insbesondere die für Cloud Computing wichtigen Eigenschaften Skalierbarkeit, Elastizität und Robustheit sind auf diese Weise nicht erreichbar. Wir müssen also umdenken, um die Potenziale der Cloud freizusetzen. Dieser Artikel betrachtet einige der veränderten Konzepte und Herangehensweisen an Cloud-Architekturen.

Cloud Computing ist zurzeit en vogue. Die Analysten prophezeien ein gigantisches Wachstum. Es gibt kaum ein Produkthersteller oder Beratungshaus, das sich nicht mit dem Begriff schmückt, und immer mehr Anwender beginnen, das Thema für sich zu entdecken. Es ist aber nicht nur ein Hype, es gibt ganz reale Gründe dafür.

Treiber für Cloud Computing

Da ist zum einen die Elastizität. Es ist für Unternehmen sehr schwer, Peak-Lasten vorherzusehen und hinreichende Ressourcen dafür vorzuhalten. Zum einen sind Peak-Lasten sowohl vom Zeitpunkt als auch vom Volumen her sehr schwer vorherzusagen. Dazu kommen noch saisonale Schwankungen wie das Weihnachtsgeschäft bei vielen Unternehmen. Will man für diese Lastspitzen gerüstet sein, muss man sehr große Reserven in seinen Rechenzentren vorhalten, die fast die ganze Zeit brachliegen. Ein weiterer Punkt ist das Thema Big Data. Die Datenmengen wachsen in Größenordnungen, die nicht mehr in einem Rechnerknoten verarbeitet werden können. Auch klassische Clustertechnologien, wie sie die meisten Datenbankanbieter im Programm haben, stoßen dabei an ihre Grenzen. Man muss nicht gleich Google oder Facebook sein, um mit solchen Datenmengen konfrontiert zu werden. Allein schon die Menge an Informationen, die man heute im Kontext der interaktiven Kommunikation mit seinen Kunden und Geschäftspartnern z. B. via sozialen Netzwerken aufbauen kann, lassen die Datenmengen explodieren.

Die Grenzen der vorherrschenden Architekturen

In solchen Umgebungen stoßen klassische Softwarearchitekturen für Unternehmensanwendungen an ihre Grenzen. Sie sind für eher fest umrissene Last- und Datenvolumenszenarien entworfen. Diese Architekturen zeichnen sich durch den strikten Einsatz von Transaktionen und enge Replikation aus, um maximale Datenkonsistenz zu jedem Zeitpunkt zu garantieren. Sie werden durch viele Produkte, Frameworks und Bibliotheken unterstützt, die die zugrunde liegenden Prinzipien implementieren. Das ist in den letzten zehn bis fünfzehn Jahren so weit perfektioniert worden, dass diese Prinzipien als Standard für jedwede Anwendungsentwicklung gesehen werden. Stößt man aber auf Anforderungen für nahezu unbegrenzte Elastizität und Datenvolumina, dann tragen diese Prinzipien nicht mehr, weil sie diese Anforderungen nicht unterstützen. Zusätzlich tut sich an der Stelle das Thema Wirtschaftlichkeit als weiterer Treiber für Cloud-Architekturen auf. Die meisten klassischen Lösungen haben die Eigenschaft, dass ihre Kosten mit steigender Rechenleistung und steigendem Datenvolumen überproportional ansteigen, insbesondere wenn eine hohe Verfügbarkeit gefordert ist. Dieses Problem müssen Cloud-Architekturen ebenfalls lösen. Es muss sichergestellt werden, dass die Kosten maximal linear mit der Systemskalierung ansteigen, besser sogar unterproportional. Umdenken ist also angesagt. Viele der „liebgewordenen“ Konzepte für das Design von Unternehmensanwendungen aus den letzten zehn bis fünfzehn Jahren sind nicht auf Cloud-Szenarien übertragbar. Eine infrastrukturagnostische Entwicklung scheint nicht mehr durchgängig möglich zu sein, die Illusion der lokalen Single-Thread-Entwicklung trägt nicht mehr, die engen Transaktionen und die vielen Produkte und Frameworks reichen nicht mehr aus. Ein Anpassen der Architekturen und des Designs auf die veränderten Anforderungen ist notwendig. Aber wie sehen die neuen Konzepte denn nun aus?

 Architektur und Design für Cloud Computing

Es würde den Rahmen dieses Artikels sprengen, wenn wir versuchten, eine umfassende Abhandlung über Architektur und Design von Cloud-Anwendungen zu schreiben. Deshalb beschränken wir uns hier darauf, einige wichtige Designprinzipien vorzustellen und zu erläutern.

Abbildung 1 gibt einen Überblick über die zuvor beschriebenen Treiber von Cloud-Anwendungen (in Rot dargestellt) und deren Bezug zu den im Folgenden näher beschriebenen Designprinzipien. Wie man in der Abbildung sieht, sind die ganzen Designprinzipien eng miteinander verwoben. Es ist also für den Entwurf einer Cloud-Architektur nicht sinnvoll, diese Prinzipien isoliert voneinander zu betrachten, sondern man muss das gegenseitige Beeinflussen und die Wechselwirkungen stets im Blick behalten.

Verfügbarkeit

Hat man in normalen Unternehmensanwendungen hohe Verfügbarkeit als Anforderung, so greift man üblicherweise auf die Cluster- und Hochverfügbarkeitsangebote (HA, High Availability) der Hardware- und Softwareinfrastrukturanbieter zurück. Das Problem solcher Lösungen besteht darin, dass sie zum einen nicht beliebig skalieren und zum anderen die Kosten für diese Lösungen mit der Skalierung überproportional ansteigen. Das geht soweit, dass solche Lösungen jenseits einer bestimmten Skalierungsgrenze in der Regel nicht mehr wirtschaftlich vertretbar sind. Damit fallen diese Lösungen für nahezu beliebig skalierbare Anwendungen aus. Stattdessen setzt man in Cloud-Umgebungen in der Regel auf einfache Standardhardware und auf Redundanz zur Sicherstellung der Verfügbarkeit. Das bedeutet im Gegenzug aber auch, dass man aus Anwendungssicht immer damit rechnen muss, dass eine Systemkomponente im nächsten Moment nicht mehr verfügbar ist. Ausfälle werden damit nicht mehr transparent für die Anwendungslogik behandelt, sondern müssen explizit beim Design der Anwendung betrachtet werden. Einige der folgenden Konzepte zeigen weiterführende Ideen auf, wie man so etwas umsetzen kann.

Transaktionen

Einer der wahrscheinlich am heftigsten diskutierten Aspekte sehr großer Anwendungen ist das Problem, dass man die unbedingte Datenkonsistenz zu jedem Zeitpunkt aufgeben muss, wie man sie von den bekannten RDBMS-Systemen kennt. Die Annahme des klassischen Konsistenzmodells auf der Basis von ACID-Transaktionen [1] erleichtert bekanntlich nicht nur die Entwicklung, sondern hat auch eine beruhigende Wirkung auf Kunden. Unter der Anforderung einer beliebigen Skalierbarkeit ergibt sich aber aus dem so genannten CAP-Theorem [2], dass die Konsistenz nicht uneingeschränkt aufrechterhalten werden kann. Gemäß CAP-Theorem kann ein System zum verteilten Rechnen nicht gleichzeitig die Anforderungen Konsistenz (Consistency), Verfügbarkeit (Availability) und Toleranz gegenüber partiellen Systemausfällen (Partition Tolerance) erfüllen, sondern nur maximal zwei von ihnen. Klassische RDBMS haben immer die Aspekte Konsistenz und Verfügbarkeit fokussiert. Hohe Verfügbarkeit wird natürlich auch in der Cloud erwartet. Da aber in einer Cloud-Infrastruktur immer mit Ausfällen von Knoten zu rechnen ist, ohne dass deswegen die ganze Applikation still stehen darf (Partition Tolerance), bleibt nichts anderes übrig, als die vollständige Datenkonsistenz aufzugeben. Das bedeutet jetzt aber nicht, dass beliebiger Datenmüll entsteht, sondern dass zugunsten der Verfügbarkeit darauf verzichtet wird, immer sofort die Konsistenz der Daten zu garantieren. Stattdessen wird in Kauf genommen, dass die Daten irgendwann konsistent (eventually consistent) sind. Brewster nannte diesen veränderten Konsistenzbegriff BASE (Basically Available, Soft State, Eventually Consistent) [2]. Für das Design von Cloud-Anwendungen bedeutet das insbesondere, dass man eine Robustheit gegenüber eventuellen Dateninkonsistenzen vorsehen muss, da es passieren kann, dass gelesene Werte nicht den aktuellen Stand wiederspiegeln oder dass eine Entität einen anderen Zustand darstellt als eine zweite bezogene Entität. Eine Pauschalregel für die Reaktion auf solche temporären Zustandsinkonsistenzen gibt es leider nicht, da sie immer aus anwendungsfachlicher Sicht definiert werden muss. Man sollte aber auf keinen Fall Mechanismen in die Anwendung einbauen, die sich blind auf die Konsistenz der verarbeiteten Daten verlassen.

Treiber in der Cloud

Entitäten

In sehr großen Systemen ist es ab einer bestimmten Datenmenge nicht mehr möglich, alle Daten in einer Lokation, d. h. in einer Datenbank zu speichern. Stattdessen muss man die Daten über eine Menge von Speicherorten verteilen. Da verteilte Transaktionen nicht beliebig skalierbar sind (zumindest wenn man die Systemverfügbarkeit hoch halten will), muss man zu einer weitestgehend unabhängigen Verteilung und Speicherung der Daten übergehen. Pat Helland empfiehlt, die Daten in Form von Entitäten zu organisieren, die man unabhängig verteilen kann [3]. Typischerweise sollte man dabei darauf achten, dass eine Entität nicht zu groß ist, um in einer Datenbank gespeichert zu werden. Dann kann man davon ausgehen, dass sich eine einzelne Entität jederzeit in einem für sich konsistenten Zustand befindet, was die Entwicklung erleichtert. Eine Entität sollte man in diesem Kontext auch nicht mit klassischen Datenbankentitäten gleichsetzen. In diesem Zusammenhang versteht man unter einer Entität typischerweise einen Ordnungsbegriff inklusive aller zugeordneten Daten. Im Sinne klassischer relationaler Datenbanksysteme kann eine solche Entität aus einer Top-Level-Entität und einem ganzen Baum zugehöriger beziehungsweise abhängiger Entitäten bestehen.

Shared Nothing

Klassische Clusterarchitekturen, in denen der Zustand (Daten) über den gesamten Cluster repliziert wird, skalieren nur bis zu einem gewissen Punkt. Jenseits dieses Punkts wird der Kommunikationsaufwand für die Replikation so groß, dass die Verfügbarkeit der Anwendung nicht mehr gewährleistet werden kann. Um nahezu beliebig skalierbare Anwendungen zu entwickeln, muss man stattdessen auf so genannte Shared-Nothing-Architekturen setzen. Den Begriff gibt es auf verschiedenen Architekturebenen von der Hardware bis zur Anwendung. Allgemein bedeutet „Shared Nothing“, dass die Komponenten auf der bezogenen Ebene keinerlei Zustand miteinander teilen. Damit kann man das Replikationsproblem effizient lösen. Kommunikation zwischen den Komponenten findet nur in Form von Nachrichten statt.

Lose Kopplung

Lose Kopplung ist eine Voraussetzung für die zuvor beschriebenen Shared-Nothing-Systeme. Wir werden nur in der Lage sein, eine Anwendung nahezu beliebig zu skalieren, wenn ihre Bausteine sowohl auf fachlicher als auch auf technischer Ebene möglichst unabhängig voneinander sind. Auf fachlicher Ebene haben wir das im Zusammenhang mit den Entitäten zuvor schon betrachtet. Man kann (und muss) das natürlich auch noch bis auf die Ebene der Anwendungslogik weitertreiben. Je besser ich einen Block von Anwendungslogik von dem Rest der Logik abkoppeln kann, desto leichter kann ich diesen Block bedarfsgerecht skalieren. Außerdem hilft einem die Entkopplung der Bausteine, die Anwendung robuster zu gestalten und damit die Verfügbarkeit zu erhöhen [4]. Auch auf technischer Ebene muss lose Kopplung umgesetzt werden. Das bedeutet in Verbindung mit dem Shared-Nothing-Gedanken, dass die einzelnen Blöcke nur noch über Nachrichten miteinander kommunizieren. Das hat weitreichende Folgen für die Softwareentwicklung. Tatsächlich stellt die nachrichtenbasierte asynchrone Kommunikation einen Paradigmenwechsel gegenüber der klassischen Call-Stack-basierten Softwareentwicklung dar. Das Problem für die Entwicklung besteht darin, dass einerseits dieses Paradigma weniger bekannt ist, sodass die Entwicklungszeiten steigen können. Andererseits ist das Verständnis des Verhaltens von unabhängigen, über Nachrichten kommunizierenden Einheiten für Menschen ziemlich schwierig. Damit müssen wir zusätzliche Überwachungsmechanismen und Sicherungsmechanismen vor dem Bearbeiten von Nachrichten vorsehen.

Parallelisierbarkeit

Mit den zuvor beschriebenen Prinzipien geht die verstärkte Parallelisierbarkeit der Anwendung beziehungsweise von Anwendungsteilen einher. Gemäß dem Amdahlschen Gesetz [5] ist der Geschwindigkeitszuwachs bei der parallelen Bearbeitung von Aufgaben primär durch die Menge der sequenziellen, nicht parallelisierbaren Anteile eines Programms beschränkt. Je mehr nicht parallelisierbare Anteile ein Programm enthält, desto geringer ist der Geschwindigkeitsgewinn bei einer Parallelisierung der Ausführung. Damit ist klar, dass man beim Design einer Anwendung darauf achten muss, die nicht parallelisierbaren Anteile einer Cloud-Anwendung möglichst gering zu halten. Nicht parallelisierbar sind dabei die Teile einer Anwendung, die Seiteneffekte haben. Dazu zählen insbesondere ein zwischen mehreren Ausführungs-Threads gemeinsam genutzter Zustand (Shared State) sowie Zugriffe auf externe Ressourcen. Das Problem besteht hier darin, dass Zugriffe darauf zur Vermeidung von Inkonsistenzen in kritischen Abschnitten gesichert werden müssen. Das bedeutet aber nichts anderes als eine Zwangssequenzialisierung. In dieser Situation sind Seiteneffekte möglichst zu vermeiden oder, wo unumgänglich, explizit zu machen. Dabei können funktionale Programmiersprachen helfen, da diese im Prinzip exakt so mit Seiteneffekten umgehen.

Zustand

Im Sinne des zuvor beschriebenen Shared-Nothing-Prinzips sollte man möglichst auf zwischen parallel ausgeführten Bausteinen geteilten Zustand verzichten. Aber auch darüber hinaus ist es im Sinne der Verfügbarkeit wichtig, möglichst wenig Zustand eines Bausteins im Hauptspeicher zu verwalten. Der Gedanke dahinter ist, dass man jederzeit damit rechnen muss, dass die eingesetzte Hardware ausfällt, da man zur wirtschaftlichen Realisierung der Elastizität ja typischerweise einfache Standardhardware einsetzt und keine speziellen HASysteme. Bei Ausfall eines Knotens möchte man möglichst keine Daten verlieren. Andererseits bedeutet die Persistierung des Zustands eine Verlangsamung der Verarbeitungsgeschwindigkeit gegenüber der Variante, bei der die Daten im Hauptspeicher gehalten werden. Hier muss man je Komponente einen sinnvollen Kompromiss zwischen den Anforderungen Datensicherheit und Verarbeitungsgeschwindigkeit finden, bei dem in manchen Fällen der Einsatz verteilter Caches eine Lösung sein kann.

Fehlerbehandlung

Eine hohe Verfügbarkeit ist ein wichtiger Aspekt typischer Cloud-Anwendungen. Damit erhält auch die Fehlerbehandlung einen ganz anderen Stellenwert. Tatsächlich ist aus mehreren Gründen in jedem Fall eine explizite Fehlerbehandlungsstrategie erforderlich. Zunächst kann man aufgrund der komplexen Interaktion der lose gekoppelten Komponenten nicht mehr den einzelnen Entwicklern freie Hand lassen, wie sie auftretende Fehler behandeln möchten, da sie die Folgen für andere Komponenten in der Regel nicht abschätzen können. Aus diesem Grunde verbietet es sich auch, die Fehlerbehandlung dem Container oder Framework zu überlassen. Die Applikation zu beenden oder den Fehler zu ignorieren, ist bei der Anforderung an die Verfügbarkeit ohnehin keine Option. Die speziell zu wählende Strategie ist natürlich anwendungsabhängig. Es gibt aber durchaus allgemeine Ansätze. Eine Möglichkeit besteht darin, Teilaufgaben in Worker und Supervisor zu zerlegen, wobei der Worker die Teilaufgabe erfolgreich erledigt oder im Fehlerfall einfach stirbt („Let it crash“). In letztem Fall kümmert sich der in einem eigenen Thread laufende Supervisor um die Fehlerbehandlung. Dieses Verfahren ist ein gängiges Muster in Erlang [6]. Das ist natürlich nicht die einzig mögliche Fehlerbehandlungsstrategie für hochverfügbare Systeme. So beschreibt z. B. R. Hanmer verschiedene Techniken zur Erkennung und Behandlung von Fehlern [4]. Aber für welche Strategie auch immer Sie sich entscheiden, der Umgang mit Fehlern hat in hochverfügbaren Cloud-Anwendungen einen ganz anderen Stellenwert als man es von klassischen Unternehmensanwendungen gewohnt ist.

Automatisierung

Automatisierung ist eine Voraussetzung, um elastische Anwendungen bereitstellen zu können. Ohne ein automatisiertes Deployment und einen automatisierten Start der verschiedenen Anwendungsteile ist es schlicht nicht möglich, eine Anwendung in kürzester Zeit, d. h. binnen Minuten, zu skalieren. Das beginnt mit einem automatisierten Aufspielen von System-Images auf die typischerweise virtualisierten Instanzen und geht über einen automatisierten Start des Systems bis hin zur Sicherstellung eines konsistenten Ausgangszustands nach dem Start. Dabei ist es notwendig, dass ein entsprechendes Monitorprogramm beim Erkennen einer erhöhten Systemlast selbstständig eine neue Instanz des benötigten Anwendungsteils aufsetzen und starten kann. Bei der Auswahl der Virtualisierungsumgebung muss man darauf achten, dass solche Automatisierungen prinzipiell möglich sind. Außerdem muss man sich beim Design der Anwendung auch darum kümmern, dass diese Lastüberwachungs- und Skalierungsthemen berücksichtigt und realisiert sind, denn out of the Box bekommt man sie bislang noch nicht in der für wirklich elastische Anwendungen benötigten Form.

Weitere Aspekte

Es gibt noch diverse weitere Prinzipien, die hier nicht beschrieben worden sind, die man aber auch beim Design von Cloud-Anwendungen berücksichtigen muss. So muss man sich z. B. Gedanken über das Konfigurationsmanagement machen oder auch über Mandantenfähigkeit [7]. Sicherheit ist ein weiteres Thema, das man beim Design von Cloud-Anwendungen unbedingt betrachten muss. Das Thema ist allerdings so umfangreich, dass es diesen Artikel sprengen würde. Deshalb belassen wir es hier bei dem Hinweis, dass man dieses Thema unbedingt beachten muss, da es häufig auch direkten Einfluss auf das resultierende Anwendungsdesign hat. Es gibt mittlerweile umfangreiche Literatur, aus der man weitere Empfehlungen für das Design elastischer und hochverfügbarer Cloud-Anwendungen ziehen kann. Eine recht gute und kompakte Einführung hat z.B. J. Varia gegeben [8], [9].

Fazit und Ausblick

Zusammenfassend lässt sich festhalten, dass viele der in den vergangenen Jahren als „selbstverständlich“ erachteten Architektur- und Designprinzipien für Unternehmensanwendungen im Zusammenhang mit Cloud-Anwendungen überdacht werden müssen, da bei Cloud-Anwendungen gegenüber klassischen Unternehmensanwendungen oftmals stark veränderte, nicht funktionale Anforderungen gelten. Entsprechend kann man viele Aufgaben im Cloud-Umfeld nicht mehr in der gewohnten Form auf die Infrastrukturkomponenten und Frameworks abwälzen. Natürlich sind die Hersteller mittlerweile auf den Cloud-Hype aufgesprungen und versprechen volle Cloud-Fähigkeit ihrer Produkte. Da muss man aber derzeit noch genau hinschauen. Viele der Angebote sind geeignet, Cloud-Anwendungen bis zu einer gewissen Größenordnung gut zu unterstützen. Wenn es allerdings in Richtung nahezu beliebiger Skalierbarkeit geht, dann stoßen praktisch alle Produktlösungen an ihre Grenzen. Da bleibt oft nichts anderes übrig, als die Anwendung selbst zu entwerfen und umzusetzen. Wir sind davon überzeugt, dass Cloud-Anwendungen die Architektur und das Design von Anwendungen in den kommenden Jahren nachhaltig prägen und verändern werden. Aktuell stehen wir zumindest im Zusammenhang mit echt elastischen und hochverfügbaren Cloud-Anwendungen noch recht nah am Anfang, und es wird spannend sein zu beobachten (oder auch mitzugestalten), wie sich das Thema in den nächsten Jahren weiterentwickeln wird.

Links & Literatur

[1] Wikipedia zu ACID: http://de.wikipedia.org/wiki/ACID

[2] Brewster, Eric A.: „Towards Robust Distributed Systems“, Keynote PODC 2000: http://www.cs.berkeley.edu/~brewer/ cs262b-2004/PODC-keynote.pdf

[3] Helland, Pat: „Life beyond Distributed Transactions, 3rd Conference on Innovative DataSystems Research (CIDR)“, 2007

[4] Hanmer, Robert. S.: „Patterns for Fault Tolerant Software“, Wiley 2007

[5] Wikipedia zu Amdahlsches Gesetz: http://de.wikipedia.org/ wiki/Amdahlsches_Gesetz

[6] Armstrong, Joe: „Making reliable distributed systems in the presence of software errors“, Dissertation, 2003

[7] Apache ZooKeeper: http://zookeeper.apache.org/

[8] Varia, Jinesh: „Architecting for the Cloud: Best Practices“, Amazon Web Services 2010

[9] Varia, Jinesh: „Cloud Architectures“, Amazon Web Services 2008

Vollständiger Artikel