Bessere Skalierbarkeit mit entkoppelten Queues: So richtest du RabbitMQ mit TYPO3 ein

|Jochen Roth

TYPO3 v12 unterstützt Symfony Messenger, einen flexiblen Message Bus mit der Möglichkeit, Nachrichten innerhalb der Anwendung zur sofortigen Bearbeitung oder über externe Message Transport Services zur asynchronen Bearbeitung zu senden.

In diesem Artikel werden die Schritte zum Hinzufügen eines externen Transports, RabbitMQ, zu einer TYPO3-Instanz erläutert. 

Externe Messaging-Dienste verstehen

Symfony Messenger unterstützt eine Reihe von verschiedenen Transporttypen, darunter:

  • In-Memory
  • Doctrine
  • Redis
  • AMQP
  • und andere.

In der Regel wirst du mit den einfachsten Optionen beginnen wollen. Der In-Memory-Transport ist vielleicht die einfachste Variante, wird aber hauptsächlich zu Testzwecken verwendet.

Der Doctrine-Transport verwendet die vorhandene Datenbank zum Speichern von Nachrichten. Er ist oft die erste Wahl, weil er einfach einzurichten ist und keine externen Abhängigkeiten erfordert.

Doctrine sollte jedoch nicht Ihre endgültige Lösung sein, da die Verwendung einer Datenbank als Nachrichtenwarteschlange nicht gut skalierbar ist. Irgendwann wirst du ein externes Message-Queue-System wie RabbitMQ oder Redis verwenden wollen - Vermittler, die deinen Anwendungen, Microservices und Softwarekomponenten eine gemeinsame Plattform zum Austausch von Nachrichten bieten. 

In diesem Artikel konzentrieren wir uns auf RabbitMQ, einen sehr beliebten und ausgereiften Open-Source Messaging Broker.

Abgesehen von dem in TYPO3 integrierten Doctrine-Transport hat ein System wie RabbitMQ mehrere Vorteile:

  • Es ist gut skalierbar. RabbitMQ kann eine große Anzahl von Nachrichtenanbietern und -verbrauchern bedienen. Anwendungen können miteinander verbunden werden, als Komponenten einer größeren Anwendung, oder mit Benutzergeräten und Daten. Die Arbeitslast kann auf mehrere Prozesse verteilt werden.
  • Unterstützung für verteilte Systeme. RabbitMQ kann Nachrichten-Broker auf mehreren Knoten ausführen, um die Arbeitslast auf ein Netzwerk von Diensten zu verteilen und Nachrichten zwischen Produzenten und Konsumenten weiterzuleiten.
  • Fehlertoleranz durch Design. Nachrichten werden in einer Warteschlange gespeichert, bis sie verbraucht werden. Die Warteschlange kann Lastspitzen auffangen und Nachrichten speichern, falls ein oder mehrere Verbraucher vorübergehend nicht verfügbar sind.

Diese Vorteile machen es lohnenswert, die Zeit zu investieren, um einen externen Nachrichtentransport für TYPO3 einzurichten. Wenn du RabbitMQ ausprobieren willst, lies weiter, um zu sehen, wie man ein DDEV-basiertes Nachrichtenwarteschlangen-Projekt erstellt. 

Einrichten von RabbitMQ in TYPO3 mit DDEV

Beginnen wir mit einem leeren Blatt Papier. Für diese Übung verwenden wir Docker und DDEV sowie das GitLab TYPO3-Site-Paket, du benötigst also ein GitLab-Konto. Das GitLab TYPO3-Site-Paket ist eine Projektvorlage für die Erstellung eines Site-Pakets auf der Grundlage einer sauberen TYPO3-Installation.

Docker und DDEV installieren

DDEV verwaltet containerisierte PHP-Entwicklungsumgebungen und ermöglicht es Ihnen, ein Projekt in Sekundenschnelle einzurichten.

Folge den DDEV-Installationsanweisungen, um sowohl Docker als auch DDEV zu installieren.

Für die Einrichtung siehe auch die Dokumentation zu DDEV.

Einrichten einer TYPO3-Site

Mit der TYPO3-Distributionsvorlage in GitLab kannst du auf einfache Weise eine TYPO3-Site einschließlich eines leeren Site-Pakets einrichten.

1. Anmelden bei GitLab

2. Klicke in der oberen linken Ecke auf das Pluszeichen + und wählen Sie New project/Repository.
 

3. Wähle „Create from template".

4. Blätter  in der Liste der Vorlagen nach unten zu "TYPO3 Distribution" und klicke auf “Use template”.

5. Gib im Vorlagenformular den Namen "TYPO3 and RabbitMQ" ein. Wähle eine Gruppe oder einen Namespace und klicke dann auf "Create project".

6. Wenn das Projekt erstellt ist, öffne eine lokale Shell auf Ihrem System und klone das Repository in einen lokalen Ordner.

