Bereiten Sie unser Produkt mit Laravel-Warteschlangen skalierbar vor

Eine Übersetzung des Artikels wurde speziell für Studenten des Kurses „Framework Laravel“ erstellt .




Hallo, ich bin Valerio, ein Softwareentwickler aus Italien.

Dieses Handbuch richtet sich an alle PHP-Entwickler, die bereits Online-Anwendungen mit echten Benutzern haben, aber kein tieferes Verständnis dafür haben, wie die Skalierbarkeit in ihrem System mithilfe von Laravel-Warteschlangen implementiert (oder erheblich verbessert) werden kann. Ich habe Laravel zum ersten Mal Ende 2013 zu Beginn der 5. Version des Frameworks kennengelernt. Damals war ich noch kein Entwickler, der an ernsthaften Projekten beteiligt war, und einer der Aspekte moderner Frameworks, insbesondere in Laravel, der mir am unverständlichsten erschien, waren Warteschlangen.

Als ich die Dokumentation las, vermutete ich das Potenzial, aber ohne echte Entwicklungserfahrung blieb dies alles nur auf der Ebene der Theorien in meinem Kopf.
Heute bin ich der Schöpfer Inspector.dev- das Echtzeit-Dashboard , das jede Stunde Tausende von Aufgaben ausführt, und ich verstehe diese Architektur viel besser als zuvor.



In diesem Artikel werde ich Ihnen erklären, wie ich Warteschlangen und Aufgaben entdeckt habe und welche Konfigurationen mir geholfen haben, große Datenmengen in Echtzeit zu verarbeiten und gleichzeitig Serverressourcen zu sparen.

Einführung


Wenn eine PHP-Anwendung eine eingehende http-Anfrage empfängt, wird unser Code Schritt für Schritt nacheinander ausgeführt, bis die Anfrage abgeschlossen ist und eine Antwort an den Client (z. B. den Browser des Benutzers) zurückgegeben wird. Dieses synchrone Verhalten ist wirklich intuitiv, vorhersehbar und leicht zu verstehen. Ich sende eine http-Anfrage an den Endpunkt, die Anwendung extrahiert Daten aus der Datenbank, konvertiert sie in das entsprechende Format, führt einige zusätzliche Aufgaben aus und sendet sie zurück. Alles geschieht linear.

Aufgaben und Warteschlangen führen ein asynchrones Verhalten ein, das diesen Zeilenfluss verletzt. Deshalb kamen mir diese Funktionen zunächst etwas seltsam vor.
Manchmal kann eine eingehende http-Anforderung jedoch einen Zyklus zeitaufwändiger Aufgaben auslösen, z. B. das Senden von E-Mail-Benachrichtigungen an alle Teammitglieder. Dies kann das Senden von sechs oder zehn E-Mails bedeuten, was vier oder fünf Sekunden dauern kann. Daher muss der Benutzer jedes Mal, wenn er auf die entsprechende Schaltfläche klickt, fünf Sekunden warten, bevor er die Anwendung weiter verwenden kann.

Je mehr die Anwendung wächst, desto schlimmer wird dieses Problem.

Was ist eine Aufgabe?


Ein Job ist eine Klasse, die die Handle- Methode implementiert , die die Logik enthält, die asynchron ausgeführt werden soll.

<?php

use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Bus\Queueable;


class CallExternalAPI implements ShouldQueue
{
    use Dispatchable,
        InteractsWithQueue,
        Queueable;
        
    /**
     * @var string
     */
    protected $url;

    /**
     *    .
     *
     * @param array $Data
     */
    public function __construct($url)
    {
        $this->url = $url;
    }
    

    /**
     *  ,   .
     *
     * @return void
     * @throws \Throwable
     */
    public function handle()
    {
        file_get_contents($this->url);
    }
}

Wie oben erwähnt, besteht der Hauptgrund für das Abschließen eines Codeteils in Job darin, eine zeitaufwändige Aufgabe auszuführen, ohne den Benutzer zu zwingen, auf den Abschluss zu warten.

Was verstehen wir unter „mühsamen Aufgaben“?


Dies ist eine natürliche Frage. Das Senden von E-Mails ist das häufigste Beispiel für Artikel, die sich mit Warteschlangen befassen. Ich möchte Ihnen jedoch die tatsächlichen Erfahrungen mit meinen Aufgaben erläutern.

Als Product Owner ist es mir sehr wichtig, die Reiseinformationen der Benutzer mit unseren Marketing- und Kundensupport-Tools zu synchronisieren. Basierend auf Benutzeraktionen aktualisieren wir Benutzerinformationen in verschiedenen externen Softwareprogrammen über API (oder externe http-Aufrufe) für Service- und Marketingzwecke.
Einer der am häufigsten verwendeten Endpunkte in meiner Anwendung kann vor Abschluss 10 E-Mails senden und 3 http-Anrufe an externe Dienste tätigen. Kein Benutzer wird so lange warten - höchstwahrscheinlich hören alle einfach auf, meine Anwendung zu verwenden.

