Tryb worker

Tryb worker uruchamia trwałe procesy PHP, które inicjalizują się raz, a następnie obsługują wiele żądań, dzięki czemu koszt uruchomienia PHP jest ponoszony tylko jeden raz, a nie przy każdym żądaniu. Zamiast burzyć i odbudowywać stan PHP przy każdym żądaniu, aplikacja ładuje swój autoloader, konfigurację i połączenia z bazą danych jeden raz i wykorzystuje je ponownie przez cały czas życia workera.

Jak to działa

  1. Włącz tryb worker. Ustaw WORKER_MODE_ENABLED=true i skieruj ENTRY_FILE na swój skrypt startowy. Włącza to tryb worker dla wszystkich workerów PHP w puli.
  2. Inicjalizacja raz. PHP uruchamia się i wykonuje zakres zewnętrzny jeden raz. Rejestracja autoloadera, wczytanie konfiguracji, połączenia z bazą danych oraz pozostały kod inicjalizacyjny wykonują się tylko jeden raz.
  3. Wejście w pętlę żądań. Wywołaj oxphp_worker(callback). OxPHP zaczyna przekazywać przychodzące żądania HTTP do Twojego callbacka.
  4. Reset między żądaniami. Superglobalne ($_GET, $_POST, $_SERVER, $_COOKIE, $_FILES, php://input), bufory wyjścia oraz nagłówki odpowiedzi są resetowane automatycznie. Miękki reset czyści stan poszczególnych żądań, zachowując zainicjalizowane zasoby w zakresie zewnętrznym.
  5. Zakres zewnętrzny jest zachowywany. Zmienne zdefiniowane przed oxphp_worker(), właściwości statyczne, połączenia z bazą danych i autoloadery pozostają dostępne we wszystkich żądaniach obsługiwanych przez danego workera.
Note

Tryb worker zmienia zachowanie routingu. Wszystkie żądania, które nie pasują do pliku statycznego na dysku, są przekazywane do workera zamiast zwracać 404. Szczegóły znajdziesz w Routing.

Konfiguracja

Zmienna Domyślnie Opis
WORKER_MODE_ENABLED false Włącza trwały tryb worker. Akceptuje true, 1, yes. Wymaga, aby ENTRY_FILE wskazywał na skrypt .php
ENTRY_FILE (nieustawione) Ścieżka do skryptu startowego workera. Rozwiązywana względem DOCUMENT_ROOT, gdy jest względna; segmenty .. i ścieżki bezwzględne są dozwolone (skrypty startowe workera znajdujące się poza publicznym katalogiem dokumentów to obsługiwany układ)
WORKER_MAX_MEMORY_MIB 0 Maksymalna pamięć PHP na workera w MiB przed recyklingiem. 0 = bez limitu
Migracja z WORKER_FILE

Starsza zmienna WORKER_FILE jest nadal parsowana (z ostrzeżeniem WARN przy starcie) i zachowuje się jak WORKER_MODE_ENABLED=true ENTRY_FILE=$WORKER_FILE. Nowe wdrożenia powinny używać jawnej pary; starsza forma zostanie usunięta w przyszłej wersji.

W przypadku recyklingu sterowanego przez aplikację wywołaj OxPHP\Server\Worker::scheduleExit() wewnątrz procedury obsługi żądania. Worker kończy działanie czysto po zakończeniu bieżącego żądania.

Pisanie skryptu workera

Skrypt workera składa się z dwóch części: zakresu zewnętrznego, który wykonuje się raz przy starcie, oraz callbacka przekazywanego do oxphp_worker(), który wykonuje się przy każdym żądaniu.

worker.php
<?php // Outer scope: runs once at startup require __DIR__ . '/../vendor/autoload.php'; $config = parse_ini_file(__DIR__ . '/../config/app.ini'); $db = new PDO($config['dsn'], $config['user'], $config['pass'], [ PDO::ATTR_PERSISTENT => true, ]); $app = new MyApp\Application($config, $db); // Request loop: runs for every request oxphp_worker(function () use ($app) { $app->handle(); }); // Shutdown: runs when the worker exits $app->terminate();

Co jest resetowane, a co zachowywane

OxPHP wykonuje miękki reset między żądaniami. Stan poszczególnych żądań jest czyszczony automatycznie, natomiast wszystko, co zostało zainicjalizowane w zakresie zewnętrznym, przetrwa przez cały czas życia workera.

Resetowane między żądaniami
  • Superglobalne$_GET, $_POST, $_SERVER, $_COOKIE, $_FILES i php://input są ponownie wypełniane danymi nowego żądania
  • Bufory wyjścia — wszystkie bufory wyjścia są opróżniane i czyszczone
  • Nagłówki odpowiedzi — kod statusu HTTP i nagłówki są resetowane do wartości domyślnych
  • Stan błędów — informacje o ostatnim błędzie (komunikat, plik, wiersz, typ) oraz status połączenia są czyszczone. Zarejestrowane przez użytkownika procedury obsługi błędów (set_error_handler()), procedury obsługi wyjątków (set_exception_handler()) oraz poziom error_reporting() są zachowywane między żądaniami
Zachowywane między żądaniami
  • Zmienne w zakresie zewnętrznym — wszystko, co zdefiniowano przed oxphp_worker() i przechwycono przez use
  • Właściwości statyczne — statyczne właściwości klas zachowują swoje wartości
  • Połączenia z bazą danych — PDO, MySQLi i inne trwałe połączenia pozostają otwarte
  • Autoloadery — zarejestrowane autoloadery (Composer, własne) pozostają aktywne
  • Załadowane klasy i funkcje — wszystkie wcześniej załadowane klasy, interfejsy, traity i funkcje

Recykling

Workery są automatycznie recyklowane (restartowane ze świeżym procesem PHP), gdy spełniony jest którykolwiek z poniższych warunków:

  • Przekroczenie maksymalnej pamięci — zużycie pamięci PHP przez workera przekracza WORKER_MAX_MEMORY_MIB MiB
  • Żądanie zakończenia przez aplikację — procedura obsługi wywołała Worker::scheduleExit(). Przydatne przy sterowanym przez aplikację hot reload, przeładowaniu na podstawie mtime plików lub ponownym wykonaniu inicjalizacji dla każdego żądania
  • Kolejne błędy — worker napotyka 3 kolejne niepowodzenia procedury obsługi (błędy krytyczne, przekroczenia limitu czasu lub nieobsłużone wyjątki). Zauważ, że wywołania exit()/die() nie są liczone jako niepowodzenia

Gdy worker jest recyklowany, proces PHP kończy działanie i uruchamia się nowy, ponownie wykonując zakres zewnętrzny skryptu workera. W przypadku zakończenia opartego na pamięci oraz zaplanowanego zakończenia bieżące żądanie kończy się normalnie, zanim worker zakończy działanie. W przypadku recyklingu opartego na błędach worker kończy działanie po nieudanym żądaniu.

Przeładowanie w trybie deweloperskim

Tryb worker przechowuje stan inicjalizacji (autoloader, kontener DI, połączenia z bazą danych) w pamięci, więc samo opcache.validate_timestamps=1 nie wystarczy, aby wychwycić zmiany w kodzie, który wykonał się w zakresie zewnętrznym. W pętlach deweloperskich są dwie opcje:

  • Recyklinguj każde żądanie. Wywołuj OxPHP\Server\Worker::current()->scheduleExit() na końcu każdego wywołania procedury obsługi (na przykład warunkowo, w zależności od zmiennej środowiskowej OXPHP_DEV). Bieżące żądanie kończy się normalnie, po czym worker kończy działanie i jest uruchamiany ponownie, wykonując zakres zewnętrzny na nowo. To zamienia zysk wydajnościowy trybu worker na semantykę przeładowania w stylu FPM. Jest to najprostsze i najbardziej niezawodne podejście przy aktywnej pracy deweloperskiej.
  • Utrzymuj workera rozgrzanego, przeładowuj procedury obsługi żądań. Całkowicie pomiń scheduleExit(), włącz opcache.validate_timestamps=1 i utrzymuj minimalną inicjalizację. Kod załadowany wewnątrz callbacka żądania zostanie odświeżony przez OPcache przy następnym żądaniu; kod załadowany raz w zakresie zewnętrznym — nie. Pełną listę zastrzeżeń znajdziesz w OPcache i JIT → Ustawienia deweloperskie.

Rozwiązywanie problemów

Żądania zawieszają się i nigdy się nie kończą

Jeśli oxphp_worker() nigdy nie zostanie wywołane w skrypcie startowym, żadne żądania nie są przekazywane i każde żądanie czeka w nieskończoność. Upewnij się, że Twój skrypt wywołuje oxphp_worker() bezwarunkowo w normalnej ścieżce kodu.

Stan przecieka między żądaniami

Zmienne zdefiniowane wewnątrz callbacka oxphp_worker() są sprzątane przez garbage collector PHP, ale właściwości statyczne i zmienne globalne zdefiniowane w zakresie zewnętrznym są zachowywane. Jeśli widzisz dane z jednego żądania pojawiające się w innym, sprawdź, czy nie ma właściwości statycznych lub zmiennych globalnych, które kumulują stan między wywołaniami.

Rozwiązanie: Resetuj stan statyczny jawnie na początku każdego callbacka żądania lub unikaj przechowywania stanu poszczególnych żądań w polach statycznych.

Worker natychmiast się recykluje (limit pamięci)

Limit pamięci workera jest sprawdzany po każdym żądaniu na podstawie zgłaszanego przez PHP zużycia pamięci. Jeśli faza inicjalizacji alokuje dużą ilość pamięci (np. ładowanie dużego cache'u), początkowy ślad pamięciowy może już być bliski limitu.

Rozwiązanie: Zwiększ WORKER_MAX_MEMORY_MIB lub odłóż duże alokacje do pierwszego żądania.

Worker natychmiast się recykluje (limit błędów)

Trzy kolejne niepowodzenia procedury obsługi wyzwalają recykling. Sprawdź logi aplikacji pod kątem wyjątków lub błędów krytycznych występujących w callbacku żądania.

Sprawdź: Poszukaj błędów w logu dostępu lub w ustrukturyzowanym wyjściu logów:

bash
docker logs <container> 2>&1 | grep '"level":"error"'
Połączenie z bazą danych zrywa się po okresie bezczynności

Jeśli Twój serwer bazy danych zamyka bezczynne połączenia, próby ponownego połączenia w kolejnym żądaniu mogą się nie powieść. Użyj puli połączeń, która obsługuje ponowne łączenie, lub przechwyć wyjątek i połącz się ponownie ręcznie.

Przykład dla Dockera

compose.yaml
services: app: image: ghcr.io/oxphp/oxphp:0.10.0 ports: - "8080:80" volumes: - ./src:/var/www/html environment: - DOCUMENT_ROOT=/var/www/html/public - WORKER_MODE_ENABLED=true - ENTRY_FILE=/var/www/html/worker.php - WORKER_MAX_MEMORY_MIB=128

API PHP

Introspekcja workera oraz punkt wejścia workera są udostępniane przez klasę OxPHP\Server\Worker.

php
<?php $worker = OxPHP\Server\Worker::current(); $worker->serve(function () { handleRequest(); });

Starsze funkcje globalne (oxphp_is_worker, oxphp_worker_id, oxphp_worker) są nadal dostępne i korzystają z tego samego stanu wewnętrznego. Nowy kod powinien preferować API klasy.

Klasa udostępnia również introspekcję w czasie działania przydatną przy łagodnym samodzielnym recyklingu, obserwowalności i kontrolach stanu:

Metoda Zwraca
Worker::isWorkerMode(): bool Czy serwer działa w trybie worker
$worker->id(): int Stabilne ID workera dla danego wątku
$worker->startTime(): float Uniksowy znacznik czasu uruchomienia tego workera
$worker->requestCount(): int Liczba żądań obsłużonych przez tego workera
$worker->memoryUsage(): int Bieżące memory_get_usage(true) dla tego workera
$worker->rss(): int Bieżący resident set size w bajtach (Linux/macOS)
$worker->maxMemoryBytes(): int Próg recyklingu — WORKER_MAX_MEMORY_MIB × 1 MiB lub 0, gdy bez limitu
$worker->isExitScheduled(): bool Czy scheduleExit() zostało wywołane
$worker->exitReason(): ?string null w trakcie działania; "scheduled", "max_memory" lub "error", gdy worker jest wyłączany

Pełne sygnatury i przykłady z rozwiązaniami znajdziesz w OxPHP\Server\Worker.

Przykłady PHP

Wykrywanie trybu worker

Użyj OxPHP\Server\Worker::isWorkerMode(), aby sprawdzić, czy bieżący proces działa w trybie worker. Jest to przydatne przy pisaniu kodu, który działa zarówno w trybie tradycyjnym, jak i w trybie worker.

php
<?php if (OxPHP\Server\Worker::isWorkerMode()) { // Reuse a persistent connection $redis = new Redis(); $redis->pconnect('redis', 6379); } else { // Traditional mode: connect per request $redis = new Redis(); $redis->connect('redis', 6379); }

Skrypt workera dla Symfony

worker.php
<?php use App\Kernel; require __DIR__ . '/../vendor/autoload.php'; $kernel = new Kernel('prod', false); $kernel->boot(); oxphp_worker(function () use ($kernel) { $request = Symfony\Component\HttpFoundation\Request::createFromGlobals(); $response = $kernel->handle($request); $response->send(); $kernel->terminate($request, $response); }); $kernel->shutdown();

Dobre praktyki

  • Ustaw WORKER_MAX_MEMORY_MIB (np. 128), aby przeciekający worker recyklował się automatycznie zamiast pochłaniać zasoby hosta. Połącz to z Worker::scheduleExit(), aby dodatkowo uzyskać recykling sterowany przez aplikację.
  • Unikaj przechowywania stanu poszczególnych żądań we właściwościach statycznych lub zmiennych globalnych. Ponieważ są one zachowywane między żądaniami, pozostały stan z jednego żądania może przeciec do innego.
  • Wcześnie zweryfikuj miękki reset. Dodaj Worker::current()->scheduleExit() do swojej procedury obsługi pod flagą deweloperską i przetestuj aplikację end-to-end. Pozwala to wychwycić błędy przeciekania stanu, zanim postawisz na długo żyjące workery.
  • Obsłuż limity czasu bezczynności bazy danych. Jeśli sterownik bazy danych rozłącza się po okresie bezczynności, przechwyć wyjątek i połącz się ponownie lub użyj puli połączeń, która automatycznie obsługuje ponowne łączenie.
  • Utrzymuj minimalny zakres zewnętrzny. Inicjalizuj tylko to, co naprawdę musi być zachowane: autoloadery, konfigurację i współdzielone usługi. Konfigurację specyficzną dla żądania odłóż do callbacka.

Zobacz też

  • Routing — jak tryb worker włącza się w routing adresów URL
  • Wczesna odpowiedź — wyślij odpowiedź natychmiast i kontynuuj przetwarzanie w tle
  • Funkcje PHP — pełna dokumentacja oxphp_worker(), oxphp_is_worker() i innych funkcji wbudowanych
  • Dokumentacja konfiguracji — pełna lista zmiennych środowiskowych