Auf dem Weg vom Monolithen zum Microservice. Teil 2 / Sudo Null IT-News

Im vorherigen Artikel endete alles damit, dass wir einzelne Dienste aus einem einzigen Monolithen herausgegriffen haben.

Wir gestalten den Datenaustausch.

Jetzt ist es an der Zeit, nach Möglichkeiten der Interaktion zwischen Diensten zu suchen. Im Großen und Ganzen gibt es zwei davon, Direktanrufe und Nachrichtenwarteschlangen.

Und hier lohnt es sich, ein wenig innezuhalten – wenn Sie mit einer monolithischen Anwendung arbeiten, müssen Sie im Allgemeinen nicht über Dinge wie die Anforderungszeit oder die Anzahl der ausgehenden Anrufe nachdenken. Und oft muss man über eine Art asynchrone Verarbeitung nachdenken, wenn sich Probleme mit der Reaktionsgeschwindigkeit des Systems bemerkbar machen.

Beim Microservice-Ansatz müssen Sie sofort akzeptieren, dass die Zeit einer Anfrage groß genug ist. Akzeptieren Sie, dass jeder zusätzliche Aufruf eines anderen Dienstes die Gesamtantwortzeit erheblich verlängert und dass asynchrone Ereignisse die Hauptmethode für die Interaktion von Diensten sind. Was ändert es? Das Verständnis dieser Funktionen zwingt Sie dazu, die Art und Weise, wie Anfragen verarbeitet werden, zu überdenken. Bei einem einfachen Übergang von einem Monolith zu Microservices kann man leicht zu dem Schluss kommen, dass das neue System um ein Vielfaches langsamer werden kann. Und das alles, weil im Monolithen die Anfrage 3-5 oder mehr Aufrufe an den Code verschiedener Module durchlaufen und zurückkommen könnte. Innerhalb einer Anwendung kann dies Nanosekunden oder Mikrosekunden dauern, aber jeder Aufruf zwischen Microservices ist bereits ein Netzwerkaufruf, und hier kann die Zeit mehrere zehn Millisekunden betragen.
Daher sollte der Übergang zu Microservices immer von einer Überprüfung der Interaktionsprozesse mit dem System begleitet werden.

Versuchen wir, uns unser Beispiel anzusehen.

Sie können sehen, dass der Dienst zum Sammeln von Daten über den Verkäufer und das Geschäft miteinander verbunden sind. Aber es ist auch klar, dass diese Beziehung nicht starr ist. Das heißt, die Verarbeitung eines Teils ist möglicherweise nicht synchron mit dem anderen: In diesem Fall basiert die Geschäftsdatenmanipulation auf den Verkäuferdaten, verzögert jedoch nicht die Verarbeitung der Verkäuferdaten. Daher können wir bei der Registrierung eines Verkäufers sicher Nachrichten über die unternommenen Schritte senden. Wenn wir diese Nachrichten im Datenerfassungsdienst des Geschäfts lesen, können wir darauf reagieren: einen ersten Eintrag in die Datenbank vornehmen, einige Anfragen stellen, um die TIN des Verkäufers in anderen Systemen zu überprüfen, beispielsweise im Steuersystem. Wenn genügend Daten empfangen wurden, kann eine Nachricht an die Warteschlange gesendet werden, die anzeigt, dass die Daten gesammelt wurden und überprüft werden können. Und in diesem Fall kann der Datenüberprüfungsdienst, nachdem er ein solches Ereignis gelesen hat, unabhängig von jemandem den gewünschten Prozess starten. Außerdem sendet der Dienst beim Schritt der Vertragsunterzeichnung ein Ereignis über die ausgewählte Vertragsart und zusätzliche Daten, während die Antworten des Registrierungsdienstes von niemandem blockiert werden: Der Verkäufer geht schnell alle erforderlichen Schritte durch, der Dienst der die Vertragsunterzeichnung verarbeitet, die Ereignisse liest und seine Arbeit erledigt.