Dank der Warteschlangen kann ich alle diese Aufgaben in den zugewiesenen Klassen kapseln, die für die Ausführung ihrer Arbeit erforderlichen Informationen an den Konstruktor übertragen und planen, dass sie zu einem späteren Zeitpunkt im Hintergrund abgeschlossen werden, damit mein Controller die Antwort sofort zurückgeben kann.

<?php

class ProjectController 
{
    public function store(Request $request)
    {
        $project = Project::create($request->all());
        
        //  NotifyMembers, TagUserActive, NotifyToProveSource 
        //   ,     
        Notification::queue(new NotifyMembers($project->owners));
        $this->dispatch(new TagUserAsActive($project->owners));
        $this->dispatch(new NotifyToProveSource($project->owners));
        
        return $project;
    }
}

Ich muss nicht auf den Abschluss all dieser Prozesse warten, bevor ich die Antwort zurückschicke. Im Gegenteil, die Wartezeit entspricht der Zeit, die erforderlich ist, um sie in der Warteschlange zu veröffentlichen. Und das bedeutet den Unterschied zwischen 10 Sekunden und 10 Millisekunden !!!

Wer führt diese Aufgaben aus, nachdem er sie an die Warteschlange gesendet hat?


Dies ist die klassische Publisher / Consumer- Architektur . Wir haben unsere Aufgaben bereits in der Warteschlange des Controllers veröffentlicht. Jetzt werden wir verstehen, wie die Warteschlange verwendet wird, und schließlich werden die Aufgaben ausgeführt.



Um die Warteschlange zu verwenden, müssen wir einen der beliebtesten Handwerkerbefehle ausführen:

php artisan queue:work

In der Dokumentation heißt es:
Laravel enthält einen Warteschlangenarbeiter, der neue Aufgaben verarbeitet, wenn sie in die Warteschlange gestellt werden.

Großartig! Laravel bietet eine vorgefertigte Schnittstelle für Warteschlangenaufgaben und einen gebrauchsfertigen Befehl zum Abrufen von Aufgaben aus der Warteschlange und zum Ausführen ihres Codes im Hintergrund.

Supervisor-Rolle


Am Anfang war es eine andere „seltsame Sache“. Ich denke, es ist normal, neue Dinge zu entdecken. Nach dieser Schulungsphase schreibe ich diese Artikel, um meine Fähigkeiten zu organisieren und gleichzeitig anderen Entwicklern zu helfen, ihr Wissen zu erweitern.

Wenn die Aufgabe beim Auslösen einer Ausnahme fehlschlägt, beendet das Team queue:workseine Arbeit.

Damit ein Prozess queue:workkontinuierlich ausgeführt werden kann (was Ihre Warteschlangen beansprucht), müssen Sie einen Prozessmonitor wie Supervisor verwenden, um sicherzustellen, dass der Befehl auch dann queue:worknicht nicht mehr funktioniert, wenn die Task eine Ausnahme auslöst. Supervisor startet den Befehl nach dem Absturz neu und startet wieder von der nächsten Aufgabe, die auslösende Ausnahme beiseite legen.

Aufgaben werden im Hintergrund auf Ihrem Server ausgeführt und sind nicht mehr von der HTTP-Anforderung abhängig. Dies führt einige Änderungen ein, die ich bei der Implementierung des Task-Codes berücksichtigen musste.

Hier sind die wichtigsten, auf die ich achten werde:

Wie kann ich feststellen, dass der Aufgabencode nicht funktioniert?


Wenn Sie im Hintergrund ausgeführt werden, können Sie nicht sofort feststellen, dass Ihre Aufgabe Fehler verursacht.
Sie erhalten kein sofortiges Feedback mehr, z. B. wenn Sie eine http-Anfrage von Ihrem Browser aus stellen. Wenn die Aufgabe fehlschlägt, wird sie still ausgeführt und niemand wird es bemerken.

Erwägen Sie die Integration eines Echtzeit-Überwachungstools wie des Inspektors, um jeden Fehler aufzudecken.

Sie haben keine http-Anfrage


Es gibt keine HTTP-Anforderung mehr. Ihr Code wird von cli ausgeführt.

Wenn Sie Abfrageparameter benötigen, um Ihre Aufgaben abzuschließen, müssen Sie diese zur späteren Verwendung zur Laufzeit an den Aufgabenkonstruktor übergeben:

<?php

//   

class TagUserJob implements ShouldQueue
{
    public $data;
    
    public function __construct(array $data)
    {
        $this->data = $data;
    }
}

//       

$this->dispatch(new TagUserJob($request->all()));

Sie wissen nicht, wer sich angemeldet hat


