Powrót do strony głównej

Współbieżność w Laravel z TrueAsync — przewodnik techniczny

Artykuł opisuje integrację Laravel z TrueAsync w celu osiągnięcia prawdziwej współbieżności. Szczegółowo omówiono mechanizmy izolacji kontekstowej, adaptacji usług stateful i pracy z PDO Pool. Rozwiązanie pozwala obsługiwać setki zapytań w jednym wątku bez Swoole.

TrueAsync zamiast Swoole: architektura współbieżności w Laravel
Advertisement 728x90

# TrueAsync w Laravel: architektura prawdziwej konkurencyjności bez Swoole

Tradycyjne rozwiązania dla asynchroniczności w PHP, takie jak Swoole czy RoadRunner, wymagają głębokiej modyfikacji kodu i wprowadzenia kolorowania funkcji. TrueAsync proponuje zupełnie inny podejście — modyfikację jądra Zend dla przezroczystej asynchroniczności. Rozłożymy na części pierwsze, jak dostosować Laravel do tego modelu, pokonując problemy usług stateful i zapewniając konkurencyjne przetwarzanie operacji związanych z wejściem/wyjściem.

Podstawy TrueAsync: jak działa przezroczysta asynchroniczność

TrueAsync to alternatywne jądro PHP, które modyfikuje Zend Engine, biblioteki I/O i pracę z bazami danych. Jego kluczową zaletą jest brak problemu kolorowania funkcji. W przeciwieństwie do Node.js czy Python asyncio, zwykłe funkcje PHP (PDO::query(), file_get_contents()) automatycznie oddają sterowanie planistowi w momencie oczekiwania na I/O. Korutiny są wstrzymywane na poziomie funkcji C, co pozwala na obsługę setek żądań w jednym wątku.

Ważne jest zrozumienie: to nie wielowątkowość. Model opiera się na kooperatywnym wielozadaniowości — przełączaniu kontekstów podczas oczekiwania na zewnętrzne zasoby. Dla zadań związanych z CPU (ciężkie obliczenia) rozwiązanie nie jest efektywne, ale idealnie nadaje się do typowych scenariuszy webowych z zapytaniami do baz danych i API. Przykład podstawowego użycia:

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);

Problem usług stateful w Laravel

Laravel został pierwotnie zaprojektowany pod model FPM, gdzie każde żądanie jest obsługiwane w izolowanym procesie. Usługi stateful (AuthManager, Session, Router) przechowują stan w właściwościach obiektów, które są niszczone po obsłużeniu żądania. W trybie async proces żyje stale, co prowadzi do wycieków stanu:

// 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

Statyczne właściwości (Model::$dispatcher, Facade::$resolvedInstance) i usługi w kontenerze IoC stają się wspólne dla wszystkich żądań. Octane rozwiązuje problem przez klonowanie kontenera, ale to generuje narzut i nie obejmuje statycznych właściwości.

Izolacja kontekstowa: architektura rozwiązania

TrueAsync udostępnia current_context() — magazyn danych powiązany z cyklem życia żądania. Po zakończeniu korutiny kontekst jest automatycznie czyszczony. Architektura dzieli Laravel na dwie strefy:

Google AdInline article slot
  • Statyczne jądro: router, konfiguracja, kontener IoC. Ładowane raz, wspólne dla wszystkich żądań.
  • Usługi per-żądanie: Auth, Session, Cookie. Tworzone w kontekście każdej korutiny.

Kluczowy mechanizm to przechwycenie rozwiązywania usług przez 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, ...);
}

Klucze kontekstowe przez enum i rozwiązanie dla Facades

Aby uniknąć kolizji i zapewnić bezpieczeństwo dostępu, użyto kluczy enum:

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

To daje:

Google AdInline article slot
  • Izolację dostępu (tylko właściciel enum może czytać/pisać dane)
  • Gwarancję braku kolizji (nawet przy identycznych wartościach stringowych)
  • Obsługę statycznej analizy (PHPStan/IDE śledzą użycie)

Fasady wymagały osobnego rozwiązania z powodu buforowania resolvedInstance w statycznej tablicy. Wdrożono ScopedServiceProxy:

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

Proxy przekierowuje wywołania do kontekstu bieżącej korutiny, zachowując bufor na poziomie proxy.

Krytyczne adaptacje i PDO Pool

Po bazowej izolacji zidentyfikowano usługi przechowujące stan per-żądanie wewnątrz siebie. Wymagały specjalistycznych wersji async:

  • AsyncRouter (izolacja bieżącej trasy)
  • AsyncDispatcher (odroczone zdarzenia per-korutina)
  • AsyncTranslator (lokalizacja tłumaczeń)
  • AsyncViewFactory (View::share())
  • AsyncConfig (zmiany konfiguracji w runtime)
  • AsyncDatabaseSessionHandler (atomowy zapis sesji)
  • Async\*Connection (licznik transakcji)

Szczególna uwaga na pracę z bazami danych. Bez puli połączeń jednoczesne zapytania od korutin psują protokół PostgreSQL. TrueAsync rozwiązuje to wbudowanym 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,
]);

Pula dynamicznie dystrybuuje połączenia: po otrzymaniu wyniku SELECT połączenie wraca do puli, obsługując więcej korutin niż maksymalny rozmiar puli. Dla transakcji połączenie jest blokowane do commit/rollback.

Co ważne

  • TrueAsync eliminuje problem kolorowania funkcji przez modyfikację jądra PHP
  • Usługi stateful Laravel wymagają izolacji kontekstowej przez current_context()
  • PDO Pool jest kluczowy dla poprawnej pracy z bazami danych w środowisku konkurencyjnym
  • Klucze enum zapewniają bezpieczeństwo i śledzalność danych kontekstowych
  • Rozwiązanie nie zastępuje wielowątkowości dla zadań związanych z CPU

— Editorial Team

Advertisement 728x90

Czytaj dalej