Wie Sie sehen können, vereinfacht dieser Ansatz die Entwicklung verschiedener Teile des Systems erheblich. Basierend auf Ereignissen wissen verschiedene Teams möglicherweise nicht einmal von der Existenz des anderen, entwickeln aber dennoch ihre eigenen Teile des Systems.
Und hier sieht man, wie Microservices dem Gesamtsystem einen Leistungsschub geben können.
Man sieht, dass die Glieder des ehemaligen Monolithen nicht mehr starr verbunden sind. Jeder Link (API der Registrierungsschritte, Datenerfassungsdienst, Vertragsverarbeitungsdienst) kann jetzt seine Arbeit sicher mit der Geschwindigkeit ausführen, zu der er in der Lage ist: die erforderlichen Prüfungen durchführen, verarbeiten, im Allgemeinen einen eigenen Freigabezyklus haben. Und vor allem, je nach Belastung, erhöhen Sie die Anzahl der Anwendungsinstanzen.
Hier lohnt es sich natürlich, den Kopf etwas zu kühlen – Engpässe kann es in jedem Fall immer geben. Zum Beispiel die Arbeit mit einer Datenbank: ob es eine große und fragmentierte Datenbank sein wird oder für jede Instanz, wie der Lastenausgleich aufgebaut wird und viele andere Details.

Probleme beim Erstellen von Microservices.

Lassen Sie uns als Bonus einen Blick auf die Probleme werfen, die bei einer solchen Entwicklung auftreten können.
Irgendwann kann die Zooverwaltung zum Problem werden.

Die Zahl der Dienste wächst unmerklich und dazu kommt, dass es problematisch wird, den Ablauf der Datenverwaltung zu überwachen. Daher wird die Frage nach einer möglichst lebendigen, genauen und aktuellen Dokumentation sehr wichtig. Mit verschiedenen Schemata der Interaktion von Diensten und angenommenen architektonischen Lösungen. Das wird das Leben für alle einfacher machen: Architekten, Entwickler, Tester, und auch die Anpassung neuer Teammitglieder weniger schmerzhaft machen. Andernfalls kann alles so weit kommen, dass zum Zeitpunkt des Scheiterns niemand weiß, wo er suchen soll. Es wird unmöglich, die Größe des Systems, seine Funktionalität und die benötigten Ressourcen zu verstehen, und es erhöht auch die Wahrscheinlichkeit, dass jemand vorhandene Funktionen zweimal verwendet.
Andere Probleme ergeben sich aus der Art und Weise, wie Microservices interagieren.

Das Problem der historischen Datenskalierung.

Ein markantes Beispiel können Prozesse sein, in denen historische Daten vorhanden sind.
Ein kleines Beispiel aus dem Leben: Es gibt einen Speicher mit einer für uns interessanten Tabelle für 8 Millionen Datensätze. Einmal am Tag werden die Daten in dieser Tabelle aktualisiert, und nur einmal am Tag müssen wir sie erhalten und verarbeiten. Wir können nicht einfach direkt zu diesem Speicher gehen, daher erfolgt die Interaktion mit den Daten über eine Kette von Proxy-Diensten. Das heißt, einmal am Tag wird eine bestimmte Aufgabe gestartet, die Daten aus dieser Tabelle liest und sie in Kafka wirft. Am Anfang wird eine Nachricht ausgegeben, dass die Daten begonnen haben, und am Ende, dass die Daten geendet haben. Alles scheint in Ordnung zu sein, aber viele Leute möchten diese Proxy-Kette verwenden, und deshalb gibt es mehrere Aufgaben zum Subtrahieren verschiedener Tabellen darin. Da viele Daten übertragen werden müssen, ist es logisch, das Thema zu partitionieren. Alles sieht gut aus: Die Daten sind gleichmäßig verteilt, die Last ist gleichmäßig verteilt. Aber wie liest man sie jetzt? Wenn Sie Daten gleichmäßig über Partitionen verteilen, entsteht zwangsläufig das Problem, dass Sie die Meldung über das Ende der Daten früher lesen können als die letzten Daten. Dies kann passieren, wenn Sie mit mehreren Verbrauchern zu lesen beginnen.

