Zurück zur Startseite

Concurrency in Laravel mit TrueAsync — technischer Leitfaden

Der Artikel beschreibt die Integration von Laravel mit TrueAsync, um echte Concurrency zu erreichen. Mechanismen der kontextuellen Isolation, Anpassung zustandsbehafteter Dienste und Arbeit mit PDO Pool werden detailliert besprochen. Die Lösung ermöglicht die Bearbeitung von Hunderten von Anfragen in einem Thread ohne Swoole.

TrueAsync statt Swoole: Concurrency-Architektur in Laravel
Advertisement 728x90

TrueAsync in Laravel: Architektur für echte Parallelität ohne Swoole

Traditionelle Lösungen für Asynchronität in PHP, wie Swoole oder RoadRunner, erfordern umfangreiche Code-Änderungen und die Einführung von gefärbten Funktionen. TrueAsync bietet einen grundlegend anderen Ansatz – die Modifikation des Zend-Kerns für transparente Asynchronität. Wir erklären Schritt für Schritt, wie man Laravel an dieses Modell anpasst, Probleme mit zustandsbehafteten Diensten löst und parallele Verarbeitung von I/O-gebundenen Operationen ermöglicht.

Grundlagen von TrueAsync: So funktioniert transparente Asynchronität

TrueAsync ist ein alternativer PHP-Kern, der den Zend Engine, I/O-Bibliotheken und Datenbankoperationen modifiziert. Der entscheidende Vorteil: Das Problem der gefärbten Funktionen entfällt. Im Gegensatz zu Node.js oder Python asyncio geben Standard-PHP-Funktionen (PDO::query(), file_get_contents()) bei I/O-Wartezeiten automatisch die Kontrolle an den Scheduler ab. Coroutines hängen auf C-Funktions-Ebene ein und ermöglichen die Bearbeitung Hunderter Anfragen in einem einzigen Thread.

Wichtig zu verstehen: Das ist keine Multithreading-Lösung. Das Modell basiert auf kooperativem Multitasking – Kontextwechsel beim Warten auf externe Ressourcen. Es eignet sich nicht für CPU-intensive Aufgaben (schwere Berechnungen), ist aber ideal für typische Web-Szenarien mit Datenbank- und API-Aufrufen. Hier ein einfaches Beispiel:

Google AdInline article slot
use function Async\spawn;
use function Async\await;
use function Async\delay;

$a = spawn(function() {
    delay(100);
    return 'result A';
});

$b = spawn(function() {
    delay(100);
    return 'result B';
});

// Obschee time vypolneniya ~100ms
 echo await($a); 
echo await($b);

Das Problem der zustandsbehafteten Dienste in Laravel

Laravel wurde ursprünglich für das FPM-Modell entwickelt, bei dem jede Anfrage in einem isolierten Prozess bearbeitet wird. Zustandsbehaftete Dienste (AuthManager, Session, Router) speichern ihren Zustand in Objekteigenschaften, die nach der Anfragezerbearbeitung zerstört werden. Im Async-Modus lebt der Prozess unbefristet weiter, was zu Zustandslecks führt:

// Coroutine A: request from user_1
Auth::loginUsingId(1);
delay(200);

// Coroutine B: request from user_2  
Auth::loginUsingId(2);

// Return in korutinu A
echo Auth::id(); // Vozvraschaet 2 vmesto 1

Statische Eigenschaften (Model::$dispatcher, Facade::$resolvedInstance) und Dienste im IoC-Container werden über alle Anfragen hinweg geteilt. Octane umgeht das durch Klonen des Containers, was jedoch Overhead erzeugt und statische Eigenschaften nicht abdeckt.

Kontextuelle Isolation: Die Lösungsarchitektur

TrueAsync stellt current_context() bereit – einen Datenspeicher, der an den Anfragelebenszyklus gebunden ist. Bei Abschluss einer Coroutine wird der Kontext automatisch geleert. Die Architektur teilt Laravel in zwei Zonen auf:

