Wir haben uns vom Monolithen befreit

Und wie wir uns mit Microservices entwickelt haben

(19. Juli 2018)

Ein Monolith-Regelcode-Affe

Bei Conio begann alles mit dem, was allgemein als „Monolith“ bezeichnet wird. Das heißt, eine einzelne Codebasis, die alles von der gesamten Anwendung enthält: Frontend-Komponenten, Backend-Komponenten, API-Services, Hintergrundaufgaben; Hölle, entwickelt sogar Skripte. Und es hat am Anfang sehr gut funktioniert. Nur ein paar Softwareentwickler, die an separaten Bereichen der Codebasis arbeiten (daher nur sehr geringe Chancen für Codeänderungskonflikte), sind einfach bereitzustellen. Voller Fokus auf das Schreiben von Anwendungsfunktionen, ohne sich um viel anderes kümmern zu müssen. Wie sind wir mit dem Einsatz umgegangen? Da nur wenige Beta-Kunden über die fortschreitenden Fortschritte informiert waren, war es kein wirkliches Problem, Dienste für eine Weile herunterzufahren, die vollständige Codebasis einzuführen (unabhängig davon, wie klein oder groß die allgemeinen Änderungen waren und ob sie Datenbankmigrationen beinhalteten). und dann die Dienstleistungen wieder aufrufen.

Es war auf jeden Fall befriedigend zu sehen, wie ein Produkt von Grund auf Gestalt annahm und von den Endkunden Anerkennung erhielt. Wir wussten jedoch sehr gut, dass dieser Ansatz nicht zu einem modernen Fintech-Unternehmen passt.

Was dann?

Bei den meisten Softwareanwendungen sind Kunden sehr tolerant. Ja, WhatsApp funktioniert möglicherweise nicht mehr und hat einen Ausfall, der einige Stunden anhält: definitiv ein Ärgernis, aber kein wahrgenommenes Problem. Gleiches gilt für Pokemon Go oder Ihre Lieblingsspiel-App. Dies ist jedoch nicht der Fall, wenn es um Geld geht. : Die Stimmung ändert sich, wenn Sie sich nicht bei Ihrem Bankkonto anmelden können oder können Handelsgeschäfte machen. Dies ist bei Kryptowährungsanwendungen noch schlimmer: Die meisten Menschen erinnern sich an berüchtigte Fehler der Vergangenheit, und wenn sie auch nur für kurze Zeit nicht auf ihre Kryptowährungsgelder zugreifen können, entstehen Spekulationen. Das ist fair. Es ist Ihr Geld, und Sie sollten wenig oder gar keine Probleme haben, wenn Sie es verwenden möchten.

Der Monolith oben ist für ein solches Szenario nicht geeignet: Jede Änderung der Codebasis in der Produktion würde eine vollständige Bereitstellung erfordern. mit damit verbundenen Ausfallzeiten. Jeden Tag arbeiten wir daran, unsere Dienste zu verbessern, indem wir Fehler beheben, unsere Benutzeroberfläche noch benutzerfreundlicher gestalten, alte Funktionen entfernen und neue hinzufügen, die besser genutzt werden können. Wir veröffentlichen diese Updates häufig täglich, damit unsere Kunden sofort davon profitieren können, und wir bemühen uns, keinen Einfluss auf das Kundenerlebnis zu haben. Das heißt, welche Formel wir auch immer hinter den Kulissen entwickeln, sie muss für die Außenwelt unsichtbar sein (zumindest meistens). Also haben wir uns vom Monolithen entfernt und uns für die sogenannte „Microservices-Architektur“ entschieden.

Evolution durch Microservices

Die massive, eng geklebte einzelne Codebasis wird jetzt in kleinere Teile zerlegt, von denen jeder einen bestimmten Dienst darstellt. Bei jeder Ausführung kommunizieren Dienste synchron über das Standard-HTTP-Protokoll und asynchron über Warteschlangen (z. B. von RabbitMQ und Apache Kafka).

Interaktionen in einer Microservices-Architektur

Es ist ziemlich schwierig, den Monolithen in kleinere Komponenten zu zerlegen, aber es lohnt sich sehr der Aufwand. In militärischer Hinsicht ist es sehr ähnlich zu dem, was Julius Caesar getan hat, um das große Gebiet von Gallia stetig zu regieren: „Teilen und Erobern „.

1) Das Produkt kann kontinuierlich eingesetzt werden. Ein Code-Update gilt jetzt nur für einen Microservice: In den meisten Fällen kann es sofort für die Produktion bereitgestellt und ohne Auswirkungen auf den Kunden freigegeben werden.

2) Code ist einfacher zu verwalten. Aus Sicht der Unternehmensorganisation ändern sich die Dinge, wenn aus einem Team von 2 Software-Ingenieuren ein Team von 10 Software-Ingenieuren wird. Es ist effektiver und mit weniger Codekonflikten, wenn jedes Teammitglied für seinen eigenen Microservice verantwortlich ist.

3) Code ist einfacher zu pflegen. Eine Microservices-Architektur erfordert von Natur aus die Definition einer Schnittstelle für die Kommunikation mit der Außenwelt (sei es die Frontend-App oder ein anderer Backend-Dienst) und ist von allen anderen Gesichtspunkten vollständig isoliert. Dies ermöglicht es, einzelne Komponenten der Anwendung zu überprüfen, neu zu entwerfen oder sogar von Grund auf neu zu schreiben (auch in verschiedenen Sprachen, wenn dies zweckmäßig ist), ohne den Rest zu beeinträchtigen.

