RabbitMQ + EasyNetQ
Bei RabbitMQ handelt es sich um einen sogenannten Message Broker, der das Advanced Message Queuing Protokoll, kurz AMQP, unterstützt. Das Programm ist Open Source und steht unter der MPL-Lizenz und kann somit auch wunderbar in kommerzieller Software verwendet werden. Laut der Angabe auf der Homepage gibt es ca. 35.000 Produkte Einsätze, von kleinen bis großen Betrieben.
Was aber ist ein Message Broker? Um es mit meinen eigenen Worten zu erklären: dieser nimmt Nachrichten z.B. von einem Webserver oder einer Client-Anwendung entgegen und leitet sie an bestimmte andere Prozesse weiter. Ein Beispiel: eine Kunde bestellt ein Produkt im Webshop. Der Webserver sendet nun eine Nachricht über den Bestelleingang an der Broker. Das Bestellverarbeitungsmodul hat sich auf den Typ dieser Nachricht beim Broker angemeldet und bekommt so die Nachricht, jetzt etwas zu verarbeiten. Damit ist dieser das entscheidende Puzzleteil für die Kommunikation unterschiedlicher Prozesse auf dem gleichen oder unterschiedlichen Hosts.
Dies ist jetzt alles sehr vereinfacht und RabbitMQ kann natürlich wesentlich mehr. Mehr Informationen findet ihr auf der Homepage. Eine tolle Beschreibung mit vielen Bildern gibt es unter https://www.cloudamqp.com/blog/2015-05-18-part1-rabbitmq-for-beginners-what-is-rabbitmq.html.
Die Installation mit Docker ist natürlich super simpel. Nachfolgend mein docker-compose dazu.
version: '3'
services:
rabbit:
image: "rabbitmq:3-management"
restart: always
ports:
- "8150:15672"
- "5672:5672"
hostname: rabbit
Anschließend kann die GUI im Browser geöffnet werden: http://$HOST:8150. Benutzer und Passwort für die Anmeldung ist guest/guest.
Auf NuGet findet man das RabbitMQ.Client-Paket, mit dem in .NET die Anbindung an den RabbitMQ-Server gemacht werden kann. Meine ersten Versuche habe ich mit dieser Bibliothek gemacht und ich kann bestätigen, das ganze war ziemlich einfach, wenn man sich etwas mit der Dokumentation auf der Homepage beschäftigt.
Allerdings bin ich bei meinen Recherchen auf EasyNetQ gestoßen. Mit dieser Bibliothek wird die Anbindung gleich nochmal vereinfacht. Außerdem kümmert sich EasyNetQ um Standard-Fehlerfälle, wenn z.B. die Verbindung zum Message Broker weg ist und neu hergestellt werden muss. Daher gehe in Nachfolgend mehr auf diese API ein.
Als erstes muss die Verbindung von der Anwendung zu RabbitMQ hergestellt werden.
var bus = RabbitHutch.CreateBus("host=localhost");
Dann erstellen wir uns eine Klasse für eine Nachricht. Nehmen wir das Beispiel von oben mit dem Bestelleingang.
public class OrderReceived
{
public int OrderID { get; set; }
}
Die Nachrichten werden standardmäßig mit Newtonsoft.Json serialisiert. D.h. man muss darauf achten, dass entsprechend serialisierbar sind. Anschließend publizieren wir eine Nachricht.
var orderReceived = new OrderReceived() { OrderId = 155 };
bus.Publish(message);
That’s it. Wir haben unsere erste Nachricht versendet. Nun fehlt allerdings noch der Gegenpart, sprich jemand muss die Nachricht auch tatsächlich verarbeiten. Dazu muss die Subscribe-Methode mit der Message-Klasse als generischem Argument aufgerufen werden.
bus.Subscribe<OrderReceived>(
"my_subscription_id",
msg =>
Console.WriteLine(msg.OrderID));
Einfach, ha? Was hat es nun aber noch mit dieser my_subscription_id auf sich? EasyNetQ erstellt aus der Kombination des FullName der Message-Klasse und der Subscription-ID eine Queue in RabbitMQ. Beim Publizieren wird die Nachricht in alle Queues, die zu dieser Klasse gehören, gestellt. Nun kommt das Interessante: Mehrere Subscriber können mit der selben Subscription-ID arbeiten, allerdings bekommt immer nur einer von ihnen die Nachricht zugestellt. RabbitMQ wendet Round-Robin an. Dies bedeutet, dass bei jeder Nachricht ein anderer Subscriber mit der selben Subscription-ID die Nachricht zum Bearbeiten erhält.
Das zuvor geschriebene trifft auf das so genannte Publish/Subscribe-Pattern zu. Nachfolgend ein kurzer Einblick, welche EasyNetQ unterstützt. Ev. gibt es noch mehr, aber diese drei waren zumindest die, die für mich interessant sind.
Publish/Subscribe
Hierbei werden Nachrichten versendet und wenn sich jemand dafür interessiert, sprich eine Subscription hat, dann wird dieser informiert. Wenn nicht, dann verpufft die Nachricht. Die Queues werden von den Subscribern erstellt. Ich verwende diese Art immer mit einer Guid als Subscription-ID und AutoDelete = true, damit die Queues gelöscht werden, sobald der Subscriber nicht mehr präsent ist.
Wer genau aufgepasst hat, dem dürfte jetzt aufgefallen sein, dass mein Beispiel von oben nicht ganz optimal ist, da im Falle eines Bestelleingangs sich auch jemand darum kümmern sollte – zumindest wenn nicht gleich, dann irgendwann. Daher kommen wir jetzt zum Send/Receive-Pattern.
Send/Receive
Hierbei werden nicht die Publish und Subscribe-Methoden verwendet, sondern Send und Receive. Die Queue wird vom Sender oder Empfänger erstellt, je nachdem wer zuerst da ist. Zusätzlich muss der Queue ein Name gegeben werden. Dadurch bleibt die Nachricht auch erhalten, wenn es keinen Subscriber gibt. Sobald die Receive-Methode von einem Prozess aufgerufen wird, wird diesem die Nachricht zugestellt.
Im Falle, dass es mehrere Empfänger mit dem selben Queue-Namen gibt, wird wieder Round-Robin verwendet.
Request/Response
Eigentlich ist dieses Pattern sehr ähnlich dem Send/Receive, nur das der Sender „direkt“ eine Antwort vom Empfänger erhalten will. Unter Umständen kann dies allerdings etwas dauern, wenn kein Empfänger zur Verfügung steht oder gerade ausgelastet ist. Die Methoden sind hierbei – Achtung es wird spannend – Request und Respond 😉.
Soweit zu meinem kleinen Einblick in RabbitMQ und EasyNetQ. Wer sich genauer dafür interessiert, dem empfehle ich die Homepages der jeweiligen Produkte. Die Dokumentationen sind meiner Meinung nach sehr ordentlich und helfen gut weiter.
https://www.rabbitmq.com/
http://easynetq.com/