Die Entscheidung, die Nachricht am Anfang und am Ende der Daten abzulehnen, liegt nahe. Dann tritt das Problem auf – wie kann festgestellt werden, dass alle Daten gelesen wurden und Sie mit der Verarbeitung beginnen können? Sie können versuchen, ein Intervall festzulegen, und wenn nichts anderes kommt, gehen Sie davon aus, dass alle Daten gelesen wurden. Aber hier ist ein anderes Problem – was in den Fällen zu tun ist, wenn der Speicher auf einige Anfragen mit Fehlern antwortet. Schließlich werden die Daten nicht auf einmal, sondern seitenweise erfasst. Was tun, wenn bedingte 3-Versuche, eine bestimmte Seite abzurufen, fehlgeschlagen sind?

  • Muss ich eine spezielle Nachricht an die Warteschlange senden?

  • Soll ich weiter zur nächsten Seite gehen?

  • Was ist mit denen, die die Fehlermeldung aus der Warteschlange lesen?

Viele dieser Fragen lassen sich nicht eindeutig beantworten. Und in einigen Fällen werden andere Vereinbarungen als Lösung verwendet. Solche Entscheidungen bieten keine Garantien oder besondere Zuverlässigkeit – es sind immer Entscheidungen mit einem angemessenen Risiko, das als akzeptabel akzeptiert wird.

Ein noch bunteres Beispiel ist die Geschichte des Zahlungsverkehrs. Dabei ist die Bewahrung der Historizität von grundlegender Bedeutung. Das örtliche Umordnen von Nachrichten kann eine völlig legale Transaktion verhindern. Ich denke, jeder versteht die Situation, wenn sich 10 Rubel auf dem Konto befinden, Transaktionen 5 Rubel abschreiben, dann 2 Rubel hinzufügen und dann 6 Rubel abschreiben. Infolgedessen beträgt der Kontostand 1 Rubel. Aber wenn Sie die letzten 2 Transaktionen neu anordnen, wird alles zusammenbrechen. Es ist ganz klar zu sehen, dass es in diesem Fall ein Thema mit einer Partition geben sollte, und dann wird Kafka eine klare Nachrichtenreihenfolge bereitstellen. Aber die Frage ist, die Anzahl der Nachrichten hat zugenommen, wie kann man in diesem Fall das System unter Last skalieren? Auch hier gibt es keine eindeutige Antwort. Im Allgemeinen vertikal skalieren (Serverleistung, Kerne, Arbeitsspeicher usw. erhöhen). Oder teilen Sie Transaktionen basierend auf einigen Kenntnissen über den Client in Partitionen auf. Hier ist es notwendig zu verstehen, dass die meisten Lösungen für allgemeine Fälle bestimmt sind. Aber speziell für Ihre Aufgabe muss es an die Bedingungen angepasst werden, in denen Sie sich befinden.

Welche Schlussfolgerung kann aus dem oben Gesagten gezogen werden. Der Microservice-Ansatz ist das unvermeidliche Ergebnis der Evolution in der Entwicklung. Ein Ansatz, der die Fähigkeiten von Computersystemen dramatisch erweiterte, eröffnete die Chance für die Entstehung riesiger Systeme und Infrastrukturen im Einsatz.
Inspiriert von diesem Ansatz sollten wir vor der Anwendung immer daran denken:

  1. Ist es die Lösung aller Entwicklungsprobleme? Nein.

  2. Lohnt es sich, es in Ihrer Arbeit anzuwenden? Ja. Aber vergessen Sie nicht Frage Nummer eins.

Similar Posts

Leave a Reply

Your email address will not be published.