4) Die Leistung kann verbessert werden. Jeder Microservice kann jetzt seine am besten geeignete Sprache verwenden. Schwere kryptografische Berechnungskomponenten können beispielsweise in C optimiert werden, während API-Dienste in Python und lang laufende Aufgaben in Go ausgeführt werden.

5) Verbesserte Codeisolation und Sicherheit. Jeder Mikrodienst kann in einem eigenen Docker-Container ausgeführt werden, wodurch Berechtigungsisolierung, Netzwerk- und Datensegregation und, was für eine Wachstumsphase von größter Bedeutung ist, ein enormes Skalierbarkeitspotenzial bereitgestellt werden.

Sind Microservices dann die Antwort?

Natürlich gibt es kein kostenloses Mittagessen. Eine Microservices-Architektur bringt auch eine Reihe schwieriger Herausforderungen mit sich:

1) Komplexität des Betriebs. DevOps-Ingenieure sind auf jeden Fall erforderlich, um die Feinheiten des neuen Bereitstellungsprozesses zu vereinfachen.

2) Hardware aufblähen. Microservices werden häufig in Docker-Containern ausgeführt. Sobald sich die Anzahl der Mikrodienste erhöht, wird es immer schwieriger, die gesamte Anwendung auf derselben Hardware wie zuvor auszuführen.

3) Kommunikationsaufwand: Jede Anforderung muss möglicherweise mit einem oder mehreren anderen interagieren Microservices über das Netzwerk. Dies kann zu einer erhöhten Latenz führen und zu vorübergehenden Fehlern führen. Um ausfallsichere Dienste zu implementieren und die Skalierbarkeit des gesamten Systems zu verbessern, müssen Interaktionen auf asynchrones Messaging verschoben werden (z. B. unter Verwendung von Apache Kafka und / oder RabbitMQ).

4) Eventuelle Konsistenz. Dies ist wahrscheinlich die schwierigste Herausforderung einer Microservices-Architektur. Mit einem einzelnen Microservice können RDBMS-Transaktionen innerhalb ihrer Grenzen erstellt werden . Leider besteht ein häufiges Problem bei verteilten Architekturen darin, mehrere Transaktionen zu verarbeiten, die nicht innerhalb derselben Grenzen liegen. Infolgedessen befindet sich das System möglicherweise in einem illegalen und nicht wiederherstellbaren Zustand. Um solche Probleme abzumildern, verfolgt Conio verschiedene Strategien:

  1. Zerlegen Sie die übergeordneten Domänen gemäß den Praktiken des domänengesteuerten Designs in Subdomänen und beschränken Sie sie auf individuell begrenzte Kontexte ; Jeder begrenzte Kontext wird als Mikrodienst implementiert, bei dem Transaktionsgrenzen angewendet werden. Dies behebt die Möglichkeit von Inkonsistenzen für bestimmte Subdomänen.
  2. Implementieren Sie idempotente asynchrone Interaktionen, die früher oder später Inkonsistenzen beheben.
  3. Vermeiden Sie nach Möglichkeit alle Aktionen, die mehrere Subdomänen betreffen könnten.

5) Komplexe Berichterstattung. Da sich jede Subdomäne in einem bestimmten begrenzten Kontext befindet, müssen bei komplexen Berichten, an denen mehrere Subdomänen beteiligt sind, möglicherweise Daten aus mehreren Datenquellen abgefragt werden. Dies kann sich sowohl auf die Ausdruckskraft der Domänen als auch auf die Skalierbarkeit des Systems negativ auswirken. Hier in Conio haben wir eine CQRS-Architektur übernommen, um Backoffice-Aktivitäten und Geschäftsanalyseberichte zu unterstützen.

6 ) Protokollierungssystem. Jedes Element in einem verteilten System trägt zur Erstellung des Protokolls des gesamten Systems bei. Es ist jedoch erforderlich, Tools zu implementieren, mit denen die erforderlichen Verbindungen zwischen all diesen getrennten Protokollen hergestellt werden können, um für jede Interaktion ein einheitliches Protokoll zu erstellen. Hier in Conio verwenden wir den ELK-Stapel (ElasticSearch, Logstash, Kibana) zum Speichern und Abfragen von Protokolldaten: Jedes Protokoll wird mit den erforderlichen Korrelations-IDs angereichert, die das oben erwähnte einheitliche Protokoll ermöglichen.

Stoppen Sie niemals die Evolution

Unsere Einstellung? Das Zerlegen der anfänglichen einzelnen Codebasis muss als langfristige Aufgabe mit ständigen Verbesserungen angesehen werden. Bei Conio haben wir ein paar Jahre gebraucht, in denen wir Schritt für Schritt von einer massiven Codebasis auf über 100

= div <= div = "128be15f89">

Microservices umgestiegen sind. Wir sind an einem Punkt angekommen, an dem wir stolz auf die Ergebnisse sind, aber gleichzeitig erforschen wir weiter. Es gibt mehrere mögliche neue Optimierungen: Wechsel von Docker Swarm zu Kubernetes? Backend-für-Frontend-Dienste auf serverlose Lambda-Funktionen migrieren? Wechseln zu einem vollständigen kontinuierlichen Bereitstellungsvorgang? Die Möglichkeiten sind endlos.

Wir haben hier eine Reihe von Themen und Technologien angesprochen. In den nächsten Artikeln werden wir weitere Details zu unseren Erkenntnissen und Fortschritten veröffentlichen. Wenn Sie möchten, können Sie dies gerne kommentieren und uns Ihre Erfahrungen mitteilen.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.