angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Von monolithischem Code zu Services mit RabbitMQ und Pika

Verwenden Sie die Beispiele in der Dokumentation Pika , um Ihre eigenen RabbitMQ Publisher- und Consumer-Klassen zu erstellen.

8 Januar 2023 Aktualisiert 9 Januar 2023
post main image
https://www.pexels.com/nl-nl/@jeshoots-com-147458

In diesem Beitrag geht es um die Verwendung von RabbitMQ in Ihrer Python Anwendung. Wenn Sie Rabbit bereits verwenden, werden Sie in diesem Beitrag wahrscheinlich nichts Nützliches finden. Warum ein Beitrag über RabbitMQ? Weil ich eine Anwendung habe, die seit einem Jahr läuft, und ich dachte, dass ich meine Erfahrungen teilen möchte.

In diesem Beitrag wandeln wir eine monolithische Anwendung in Dienste um, die durch Warteschlangen entkoppelt sind. Außerdem spreche ich von Services und nicht von Microservices, Sie entscheiden, wie "groß" Ihr Service ist. In diesem Beitrag gibt es keine Codebeispiele.

Beispiel für eine monolithische Anwendung: Kontakt-Formular

Das Beispiel, das wir verwenden werden, ist der Prozess des Kontaktformulars. Ein user unserer Website gibt in das Kontaktformular Daten ein. Diese werden verarbeitet, eine E-Mail vorbereitet und die E-Mail wird an einen externen SMTP-Server gesendet.

        +-----------------+    smtp
user    | - process form  |  server
------->| - prepare email |------->
        | - send email    |
        +-----------------+

Warum sollten Sie Ihren Code in Dienste aufteilen?

Es gibt eine Reihe von Vor- und Nachteilen der Aufteilung Ihres Codes:

Vorteile (im Idealfall):

  • Strengere Trennung zwischen den Hauptdiensten (Funktionen) Ihres Codes.
  • Dienste können ohne Datenverlust angehalten und neu gestartet werden.
  • Leistung. Durch die Entkopplung wird ein Dienst schneller verfügbar.
  • Asynchron. Ein Dienst wartet darauf, dass ein anderer Dienst verfügbar wird, ohne andere Dienste zu beeinträchtigen.

Nachteile:

  • Erhöhte Komplexität.
  • Erhöhte Entwicklungszeit.
  • Erhöhte Schwierigkeit beim Testen.

Zusammenfassung der Vorteile ist: ein schnelleres und flexibleres System. Zusammenfassung der Nachteile: (viel) mehr Arbeit.

Es gibt viele Artikel im Internet, in denen von der "Microservices-Hölle" die Rede ist, aber es gibt ebenso viele Artikel, in denen von der "monolithischen Hölle" die Rede ist. Ich schlage vor, dass Sie selbst ein wenig recherchieren. Hier ist ein schöner Artikel: 'You Don't Need Microservices', siehe Links unten. Lesen Sie auch die Kommentare.

Die Dienste nach der Aufteilung

Hier sind die Dienste, nachdem wir unseren Code aufgeteilt haben.

        +---------+     +---------+     +---------+    smtp
user    | process |     | prepare |     | send    |  server
------->| form    |---->| email   |---->| email   |-------> 
        |         |     |         |     |         |
        +---------+     +---------+     +---------+

Nichts Besonderes, das ist einfach ein gut strukturierter Monolith.

RabbitMQ und Pika

Um die Dienste zu entkoppeln, verwenden wir Warteschlangen. RabbitMQ ist ein Message Broker und unterstützt Message Queues, mehrere Messaging-Protokolle und mehr. Um mit RabbitMQ in Python zu kommunizieren, verwenden wir das Python Paket Pika. Pika ist eine reine Python Implementierung des AMQP 0-9-1 Protokolls.

Warteschlangen in RabbitMQ sind FIFO, first-in-first-out. Mit RabbitMQ können Sie Vermittlungsstellen und Warteschlangen definieren. In der Regel definieren Sie einen Exchange und binden dann die Warteschlangen an diesen Exchange.

Ein Publisher wird verwendet, um Elemente in die Warteschlange zu stellen. Ein Consumer wird verwendet, um Elemente aus der Warteschlange zu holen. Beispiele für Publisher und Consumer finden Sie in der Dokumentation Pika .

        +-----------+     +-------------+     +-----------+    
        |           |     | RabbitMQ    |     |           |  
------->| publisher |---->| - exchange  |---->| consumer  |-------> 
        |           |     | - queue     |     |           |
        +-----------+     +-------------+     +-----------+

Publisher und Consumer können synchron oder asynchron sein.

In vielen Fällen werden wir einen:

  • Synchroner Publisher. Das bedeutet, dass wir ein Element an die Warteschlange RabbitMQ senden und auf die Bestätigung warten, dass das Element in die Warteschlange gestellt wurde, bevor wir mit anderen Dingen fortfahren.
  • Asynchroner Verbraucher. Das bedeutet, dass unsere Empfangsfunktion von RabbitMQ aufgerufen wird, sobald ein Element in der Warteschlange verfügbar ist.

