Die Kosten von Microservices | Roberto Vitillos Blog

22. November 2020

Eine Anwendung beginnt ihr Leben typischerweise als Monolith. Nehmen wir zum Beispiel ein modernes Backend einer Single-Page-Javascript-Anwendung – es beginnt als einzelner zustandsloser Webdienst, der eine RESTful-HTTP-API bereitstellt und eine relationale Datenbank als Sicherungsspeicher verwendet. Der Dienst besteht aus einer Reihe von Komponenten oder Bibliotheken, die unterschiedliche Geschäftsfunktionen implementieren:

Da die Anzahl der Feature-Teams, die zur gleichen Codebasis beitragen, zunimmt, werden ihre Komponenten im Laufe der Zeit zunehmend gekoppelt. Dies führt dazu, dass sich die Teams immer häufiger gegenseitig auf die Füße treten, was ihre Produktivität verringert.

Die Codebasis wird so komplex, dass niemand jeden Teil davon vollständig versteht, und die Implementierung neuer Funktionen oder die Behebung von Fehlern wird zeitaufwändig. Selbst wenn das Backend in verschiedene Bibliotheken unterteilt ist, die verschiedenen Teams gehören, erfordert eine Änderung an einer Bibliothek eine erneute Bereitstellung des Dienstes. Und wenn durch eine Änderung ein Fehler entsteht, beispielsweise ein Speicherverlust, kann möglicherweise der gesamte Dienst davon betroffen sein. Darüber hinaus wirkt sich das Zurücksetzen eines fehlerhaften Builds auf die Geschwindigkeit aller Teams aus, nicht nur auf dasjenige, das den Fehler verursacht hat.

Eine Möglichkeit, die Wachstumsschmerzen eines zu lindern monolithisch Backend besteht darin, es in eine Reihe unabhängig einsetzbarer Dienste aufzuteilen, die über APIs kommunizieren. Die APIs entkoppeln die Dienste voneinander, indem sie Grenzen schaffen, die im Gegensatz zu denen zwischen Komponenten, die im selben Prozess ausgeführt werden, schwer zu verletzen sind:


Mikrodienste

Dieser Architekturstil wird auch als Microservice-Architektur bezeichnet. Der Begriff Mikro kann jedoch irreführend sein – die Dienste müssen nicht unbedingt mikrotechnisch sein. Tatsächlich würde ich argumentieren, dass ein Dienst, der nicht viel bewirkt, lediglich mehr betriebliche Belastungen verursacht als Vorteile bringt. Ein passenderer Name für diese Architektur ist serviceorientierte Architektur, aber leider bringt dieser Name auch etwas Altlasten mit sich. Vielleicht werden wir in 10 Jahren das gleiche Konzept noch einmal anders benennen, aber vorerst müssen wir bei Microservices bleiben.

Durch die Aufteilung des Backends nach Geschäftsfunktionen in eine Reihe von Diensten mit klar definierten Grenzen kann jeder Dienst von einem einzigen kleinen Team entwickelt und betrieben werden. Die reduzierte Teamgröße erhöht die Entwicklungsgeschwindigkeit der Anwendung aus verschiedenen Gründen:

  • Kleinere Teams sind effektiver, da der Kommunikationsaufwand quadratisch mit der Teamgröße wächst.
  • Da jedes Team seinen eigenen Release-Zeitplan vorgibt und die vollständige Kontrolle über seine Codebasis hat, ist weniger teamübergreifende Kommunikation erforderlich und Entscheidungen können daher in kürzerer Zeit getroffen werden.
  • Die Codebasis eines Dienstes ist kleiner und für seine Entwickler leichter zu verstehen, wodurch sich die Zeit verkürzt, die für die Einarbeitung neuer Mitarbeiter benötigt wird. Außerdem verlangsamt eine kleinere Codebasis die IDEs nicht, was die Entwickler produktiver macht.
  • Die Grenzen zwischen Diensten sind viel stärker als die Grenzen zwischen Komponenten im selben Prozess. Wenn ein Entwickler einen Teil des Backends ändern muss, muss er daher nur einen kleinen Teil des Ganzen verstehen.
  • Jeder Dienst kann unabhängig skaliert werden und je nach Bedarf einen anderen Technologie-Stack übernehmen. Den Konsumenten der APIs ist es schließlich egal, wie die Funktionalität implementiert wird. Dies erleichtert das Experimentieren und Evaluieren neuer Technologien, ohne andere Teile des Systems zu beeinträchtigen.
  • Jeder Microservice kann über ein eigenes unabhängiges Datenmodell und Datenspeicher verfügen, die am besten zu seinen Anwendungsfällen passen, sodass Entwickler sein Schema ändern können, ohne dass sich dies auf andere Services auswirkt.
Lesen Sie auch  SNCF: Die Aussicht auf einen massiven Weihnachtsstreik schwindet

Kosten

Die Microservices-Architektur fügt dem Gesamtsystem weitere bewegliche Teile hinzu, und das ist nicht umsonst. Die Kosten für die vollständige Einführung von Microservices lohnen sich nur dann, wenn sie über Dutzende von Entwicklungsteams amortisiert werden können.

Entwicklungserfahrung

Nichts verbietet die Verwendung verschiedener Sprachen, Bibliotheken und Datenspeicher für jeden Microservice – aber dadurch verwandelt sich die Anwendung in ein nicht wartbares Durcheinander. Beispielsweise wird es für einen Entwickler schwieriger, von einem Team zu einem anderen zu wechseln, wenn der Software-Stack völlig anders ist. Und denken Sie an die schiere Anzahl von Bibliotheken – eine für jede übernommene Sprache –, die unterstützt werden müssen, um gemeinsame Funktionen bereitzustellen, die alle Dienste benötigen, wie z. B. die Protokollierung.

