PHP на сървъра: FPM, Swoole, workers и event loop
В учебниците често остава „качи index.php“, а в production PHP почти винаги е мениджър на процеси + уеб сървър отпред. Тук са разделени четири идеи, които обикновено се смесват:
- Класическа заявка/отговор — PHP-FPM (или
mod_php): кратък живот на един HTTP заявка върху работник. - Дългоживеещи приложения — Swoole/OpenSwoole, RoadRunner, FrankenPHP (worker): същият PHP работник обслужва много заявки; споделената памет и статичните кешове стават реален проблем.
- Библиотеки с event loop — ReactPHP, AMPHP, Revolt: кооперативна многозадачност; отлично за I/O, опасно при блокиращи извиквания.
- CLI / cron —
phpза скриптове, опашки, миграции — не е уеб модел, но същият език с други ограничения.
Нито един вариант не е „новият PHP“ — това са различни договори за хостинг. Изборът зависи от трафика, екипа и толеранса към операционна сложност.
Съдържание
- PHP-FPM + nginx (или Apache като прокси)
- Apache
mod_php(вграден) - CLI PHP (cron, workers, Artisan)
- Swoole / OpenSwoole / Laravel Octane
- RoadRunner
- FrankenPHP
- Event loop: ReactPHP, AMPHP, Revolt
- Сравнение: кога какво
- Течове на памет: общ чеклист
PHP-FPM + nginx (или Apache като reverse proxy)
Какво се случва
- nginx прекъсва TLS и обслужва статика.
- За
*.phpзаявката отива към PHP-FPM през FastCGI (Unix сокет или TCP). - FPM взима работник от пула. Той изпълнява bootstrap (
public/index.phpв Laravel), връща отговора и се връща в пула (или приключва след N заявки — вижтеpm.max_requests).
Не разчитайте глобалните променливи да „преживеят“ следващата HTTP заявка (дори Opcache да държи байткода топъл).
Плюсове
- Изпитана комбинация с Laravel, Symfony, WordPress и др.
- Ясен модел: заявка влезе — отговор излезе; паметта се освобождава при рециклиране на работника.
- Лесно хоризонтално мащабиране: повече FPM работници и сървъри зад балансьор.
- По-малко изненади с типични Composer пакети.
Минуси
- Цена за bootstrap на всяка заявка (намалява се с Opcache, preload при настройка).
- Паралелизмът = броят работници; под натоварване опашката расте в backlog на FPM — настройвайте
pm.*. - Не е идеално за милиони дълги WebSocket върху една машина без допълнителен слой.
Рецепт (Ubuntu)
sudo apt update
sudo apt install php8.5-fpm
sudo systemctl enable --now php8.5-fpm
Фрагмент nginx:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.5-fpm.sock;
}
Пул /etc/php/8.5/fpm/pool.d/www.conf (настройте според RAM):
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35
pm.max_requests = 500
sudo systemctl reload php8.5-fpm
Памет (FPM)
pm.max_requests— рестартира работник след N заявки; евтина защита срещу малки течове в разширения.- Ако RSS на работниците расте безкрайно — профилирайте кода и разширенията; FPM само маскира симптоми.
Apache mod_php (вграден)
Apache изпълнява PHP в своите процеси/нишки (mod_php). Отново е предимно обхват на заявка, но архитектурата се различава от FPM.
Плюсове
- Просто при едносървърни конфигурации; наследство от споделен хостинг.
Минуси
- Жизненият цикъл на PHP е вързан за работниците на Apache — настройката се различава от nginx + FPM.
- По-рядко в съвременни Laravel деплойменти.
Кога: легаси или съзнателен избор; иначе обикновено FPM + nginx.
CLI PHP (cron, опашки, Artisan)
php artisan ..., php bin/console ..., консуматори на опашки, планировчик — без HTTP вход.
Плюсове
- Идеално за пакетна обработка, опашки, преиндексация, импорти.
Минуси
- Не замества уеб SAPI; различни таймаути, няма nginx буфери „на заявка“.
Рецепт
cd /var/www/app
php artisan schedule:work # често dev; в prod обикновено cron -> artisan schedule:run
php artisan queue:work redis --sleep=1 --tries=3
Памет: дълго живеещите консуматори на опашки са като мини сървъри — приложете чеклиста.
Swoole / OpenSwoole / Laravel Octane
Swoole (и общностният OpenSwoole) — дългоживеещ сървър: работниците живеят дълго, обработват много заявки и могат да ползват корутини за конкурентен I/O.
Laravel Octane може да управлява Swoole/RoadRunner/FrankenPHP — същата идея: заредете фреймуърка веднъж, обслужете много заявки.
Плюсове
- Висок throughput за I/O-bound код, който „сътрудничи“ на модела.
- WebSocket, таймери, асинхронни примитиви (с корутинно-съвместими API).
Минуси
- Глобалното състояние преживява заявки —
static, синглтони, кешове могат да „текат“ между потребители. - Не всички Composer пакети са безопасни (скрит блокиращ I/O, глобали, предположения за
$_SESSION). - По-труден деплой и дебъг — след релиз трябва reload на работниците.
Рецепт (илюстративен HTTP сървър)
В production обикновено Octane или интеграция във фреймуърка; тук е формата на Swoole:
pecl install swoole # или пакет php8.5-swoole в дистрибуцията
<?php
$http = new Swoole\Http\Server('127.0.0.1', 9501);
$http->on('request', function ($request, $response) {
$response->header('Content-Type', 'text/plain; charset=utf-8');
$response->end('ok');
});
$http->start();
Octane (Laravel)
composer require laravel/octane
php artisan octane:install # изберете swoole/roadrunner/frankenphp
php artisan octane:start
Памет / течове
- Настройте рециклиране на работници (вижте текущите опции на Octane/Swoole за вашата версия).
- Не съхранявайте данни за конкретен потребител в статични полета.
- След деплой — graceful restart (systemd,
octane:reloadи т.н.).
RoadRunner
RoadRunner е Go бинарник, който държи PHP работниците живи; комуникацията е през goridge (често с Octane или пакети на Spiral).
Плюсове
- Силна история за надзор на работници; Go слоят обслужва HTTP, gRPC, опашки и др.
- Ясно разделение между работници на приложението и крайните протоколи.
Минуси
- Още един компонент в релиза (RR + конфиг).
- Същите предупреждения за персистентно състояние като при Swoole.
Рецепт
Изтеглете бинарник от релизите на RoadRunner, добавете .rr.yaml и стартирайте:
./rr serve -c .rr.yaml
Командата на работника е в конфигурацията (често чрез Octane или генериран worker).
FrankenPHP
FrankenPHP е сървър на приложения върху Caddy с worker mode (дълъг PHP за много заявки) и съвременни пътища за HTTP/3.
Плюсове
- Един бинарник с Caddy; интересно за edge и worker режим.
Минуси
- По-нова екосистема — проверете съвместимостта на разширенията и матрицата Laravel/Octane за вашата версия.
Вижте официалните ръководства на FrankenPHP и инсталацията през Octane.
Event loop: ReactPHP, AMPHP, Revolt
Библиотеки като ReactPHP или AMPHP (върху Revolt / amphp/amp) реализират еднонишков event loop и неблокиращ I/O, когато използвате техните API.
Плюсове
- Отлично за I/O-bound агенти: много сокети, HTTP клиенти, DNS, таймери.
- Удобно за персонализирани протоколи, проксита, интеграционен „лепило“.
Минуси
- Всяко блокиращо извикване (
PDO::queryкъм отдалечена БД с типичен драйвер,sleep(),file_get_contents('http://...')) спира цикъла за всички. - Нужни са async клиенти или изнасяне на блокиращата работа в пул от нишки/дъщерни процеси (по-сложно).
Рецепт (набросък AMPHP)
composer require amphp/http-client revolt/event-loop
<?php
require __DIR__ . '/vendor/autoload.php';
use Amp\Http\Client\HttpClientBuilder;
use Amp\Http\Client\Request;
use function Amp\async;
$client = HttpClientBuilder::buildDefault();
$futures = [];
foreach (['https://example.com', 'https://php.net'] as $url) {
$futures[] = async(fn () => $client->request(new Request($url)));
}
foreach ($futures as $future) {
$response = $future->await();
echo $response->getStatus(), "\n";
}
Рецепт (набросък ReactPHP)
composer require react/event-loop react/http
<?php
require __DIR__ . '/vendor/autoload.php';
$loop = React\EventLoop\Loop::get();
$loop->addPeriodicTimer(1.0, fn () => print "tick\n");
$loop->run();
Сравнение: кога какво
| Среда | Подходящо за | Обикновено не | |--------|----------------|----------------| | PHP-FPM | Типични Laravel/Symfony сайтове и API | Милиони евтини duplex връзки на един възел без допълнителен слой | | Swoole / Octane | Висок QPS, WebSocket, корутинен I/O | Екипът не е готов за персистентно състояние; много блокиращи библиотеки | | RoadRunner | Надзор на работници + много протоколи на ръба | Нямате ресурс за още един бинарник в деплоя | | FrankenPHP | Caddy-центрични деплойменти, worker експерименти | Нужен е максимално консервативен стек | | ReactPHP / AMPHP | Персонализирани мрежови услуги, async лепило | Класически CRUD с блокиращ стек на фреймуърка |
Течове на памет: общ чеклист (особено при дългоживеещ PHP)
- Статични свойства и синглтони — само конфигурация, никога данни от заявка.
- Глобални кешове без граница — LRU с таван или Redis/Memcached вместо неограничени PHP масиви.
- Затваряния, държащи големи графи от обекти.
- Таймери / слушатели — отменяйте периодични таймери; махайте слушатели при teardown.
- Резултати от БД — четете на части; не трупайте огромни масиви в памет.
- Opcache не лекува течове — рециклирайте работници (
pm.max_requests, Octane reload), за да ограничите дрейф на ниво разширение.
Наблюдение:
# FPM: следете RSS на работниците под натоварване
ps aux | grep php-fpm
В кода:
<?php
echo memory_get_usage(true), " bytes\n";