Google AdInline article slot
  • Statischer Kern: Router, Config, IoC-Container. Einmal geladen, über alle Anfragen geteilt.
  • Pro-Anfrage-Dienste: Auth, Session, Cookie. In jedem Coroutine-Kontext erzeugt.

Der zentrale Mechanismus ist die Abfangung der Dienste-Auflösung über AsyncApplication::resolve():

protected function resolve($abstract, ...)
{
    if ($this->asyncMode && $this->isScoped($abstract)) {
        $ctx = current_context();
        
        if ($instance = $ctx->find($abstract)) {
            return $instance;
        }
        
        $instance = $this->buildFresh($abstract);
        $ctx->set($abstract, $instance);
        return $instance;
    }
    
    return parent::resolve($abstract, ...);
}

Kontextuelle Schlüssel über Enum und die Facade-Lösung

Um Kollisionen zu vermeiden und sicheren Zugriff zu gewährleisten, kommen Enum-Schlüssel zum Einsatz:

enum ScopedService: string
{
    case REQUEST     = 'request';
    case SESSION     = 'session';
    case AUTH        = 'auth';
    case AUTH_DRIVER = 'auth.driver';
    case COOKIE      = 'cookie';
}

Das bietet:

Google AdInline article slot
  • Zugriffs-Isolation (nur der Enum-Besitzer kann Daten lesen/schreiben)
  • Kollisionsfreie Garantien (auch bei identischen String-Werten)
  • Unterstützung für statische Analyse (PHPStan/IDE verfolgt die Nutzung)

Fassade erforderte eine separate Lösung wegen des Cachings von resolvedInstance in einem statischen Array. Hier kommt ScopedServiceProxy ins Spiel:

class ScopedServiceProxy
{
    public function __call($method, $args)
    {
        return ($this->resolver)()->$method(...$args);
    }
}

Der Proxy leitet Aufrufe an den Kontext der aktuellen Coroutine weiter, während der Cache auf Proxy-Ebene erhalten bleibt.

Kritische Anpassungen und PDO-Pool

Nach der grundlegenden Isolation wurden Dienste identifiziert, die intern pro-Anfrage-Zustand speichern und spezielle Async-Versionen benötigen:

  • AsyncRouter (Isolation der aktuellen Route)
  • AsyncDispatcher (ereignisverzögerte Events pro Coroutine)
  • AsyncTranslator (Übersetzungs-Locale)
  • AsyncViewFactory (View::share())
  • AsyncConfig (Laufzeit-Konfigurationsänderungen)
  • AsyncDatabaseSessionHandler (atomare Session-Schreibvorgänge)
  • Async*Connection (Transaktionszähler)

Besondere Aufmerksamkeit galt der Datenbankbehandlung. Ohne Connection Pool brechen gleichzeitige Coroutine-Abfragen das PostgreSQL-Protokoll. TrueAsync löst das mit einem integrierten PDO-Pool:

$pdo = new PDO($dsn, $user, $password, [
    PDO::ATTR_POOL_ENABLED              => true,
    PDO::ATTR_POOL_MIN                  => 0,
    PDO::ATTR_POOL_MAX                  => 10,
    PDO::ATTR_POOL_HEALTHCHECK_INTERVAL => 30,
]);

Der Pool verteilt Verbindungen dynamisch: Nach einem SELECT-Ergebnis kehrt die Verbindung in den Pool zurück und bedient mehr Coroutines als die maximale Pool-Größe. Bei Transaktionen bleibt die Verbindung bis commit/rollback gesperrt.

Wichtige Erkenntnisse

  • TrueAsync eliminiert gefärbte Funktionen durch PHP-Kern-Modifikationen
  • Zustandsbehaftete Laravel-Dienste erfordern kontextuelle Isolation über current_context()
  • Der PDO-Pool ist essenziell für korrekte Datenbanknutzung in parallelen Umgebungen
  • Enum-Schlüssel gewährleisten Sicherheit und Nachverfolgbarkeit kontextueller Daten
  • Die Lösung ersetzt kein Multithreading für CPU-intensive Aufgaben

— Editorial Team

Advertisement 728x90

Weiterlesen