12. Nov 2021
Die Evolution eines Hasen
Neue Use Cases von RabbitMQ – Streams als Alternative zu Queues
Wenn es um verzögerungsfreie Nachrichtenübertragung zwischen verschiedenen Diensten geht, kann die Kommunikation eine Herausforderung darstellen. Vor allem bei der Verwendung von Microservices oder bei der Datenverarbeitung in Datenplattformen sind die kommunizierenden Dienste weitestgehend entkoppelt und nutzen häufig nicht das gleiche Nachrichtenprotokoll. An dieser Stelle kommen Nachrichtenbroker ins Spiel, welche als Vermittler und Übersetzer fungieren. In diesem Beitrag beschäftigen wir uns mit einem der wohl bekanntesten open-source Nachrichtenbroker – RabbitMQ. Durch die von RabbitMQ verwendete Datenstruktur der Queues können jedoch insbesondere Persistenz und Replayfähigkeit von Nachrichten nicht oder nur erschwert sichergestellt werden. Diese Lücke wird in Version 3.9 durch die neue Datenstruktur Streams geschlossen.
Grundprinzipien von RabbitMQ
RabbitMQ basiert auf dem Nachrichtenprotokoll AMQP (Advanced Message Queuing Protocol), durch Plug-Ins können jedoch auch weitere Protokolle wie STOMP oder MQTT verwendet werden. Unabhängig vom verwendeten Protokoll bleibt die Grundidee von RabbitMQ jedoch die Gleiche: Zwischen dem Sender (Publisher) und dem Empfänger (Consumer) einer Nachricht befindet sich eine Warteschlange (Queue), in welcher Nachrichten zwischengespeichert werden. Auf diese Weise muss der Publisher nicht darauf warten, dass die gesendete Nachricht gelesen wird und der Consumer kann Nachrichten zu einem beliebigen Zeitpunkt abholen. Die Nachrichtenübertragung findet also asynchron statt.
Wird eine Nachricht mit Hilfe von RabbitMQ verarbeitet, so durchläuft sie 4 Stationen, welche in Abbildung 1 dargestellt sind.
- Producer: Hier wird die Nachricht erzeugt. Zusätzlich werden verschiedene Nachrichtenattribute, wie zum Beispiel der Routing Key, festgelegt.
- Exchange: Die Nachricht wird nicht direkt an den Empfänger gesendet, sondern an den Exchange übergeben. Dieser leitet die Nachricht anhand ihres Routing Keys an eine oder mehrere Queues weiter.
- Queue: In der Queue werden Nachrichten gelagert, bis sie von einem Consumer abgeholt werden. Sobald eine Nachricht acknowledged wurde, wird sie aus der Queue gelöscht. Die Reihenfolge des Auslesens entspricht dem FIFO-Prinzip.
- Consumer: Dieser holt die Nachrichten ab und verarbeitet sie. Ein Consumer registriert sich auf eine Queue und bezieht von dieser Nachrichten. Wenn mehrere Consumer derselben Queue zugeordnet sind, so werden die Nachrichten im Round-Robin-Verfahren verteilt.
Persistenz und Replayfähigkeit
Bei vielen Anwendungsfällen kann es wichtig sein, Datenverlust vorzubeugen. Hierbei sollten sowohl die Persistenz von Nachrichten und Queues als auch die Replayfähigkeit, also die Fähigkeit Nachrichten für eine konfigurierte Zeitspanne vorhalten und nachträglich erneut auslesen zu können, betrachtet werden.
Die Persistenz kann bei RabbitMQ durch entsprechende Konfigurationen sichergestellt werden. So müssen zum einen die Queues und Exchanges bei Erstellung als durable definiert werden, zum anderen sollte für jede Nachricht mandatory=true und persistent=true gesetzt werden. Auf diese Weise wird sichergestellt, dass jede Nachricht in eine Queue weitergeleitet wird und dass Nachrichten, Queues und Exchanges einen Neustart des Brokers überdauern.
Im Gegensatz dazu ist die Replayfähigkeit bei Queues nicht gegeben. Sobald eine Nachricht vom Consumer acknowledged wurde, wird sie aus der Queue gelöscht und kann nicht erneut ausgelesen werden.
Mit RabbitMQ Version 3.9 (Release: 23. Juli 2021) wurde jedoch eine neue Datenstruktur vorgestellt, welche genau diese Lücke schließt: Streams.
Streams – neue Wege in Version 3.9
Mit Streams bietet RabbitMQ seit Version 3.9 eine neue persistente und replizierte Datenstruktur als Alternative und Ergänzung zu Queues, welche den Topics in Kafka ähnlich ist. Daraus ergeben sich verschiedene neue Use-Cases, welche bisher durch RabbitMQ nicht oder nur eingeschränkt abgedeckt werden konnten.
Sollen mehrere Consumer die gleiche Nachricht erhalten, so musste bisher für jeden Consumer eine eigene Queue angelegt werden. Dies kann schnell zu einer unüberschaubaren Menge an Queues führen. Mit Streams wird dieses Problem behoben, da hier beliebig viele Consumer die gleiche Nachricht auslesen können, ohne dass diese bei Acknowledgement gelöscht wird. Ein weiteres Feature von Streams ist die Replayfähigkeit. Ein Consumer kann an beliebiger Stelle Nachrichten auslesen, zum Beispiel durch Angabe eines Offsets.
Eine Möglichkeit des Load-Balancing, wie es zum Beispiel durch die Consumer Groups in Kafka bekannt ist, ist jedoch leider noch nicht gegeben.
Fazit
Durch die neue Datenstruktur Streams bieten sich viele neue Use-Cases für RabbitMQ. Vor allem die Replayfähigkeit war in früheren Versionen eine Schwachstelle. Schön wäre jedoch eine Verknüpfung von Streams mit der Möglichkeit des Load Balancing, welches Queues bieten.
Wir dürfen gespannt sein, was die nächsten Versionen von RabbitMQ noch mit sich bringen!