Es ist daher nur vernünftig, dass ein gewisses Maß an Standardisierung erforderlich ist. Eine Möglichkeit, dies zu erreichen und gleichzeitig ein gewisses Maß an Freiheit zuzulassen, besteht darin, bestimmte Technologien locker zu fördern, indem den Teams, die sich an das empfohlene Portfolio an Sprachen und Technologien halten, ein großartiges Entwicklungserlebnis geboten wird.

Ressourcenbereitstellung

Um eine große Anzahl unabhängiger Dienste zu unterstützen, sollte es einfach sein, neue Server, Datenspeicher und andere Standardressourcen einzurichten – Sie möchten nicht, dass jedes Team seine eigene Vorgehensweise entwickelt. Und sobald diese Ressourcen bereitgestellt wurden, müssen sie konfiguriert werden. Um dies zu erreichen, ist ein gewisses Maß an Automatisierung erforderlich.

Kommunikation

Fernanrufe sind teuer und führen zu neuen und unterhaltsamen Möglichkeiten, wie Ihre Systeme zusammenbrechen können. Zum Schutz vor Ausfällen benötigen Sie Abwehrmechanismen wie Zeitüberschreitungen, Wiederholungsversuche und Leistungsschalter. Sie müssen außerdem Asynchronität und Stapelverarbeitung nutzen, um die Leistungseinbußen bei der Kommunikation über das Netzwerk abzumildern. All dies erhöht die Komplexität des Systems. Vieles, was ich in meinem Buch über verteilte Systeme beschreibe, dreht sich um den Umgang mit dieser Komplexität.

Lesen Sie auch  Japan wirft seine Kampfhubschrauber ab – ein weiteres Zeichen dafür, dass eine der wichtigsten modernen Militärwaffen ihren Glanz verliert

Abgesehen davon lebt selbst ein Monolith nicht isoliert, da auf ihn von Remote-Clients zugegriffen wird, und er nutzt wahrscheinlich auch APIs von Drittanbietern. Irgendwann müssen diese Probleme also auch dort gelöst werden, wenn auch in kleinerem Maßstab.

Kontinuierliche Integration, Bereitstellung und Bereitstellung

Durch die kontinuierliche Integration wird sichergestellt, dass Codeänderungen nach der Ausführung automatisierter Build- und Testprozesse in den Hauptzweig integriert werden. Sobald eine Codeänderung zusammengeführt wurde, sollte sie automatisch veröffentlicht und in einer produktionsähnlichen Umgebung bereitgestellt werden, in der eine Reihe von Integrations- und End-to-End-Tests ausgeführt werden, um sicherzustellen, dass der Microservice keinen von ihm abhängigen Dienst unterbricht .

Während das Testen einzelner Microservices nicht anspruchsvoller ist als das Testen eines Monolithen, ist das Testen der Integration aller Microservices um eine Größenordnung schwieriger. Wenn einzelne Dienste miteinander interagieren, kann es zu sehr subtilem und unerwartetem Verhalten kommen.

Operationen

Anders als bei einem Monolithen ist es viel teurer, jedes für einen Service verantwortliche Team mit einem eigenen Betriebsteam zu besetzen. Daher ist das Team, das einen Service entwickelt, in der Regel auch dafür bereit. Dies führt zu Spannungen zwischen der Entwicklungsarbeit und dem operativen Aufwand, da das Team bei jedem Sprint entscheiden muss, welche Prioritäten es priorisieren möchte.

Das Debuggen von Systemfehlern wird ebenfalls schwieriger – Sie können nicht einfach die gesamte Anwendung auf Ihren lokalen Computer laden und sie mit einem Debugger durchgehen. Das System weist mehr Möglichkeiten zum Ausfall auf, da es mehr bewegliche Teile gibt. Aus diesem Grund ist eine gute Protokollierung und Überwachung auf allen Ebenen von entscheidender Bedeutung.

Lesen Sie auch  „Lassen Sie die Kinder zu Hause“ – Corriere.it

Endgültige Konsistenz

Ein Nebeneffekt der Aufteilung einer Anwendung in separate Dienste besteht darin, dass sich das Datenmodell nicht mehr in einem einzigen Datenspeicher befindet. Die atomare Aktualisierung von Datensätzen, die in verschiedenen Datenspeichern gespeichert sind, und die Gewährleistung einer starken Konsistenz ist langsam, teuer und schwer richtig umzusetzen. Daher erfordert diese Art von Architektur normalerweise die Annahme einer letztendlichen Konsistenz.

Praktische Überlegungen

Die Aufteilung einer Anwendung in Dienste erhöht die Komplexität des Gesamtsystems erheblich. Aus diesem Grund ist es im Allgemeinen am besten, mit einem Monolithen zu beginnen und ihn nur dann aufzuteilen, wenn es einen guten Grund dafür gibt.

Es ist eine Herausforderung, die Grenzen zwischen den Diensten richtig zu ziehen – es ist viel einfacher, sie innerhalb eines Monolithen zu verschieben, bis man einen optimalen Punkt gefunden hat. Sobald der Monolith gut ausgereift ist und die Wachstumsschmerzen beginnen, können Sie damit beginnen, einen Mikroservice nach dem anderen davon abzulösen.

Sie sollten nur dann mit einem Microservice-First-Ansatz beginnen, wenn Sie bereits Erfahrung damit haben und entweder eine Plattform dafür aufgebaut haben oder den Zeitaufwand für den Aufbau einkalkuliert haben.


Geschrieben von Roberto Vitillo

Ich respektiere Ihre Privatsphäre. Abmelden ist jederzeit möglich.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.