7. Wechsel in das Verzeichnis des Projekts und initialisiere TYPO3.

1
2
3
cd typo3-and-rabbitmq

ddev typo3-init

Mit diesem Befehl wird das anfängliche Projekt erstellt und Docker-Container mit einem Webserver und einer Datenbank darin gestartet.

Nach Beendigung des Befehls werden die Anmeldedaten in das Terminal ausgegeben und die Anmeldeseite im Browser geöffnet.

8. Melde dich an, um zu testen, ob TYPO3 einwandfrei läuft. Wenn sich die Anmeldeseite nicht automatisch öffnet, öffne sie unter dem Pfad /typo3. Basierend auf den GitLab-Details aus Schritt 5 lautet die Login-URL "https://typo3-and-rabbitmq.ddev.site/typo3".

RabbitMQ installieren

Jetzt kannst du RabbitMQ über DDEV installieren.

1. Führe in der Shell folgenden Befehl aus:

1
ddev get b13/ddev-rabbitmq

2. Füge diese Zeilen zu deinem .ddev/config.yaml hinzu:

1
webimage_extra_packages: ["php${DDEV_PHP_VERSION}-amqp"

AMQP ist das Nachrichtenprotokoll, das RabbitMQ verwendet. Das Paket webimage_extra_packages stellt sicher, dass das Paket amqp installiert ist.

3. Starte nun deine Container neu, um die Änderung zu übernehmen:

1
ddev restart

Dies kann ein paar Sekunden dauern.

4. Führe ddev describe aus, um zu sehen, ob RabbitMQ installiert ist. Der Befehl zeigt die URL des RabbitMQ-Dienstes an.

AMQP-Unterstützung für den Symfony Messenger installieren

Da RabbitMQ das AMPQ-Protokoll verwendet, werden wir nun AMQP für den Symfony Messenger einrichten.

1. Installiere das Paket symfony/messenger: 

1
ddev composer req symfony/amqp-messenger:^6.4

2. TYPO3 muss wissen, wie man mit dem RabbitMQ-Dienst kommuniziert. Um dies zu erreichen, bearbeite die Datei in packages/site-distribution/Configuration/Services.yaml wie folgt (achte darauf, dass du die Einrückung beibehältst): 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
services:
_defaults:
autowire: true
autoconfigure: true
public: true

Site\Distribution\:
resource: "../Classes/*"
exclude: "../Classes/Domain/Model/*"

Site\Distribution\Queue\Handler\MyHandler:
tags:
- name: "messenger.message_handler"

Site\Distribution\Queue\Message\MyMessage:
arguments:
$content: 'Default content'

# RabbitMQ
Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory:
public: true

Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransport:
factory:
[
'@Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory',
"createTransport",
]
arguments:
$
dsn: "amqp://rabbitmq:rabbitmq@rabbitmq:5672/%2f/queue"
$options: {}
tags:
- name: "messenger.sender"
identifier: "amqp"
- name: "messenger.receiver"
identifier: "amqp"

Beachte das Argument $dsn, das die Verbindungszeichenfolge enthält. Beim Einsatz in der Produktion musst du die Parameter entsprechend anpassen. Dies ist die kanonische Form des AMQP-DSN:

1
amqp://<username>:<password>@rabbitmq:5672/<virtual host>/<queue name>

Erstellen einer Nachrichtenklasse

Um eine Nachricht zu versenden und zu verarbeiten, benötigt man eine Nachrichtenklasse. Eine sehr einfache Version einer Nachrichtenklasse ist die folgende, die du in Classes/Queue/Message/MyMessage.php erstellen kannst:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php

declare(strict_types=1);

namespace Site\Distribution\Queue\Message;

final class MyMessage
{
public function __construct(
public readonly string $content
)
{
}
}

3. Die Klasse MyMessage muss für den asynchronen Transport über AMQP konfiguriert werden. Bearbeite die Datei packages/site-distribution/ext_localconf.php entsprechend:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

declare(strict_types=1);

use Site\Distribution\Queue\Message\MyMessage;
use TYPO3\CMS\Core\Messaging\WebhookMessageInterface;

defined('TYPO3') or die();

// Include vite generated manifest file (global)
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['vite_asset_collector']['defaultManifest'] = 'EXT:site-distribution/Resources/Public/Vite/.vite/manifest.json';

// Include custom RTE config
$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['default'] = 'EXT:site-distribution/Configuration/RTE/RteDefaultPreset.yaml';

// Unset the default, so that it no longer applies
unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing']['*']);

// Set Webhook-Messages and MyCustomMessage to asynchronous transport via amqp
foreach ([WebhookMessageInterface::class, MyMessage::class] as $className) {
$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing'][$className] = 'amqp';
}

$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['config']['amqp'] = [
'dsn' => 'amqp://rabbitmq:rabbitmq@rabbitmq:5672/%2f/messages',
];

Einrichten einer Message-Handler-Klasse

Ein Message-Handler empfängt und verarbeitet Nachrichten vom RabbitMQ-Broker. Er wendet sich nicht aktiv an den Broker, sondern verlässt sich auf einen Consumer-Prozess, der jede Nachricht liest und sie an den Message-Handler weitergibt.

Hier ist eine einfache Version eines Message-Handlers, die Sie in packages/site-distribution/Classes/Queue/Handler/MyHandler.php erstellen können:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

declare(strict_types=1);

use Site\Distribution\Queue\Message\MyMessage;
use TYPO3\CMS\Core\Messaging\WebhookMessageInterface;

defined('TYPO3') or die();

// Include vite generated manifest file (global)
$GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['vite_asset_collector']['defaultManifest'] = 'EXT:site-distribution/Resources/Public/Vite/.vite/manifest.json';

// Include custom RTE config
$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['default'] = 'EXT:site-distribution/Configuration/RTE/RteDefaultPreset.yaml';

// Unset the default, so that it no longer applies
unset($GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing']['*']);

// Set Webhook-Messages and MyMessage to asynchronous transport via amqp
foreach ([WebhookMessageInterface::class, MyMessage::class] as $className) {
$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['routing'][$className] = 'amqp';
}

$GLOBALS['TYPO3_CONF_VARS']['SYS']['messenger']['config']['amqp'] = [
'dsn' => 'amqp://rabbitmq:rabbitmq@rabbitmq:5672/%2f/messages',
];

Teste die Message Queue

Nun ist es an der Zeit, die Queue zu testen. Eine einfache Möglichkeit, Nachrichten in die Warteschlange einzuspeisen, besteht darin, eine Middleware einzurichten, die bei jedem Laden einer Seite ausgelöst wird.

1. Füge eine Klasse "RunTheRabbit", die das MiddlewareInterface implementiert, zu packages/site-distribution/Classes/Middleware/RunTheRabbit.php hinzu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

declare(strict_types=1);

namespace Site\Distribution\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Site\Distribution\Queue\Message\MyMessage;
use Symfony\Component\Messenger\MessageBusInterface;

class RunTheRabbit implements MiddlewareInterface
{
public function __construct(private readonly MessageBusInterface $bus)
{
}

public function process(
ServerRequestInterface $request,
RequestHandlerInterface $handler
)
: ResponseInterface
{
$content = 'Some important content';
$this->bus->dispatch(new MyMessage($content));

return $handler->handle($request);
}
}

2. Um die Middleware zu registrieren, erstelle die Datei packages/site-distribution/Configuration/RequestMiddlewares.php und füge den folgenden Code hinzu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
return [
'frontend' => [
'site-rabbit' => [
'target' => \Site\Distribution\Middleware\RunTheRabbit::class,
'after' => [
'typo3/cms-frontend/authentication',
],
'before' => [
'typo3/cms-frontend/base-redirect-resolver',
],
],
],
];

3. Es ist Zeit zum Testen! Starte TYPO3 neu, um alle Änderungen zu übernehmen:

1
ddev restart

4. Überprüfe, ob alle Dienste verfügbar sind:

1
ddev describe

Du solltest ein "OK" für die Web-, db- und rabbitmq-Dienste sehen.

5. Du musst einen Consumer starten, um die Nachrichten zu lesen, die RunTheRabbit an die Warteschlange sendet. Um das Testen zu erleichtern, kannst du diesen Einzeiler verwenden:

1
ddev typo3 messenger:consume -vv amqp

Das Flag -vv veranlasst den Verbraucher, seine Aktionen auf dem Terminal zu protokollieren.

6. Öffne nun das TYPO3-Frontend. Jedes Mal, wenn du eine Seite im Frontend öffnest oder neu lädst, wirst du diese Meldung im Terminal sehen:

1
[info] Site\Distribution\Queue\Message\MyMessage was handled successfully (acknowledging to transport).

Herzlichen Glückwunsch! Du hast erfolgreich RabbitMQ Messaging mit TYPO3 eingerichtet.

Schlussfolgerung: Message Queues und TYPO3 sind ein gutes Team

Das Einrichten von Nachrichten-Warteschlangen mit TYPO3 ist in v12 viel einfacher geworden. Mit wenigen Schritten ist es möglich, RabbitMQ mit TYPO3 einzurichten und zu integrieren. Jetzt gibt es keinen Grund mehr, sich über wachsende Besucherzahlen auf deiner Website Sorgen zu machen. Wenn bestimmte beliebte Dienste wie die PDF-Erzeugung oder das Zippen von Dateien für den Download zu viel CPU-Leistung verbrauchen, kannst du diese Aufgaben dank der neuen Message Queue-Integration an asynchrone Worker auf verschiedenen Nodes auslagern.

Wenn du Hilfe oder Unterstützung bei der Implementierung von Nachrichtenwarteschlangen brauchst, kontaktiere uns!

Meld dich bei uns.