Es gibt keine Sitzung mehr . Auf die gleiche Weise kennen Sie die Identität des angemeldeten Benutzers nicht. Wenn Sie also Informationen über den Benutzer benötigen, um die Aufgabe abzuschließen, müssen Sie das Benutzerobjekt an den Aufgabenkonstruktor übergeben:

<?php

//   
class TagUserJob implements ShouldQueue
{
    public $user;
    
    public function __construct(User $user)
    {
        $this->user= $user;
    }
}

//       
$this->dispatch(new TagUserJob($request->user()));

Verstehen, wie man skaliert


Leider reicht dies in vielen Fällen nicht aus. Die Verwendung einer Leitung und eines Verbrauchers kann bald unbrauchbar werden.

Warteschlangen sind FIFO-Puffer ("first in, first out"). Wenn Sie viele Aufgaben geplant haben, möglicherweise sogar verschiedene Arten, müssen diese warten, bis andere ihre Aufgaben erledigt haben, bevor sie sie erledigen.

Es gibt zwei Möglichkeiten zur Skalierung:

Mehrere Konsumenten pro Warteschlange.



Somit werden jeweils fünf Aufgaben aus der Warteschlange entfernt, wodurch die Verarbeitung der Warteschlange beschleunigt wird.

Spezialwarteschlangen

Sie können auch spezifische Warteschlangen für jeden Aufgabentyp erstellen, der mit einem dedizierten Consumer für jede Warteschlange gestartet werden soll.



Somit wird jede Warteschlange unabhängig verarbeitet, ohne auf die Ausführung anderer Arten von Aufgaben zu warten.

Horizont


Laravel Horizon ist ein Warteschlangenmanager, mit dem Sie die volle Kontrolle darüber haben, wie viele Warteschlangen Sie einrichten möchten, und die Möglichkeit haben, Verbraucher zu organisieren. So können Entwickler diese beiden Strategien kombinieren und diejenige implementieren, die Ihren Skalierbarkeitsanforderungen entspricht.

Der Start erfolgt über den PHP Artisan Horizon anstelle der PHP Artisan Queue: Work . Dieser Befehl durchsucht Ihre Konfigurationsdatei horizon.phpund startet je nach Konfiguration eine Reihe von Warteschlangenarbeitern:

<?php

'production' => [
    'supervisor-1' => [
        'connection' => "redis",
        'queue' => ['adveritisement', 'logs', 'phones'],
        'processes' => 9,
        'tries' => 3,
        'balance' => 'simple', //   simple, auto  null

    ]
]

Im obigen Beispiel startet Horizon drei Warteschlangen mit drei Prozessen, die zur Verarbeitung jeder Warteschlange zugewiesen sind. Wie in der Laravel-Dokumentation erwähnt, ermöglicht der codegesteuerte Ansatz von Horizon, dass meine Konfiguration in einem Versionskontrollsystem verbleibt, in dem mein Team zusammenarbeiten kann. Dies ist die perfekte Lösung mit einem CI-Tool.

Lesen Sie diesen wunderbaren Artikel , um die Bedeutung von Konfigurationsparametern im Detail herauszufinden .

Meine eigene Konfiguration



<?php

'production' => [
    'supervisor-1' => [
        'connection' => 'redis',
        'queue' => ['default', 'ingest', 'notifications'],
        'balance' => 'auto',
        'processes' => 15,
        'tries' => 3,
    ],
]

Der Inspektor verwendet hauptsächlich drei Warteschlangen:

  • Aufnahme für Prozesse zur Analyse von Daten aus externen Anwendungen;
  • Benachrichtigungen werden verwendet, um Benachrichtigungen sofort zu planen, wenn beim Empfang von Daten ein Fehler festgestellt wird.
  • Die Standardeinstellung wird für andere Aufgaben verwendet, die ich nicht mit Aufnahme- und Benachrichtigungsprozessen mischen möchte .

Durch die Verwendung von balance=autoHorizon versteht er, dass die maximale Anzahl aktivierter Prozesse 15 beträgt, die je nach Laden der Warteschlangen dynamisch verteilt werden.

Wenn die Warteschlangen leer sind, unterstützt Horizon einen aktiven Prozess für jede Warteschlange, sodass der Verbraucher die Warteschlange sofort verarbeiten kann, wenn eine Aufgabe geplant ist.

Schlussbemerkungen


Die gleichzeitige Ausführung im Hintergrund kann viele andere unvorhersehbare Fehler verursachen, z. B. MySQL "Lock Timeed Out" und viele andere Designprobleme. Lesen Sie hier mehr .

Ich hoffe, dieser Artikel hat Ihnen geholfen, Warteschlangen und Aufgaben sicherer zu verwenden. Wenn Sie mehr über uns erfahren möchten, besuchen Sie unsere Website unter www.inspector.dev.

Zuvor hier veröffentlicht www.inspector.dev/what-worked-for-me-using-laravel-queues-from-the-basics-to -Horizont



Steigen Sie in den Kurs ein und erhalten Sie einen Rabatt.



All Articles