RabbitMQ und Bestätigungen

RabbitMQ verfügt über zahlreiche Optionen, von denen die wichtigste die Bestätigungen sind. Es gibt zwei Arten von Bestätigungen.

  • Positive Quittierung
  • Negative Quittierung

(Synchrone) Publisher-Bestätigungen

Wenn der Herausgeber (unser Code) ein Element an RabbitMQ sendet, antwortet RabbitMQ mit einer:

  • Positive Bestätigung
    Das Element wurde von RabbitMQ empfangen
  • Negative acknowledgment
    Etwas ist schief gelaufen. Vielleicht existierte der Austausch oder die Warteschlange nicht, der Routing-Schlüssel war falsch.

(Asynchrone) Verbraucher-Bestätigungen

Unser Verbraucher wird von RabbitMQ mit dem nächsten Element aus der Warteschlange aufgerufen. Nach der Verarbeitung des Elements antwortet der Consumer (unser Code) mit dem Senden einer:

  • Positive Quittung
    Das Element wurde empfangen und verarbeitet, ich bin bereit für das nächste Element
  • Negative Bestätigung
    Das Element wurde empfangen, aber bei der Verarbeitung ist etwas schief gelaufen. RabbitMQ wird das Element NICHT aus der Warteschlange entfernen und es erneut versuchen.

Entkopplung der Dienste mit Warteschlangen

Um unsere Dienste zu entkoppeln, setzen wir eine Warteschlange zwischen den Dienst 'process form' und den Dienst 'prepare email' :

         publisher                     consumer
        +---------+     +-------+     +---------+    
user    | process |     |       |     | prepare |  
------->| form    |---->| queue |---->| email   |---->
        |         |     |       |     |         |
        +---------+     +-------+     +---------+

Der Dienst 'process form' fungiert als Herausgeber. Er enthält Code zum Senden von Elementen an die Warteschlange. Der Dienst 'prepare email' fungiert als Konsument. Er enthält Code zum Empfangen von Objekten in der Warteschlange.

Die Ausgabewarteschlange ist "Teil" des Herausgebers.

Nehmen wir nun an, die beiden Dienste laufen auf zwei Servern. Da wir wollen, dass unsere Dienste ohne Datenverlust angehalten und neu gestartet werden können, ist es wichtig zu verstehen, wo wir die Warteschlange zwischen den Diensten platzieren. Auf dem Publisher-Server oder auf dem Consumer-Server.

Die Antwort ist natürlich, dass sich die Warteschlange auf demselben Server wie der Dienst 'process form' befinden muss. Wenn der Server des Dienstes 'prepare email' ausfällt, kann der Dienst 'process form' weiterlaufen. Users kann weiterhin Kontaktformulare senden, die Daten werden vorübergehend in der Warteschlange gespeichert. Sobald der Dienst 'prepare email' wieder läuft, verarbeitet er alle in der Warteschlange wartenden Daten.

                  server 1                      server 2
         - - - - - - - - - - -  - -        - - - - - - - - - - 
                                    |     | 
         publisher                             consumer
        +---------+     +-------+   |     |   +---------+    
user    | process |     |       |             | prepare |  
------->| form    |---->| queue |------------>| email   |---->
        |         |     |       |             |         |
        +---------+     +-------+   |     |   +---------+

                                    |     | 
         - - - - - - - - - - -  - -        - - - - - - - - - - 

Im obigen Beispiel haben wir Server verwendet. Das Gleiche gilt aber auch, wenn Sie alles auf einem einzigen Server haben, aber zwei verschiedene Docker-Compose-Projekte haben, eines für den Dienst 'process form' und eines für den Dienst 'prepare email' . In diesem Fall fügen wir RabbitMQ dem 'process form' Docker-Compose Projekt hinzu.

Ein Konsument und ein Herausgeber zugleich

Es ist durchaus sinnvoll, wenn ein Dienst gleichzeitig Konsument und Herausgeber ist. In unserem Beispiel fungieren sowohl der Dienst 'prepare email' als auch der Dienst "E-Mail senden" als Verbraucher und Herausgeber.

                                       consumer                      consumer
                                          +                             +
         publisher                     publisher                     publisher
        +---------+     +-------+     +---------+     +-------+     +---------+    
 user   | process |     |       |     | prepare |     |       |     | send    |  
------->| form    |---->| queue |---->| email   |---->| queue |---->| email   |---->
        |         |     |       |     |         |     |       |     |         |
        +---------+     +-------+     +---------+     +-------+     +---------+

