Photo by Emile Perron on Unsplash
PHP (Hypertext Preprocessor) ist eine quelloffene Skriptsprache, die mit eingebauten Webentwicklungsfunktionen entwickelt wurde. Sie wird seit über 20 Jahren in der Webentwicklung eingesetzt. Auch wenn derzeit fast 80 % aller Websites damit betrieben werden, hat PHP einige erhebliche Schwächen. Eine davon ist der Single-Thread-Modus.
PHP verfügt nicht über eine Standardfunktion, um zusätzliche Threads für Hintergrundprozesse zu erzeugen. Aufgrund des Single-Thread-Designs der Sprache werden Sie es schwer haben, auch nur eine einfache Webanwendung zu erstellen, die E-Mails versenden muss. Das Versenden einer E-Mail ist ein teurer und zeitaufwändiger Prozess, der den einzelnen PHP-Thread blockieren kann.
PHP-Frameworks lösen das Problem
PHP verfügt über eine große Anzahl von Open-Source-Frameworks. Ihr Hauptziel ist es, die Entwicklungszeit für komplexe Webanwendungen durch die Anwendung vordefinierter Konzepte zu verkürzen. Sie helfen PHP-Entwicklern, Aufgaben zu automatisieren, effizient mit verschiedenen Arten von Datenbanken zu arbeiten, Anwendungen gegen bekannte Schwachstellen zu schützen und Tests durchzuführen. Die beliebtesten Frameworks sind Laravel, Zend, Symfony und Phalcon. Jedes von ihnen hat seine eigenen Vorteile und eine große Gemeinschaft, die zur Open-Source-Codebasis beiträgt.
Jedes Framework bietet eine Möglichkeit, das Problem des Multithreading in PHP zu umgehen. Wenn Sie einen Prozess im Hintergrund laufen lassen wollen, ist das allgemeine Konzept das folgende: Die Informationen über eine Aufgabe werden an einen Warteschlangentreiber/Nachrichtenbus gesendet, während der Server ständig die Liste der Aufgaben überprüft, um zu sehen, ob es etwas auszuführen gibt.
Handhabung von Nachrichten-Warteschlangen in Laravel
Laravel verfügt über eine einheitliche Warteschlangen-API, die Ihnen die Wahl zwischen verschiedenen Technologien wie Redis, Amazon SQS, Beanstalkd oder sogar einem altmodischen relationalen Datenbanksystem lässt. Sehen wir uns an, wie Laravel mit Redis, einem Open-Source-Speicher für Datenstrukturen im Arbeitsspeicher, funktioniert.
Was ist der Nutzen von Redis in Laravel? Vom Konzept her schieben Sie eine Aufgabe (oder "dispatching a job", wie es im Laravel-Ökosystem genannt wird) in die Redis-Warteschlange, wo sie wartet, bis sie abgeholt und verarbeitet wird. Um Warteschlangen zu verarbeiten, brauchen Sie Worker, die ununterbrochen laufen. Auf Produktionsservern sollten Sie einen Supervisor haben, einen Prozessmonitor, wie er in Linux-Umgebungen üblich ist.
Nehmen wir an, Sie haben ein Benutzermodell, von dem für jede neue Registrierung eine Instanz erstellt werden soll. Es gibt keine Standardmethode, um dies zu erreichen: Es ist alles eine Frage der Einstellungen. Um es einfach zu implementieren, benötigen Sie einen Beobachter und einen Job in der Warteschlange:
Der Beobachter wird ausgelöst, wenn eine Instanz des Modells erstellt wird.
Der Beobachter sendet den Auftrag an die Warteschlange.
Der Beobachter lässt queue:work
für das Projekt laufen. Sobald ein neuer Job versendet wird, wird er von Laravels Queue Worker verarbeitet.
Schauen wir uns ein Code-Beispiel an.
Zuerst müssen Sie einen Beobachter für das User-Modell und einen Job erstellen:
php artisan make:observer UserObserver --model=User
php artisan make:job SendRegistrationEmail
Als nächstes müssen Sie das, was Sie gerade erstellt haben, in die Anwendung einführen. Sie können dies tun, indem Sie einen Beobachter im EventServiceProvider
wie folgt registrieren:
namespace App\Providers;
class EventServiceProvider extends ServiceProvider
{
public function boot()
{
User::observe(UserObserver::class);
}
}
Ihre Anwendung hat nun die Definition dessen, was sie beobachten soll, und Sie sollten sie so konfigurieren, dass sie diese Aufgabe erfüllt. Der UserObserver
muss reagieren, wenn eine Instanz des User
Modells erstellt wird. Das können Sie erreichen, indem Sie die Methode created
des Beobachters für das entsprechende Modell aktualisieren. Lassen wir ihn 10 Sekunden nach der Erstellung der Modellinstanz eine E-Mail senden, um zu sehen, wie es in der Praxis funktioniert.
namespace App\Observers;
class UserObserver
{
public function created(User $user)
{
SendRegistrationEmail::dispatch($user->email)->delay(now()->addSeconds(10));
}
}
Jedes Mal, wenn ein Benutzer
erstellt wird, wird der SendRegistrationEmail
-Job vom Beobachter versendet.
namespace App\Jobs;
class SendRegistrationEmail implements ShouldQueue
{
public $email;
public function __construct($email)
{
$this->email = $email;
}
public function handle()
{
$email = $this->email;
Mail::send([], [], function ($message) use ($email)
{
$message
->to($email)
->subject('Hallo, Welt!')
->setBody('Willkommen bei der Anwendung!');
});
}
}
So implementieren Sie den Ablauf. Jetzt sendet die Anwendung bei der Registrierung eine E-Mail an einen neuen Benutzer.
Damit dies über Warteschlangen funktioniert, müssen Sie die Konfigurationen aktualisieren, um den Redis-Treiber zu verwenden. Nachdem Sie Redis auf Ihrem Rechner installiert haben, genügt es, die .env
einmal zu aktualisieren:
QUEUE_CONNECTION=redis
Vergessen Sie danach nicht, Ihre Konfigurationen zu aktualisieren: php artisan config:cache
. Um Laravel's Queue Worker zu starten, müssen Sie php artisan queue:work
ausführen.
Lösen von Problemen mit Laravels Queue-Workern
Produktionsumgebungen erfordern, dass die Worker die ganze Zeit laufen. Der Befehl queue:work
selbst kann aus vielen Gründen fehlschlagen, z.B. wegen Überschreitung des maximalen Timeouts. Es ist nicht möglich, den Server manuell zu überprüfen, um sicherzustellen, dass die Warteschlangenarbeiter aktiv sind. Stattdessen werden Sie Supervisor verwenden, einen Prozessmonitor für Linux-Umgebungen.
Nachdem Sie Supervisor auf dem Server installiert haben, müssen Sie ihm eine Konfigurationsdatei geben, damit er mit den Warteschlangenarbeitern von Laravel interagieren kann. Lassen Sie uns eine Datei laravel-worker.conf
erstellen, die den Prozess queue:work
behandelt.
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/project/artisan queue:work --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=root
numprocs=4
redirect_stderr=true
stdout_logfile=/home/project/logs/worker.log
stopwaitsecs=3600
In dieser Konfiguration sollten Sie den Supervisor anweisen, die Worker automatisch neu zu starten und 4 parallele Prozesse laufen zu lassen. Starten wir die Prozesse:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start laravel-worker:*
Abschließend ist dies so einfach, wie es sich liest. Der Supervisor wird die Arbeiter am Laufen halten. Wann immer die Warteschlange einen Auftrag erhält, werden die Arbeiter ihn auswählen und ausführen. Auf diese Weise stellen Sie sicher, dass der Benutzer nicht warten muss, bis PHP die Aufgabe des Versendens einer E-Mail bearbeitet und zur nächsten Seite übergeht, indem Sie diese zeitaufwändige Aufgabe in "parallele" Threads verschieben.