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
- Włącz tryb worker. Ustaw
WORKER_MODE_ENABLED=truei skierujENTRY_FILEna swój skrypt startowy. Włącza to tryb worker dla wszystkich workerów PHP w puli. - 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.
- Wejście w pętlę żądań. Wywołaj
oxphp_worker(callback). OxPHP zaczyna przekazywać przychodzące żądania HTTP do Twojego callbacka. - 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. - 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.
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 |
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.
<?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.
- Superglobalne —
$_GET,$_POST,$_SERVER,$_COOKIE,$_FILESiphp://inputsą 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 poziomerror_reporting()są zachowywane między żądaniami
- Zmienne w zakresie zewnętrznym — wszystko, co zdefiniowano przed
oxphp_worker()i przechwycono przezuse - 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_MIBMiB - Żą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 środowiskowejOXPHP_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łączopcache.validate_timestamps=1i 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:
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
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=128API PHP
Introspekcja workera oraz punkt wejścia workera są udostępniane przez klasę OxPHP\Server\Worker.
<?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
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
<?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 zWorker::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