Der normale Vorgang für den Dienst 'prepare email' ist folgender:

  1. Consumer wird mit dem nächsten Element aus der Input-Queue aufgerufen.
  2. Das Element wird verarbeitet.
  3. Neue Daten werden in der Output-Queue veröffentlicht.
  4. Eine positive Veröffentlichungsbestätigung wird empfangen.
  5. Der Consumer sendet eine positive Bestätigung an die Input-Queue.
  6. RabbitMQ entfernt die Position aus der Eingangsqueue.
  7. RabbitMQ ruft den Consumer mit dem nächsten Element aus der Eingangs-Warteschlange auf, sobald es verfügbar ist.

Wenn im Consumer-Prozess etwas schief läuft, z. B. die Veröffentlichung in der Output-Queue, kann der Consumer eine negative Bestätigung an RabbitMQ senden. In diesem Fall wird RabbitMQ das Element nicht aus der Input-Warteschlange entfernen, sondern das gleiche Element erneut versuchen.

Erste Schritte mit RabbitMQ

Die ersten Schritte mit RabbitMQ sind überwältigend. Anwendungsentwickler wollen Rezepte verwenden und nicht eine endlose Menge von Dokumenten lesen. Ich begann mit der Installation von Docker RabbitMQ mit dem Management Plugin. Mit dem Management Plugin kann man Tauschbörsen und Warteschlangen erstellen, die Anzahl der Elemente in den Warteschlangen sehen und vieles mehr.

Dann habe ich die Beispiele aus der Pika -Dokumentation ausprobiert. Wenn Sie diese verstanden haben, können Sie diesen Code verwenden, um Ihre eigenen Publisher- und Consumer-Klassen zu erstellen, die Sie in Ihrer Anwendung verwenden können.

Randnotizen

Asynchrones Publizieren ist komplex, ich habe es bisher nicht verwendet. Synchrones Publizieren mit RabbitMQ ist nicht sehr schnell, vor allem wenn die Daten auch persistent sein sollen. Und Sie wollen wahrscheinlich, dass die Daten in der Warteschlange erhalten bleiben, denn mit dieser Option gehen die Daten nicht verloren, wenn Sie RabbitMQ anhalten oder das System abstürzt.

Sie können die synchrone Veröffentlichung beschleunigen, indem Sie eine Liste von Elementen an die Warteschlange senden, anstatt ein einzelnes Element. Dies hängt jedoch von Ihrer Anwendung ab.

Ich verwende keine einzelne fehlertolerante RabbitMQ -Instanz, sondern spinne mehrere RabbitMQ -Instanzen zusammen mit den Services. Ich verwende RabbitMQ mit mehreren Docker-Compose Projekten, es ist nur einer der Services.

RabbitMQ ist sehr speicherhungrig. Meine Anwendung ist nicht sehr komplex oder anspruchsvoll, aber eine RabbitMQ -Instanz verbraucht laut Docker Stats etwa 200MB.

Einige Leute im Internet beschweren sich über die hohe CPU -Nutzung. Ich habe einige CPU -Nutzungsspitzen festgestellt. RabbitMQ speichert eine Menge Daten, die anscheinend nach einem Absturz/Kill nicht verschwinden. Nachdem ich dies entfernt hatte, verschwanden die CPU -Spitzen.

Ich verwende auch das Shovel Plugin, um Daten von einer RabbitMQ Instanz zu einer anderen zu senden, unter Verwendung von Docker Network. Dies funktioniert ebenfalls problemlos.

Sie können Ihre Anwendung verwenden, um RabbitMQ Austausche und Warteschlangen zu konfigurieren. Ich tue dies nicht, sondern konfiguriere die Vermittlungsstellen und Warteschlangen mit dem Management Plugin, exportiere dann die neue Konfigurationsdatei und kopiere sie in die Konfigurationsdatei von RabbitMQ .

Ich benutze Dockerized nun schon seit über einem Jahr und es läuft sehr zuverlässig. Ich musste noch nie neu starten, weil etwas nicht in Ordnung war.

Zusammenfassung

Lohnt sich die Aufteilung Ihres monolithischen Systems in Dienste mit RabbitMQ ? Ja, aber wenn Sie nicht gerade ein großes Unternehmen sind, sollten Sie es nicht übertreiben, denn es bringt auch eine Menge Komplexität mit sich. Wie immer wird dieser Schmerz geringer, wenn Sie anfangen, mehr von RabbitMQ zu verstehen. Schließlich sollte RabbitMQ in der Werkzeugkiste eines jeden Entwicklers sein.

Links / Impressum

Pika
https://pika.readthedocs.io

RabbitMQ
https://www.rabbitmq.com

You Don’t Need Microservices
https://itnext.io/you-dont-need-microservices-2ad8508b9e27

Mehr erfahren

Pika RabbitMQ

Einen Kommentar hinterlassen

Kommentieren Sie anonym oder melden Sie sich zum Kommentieren an.

Kommentare

Eine Antwort hinterlassen

Antworten Sie anonym oder melden Sie sich an, um zu antworten.