PHP FPM、PHP PPM、Nginx Unit、React PHP、RoadRunnerを比較してください



テストはYandex Tankを使用して実行されました。
Symfony 4とPHP 7.2がアプリケーションとして使用されました。
目標は、さまざまな負荷でのサービスの特性を比較し、最適なオプションを見つけることでした。
便宜上、すべてがdockerコンテナーに収集され、docker-composeを使用して発生します。
猫の下にはたくさんの表とグラフがあります。

ソースコードはこちらです。
この記事で説明されているコマンドの例はすべて、プロジェクトディレクトリから実行する必要があります。


アプリ


アプリケーションはSymfony 4およびPHP 7.2で実行されます。


1つのルートのみに応答し、戻ります。



回答例:


curl 'http://127.0.0.1:8000/' | python -m json.tool { "env": "prod", "type": "php-fpm", "pid": 8, "random_num": 37264, "php": { "version": "7.2.12", "date.timezone": "Europe/Paris", "display_errors": "", "error_log": "/proc/self/fd/2", "error_reporting": "32767", "log_errors": "1", "memory_limit": "256M", "opcache.enable": "1", "opcache.max_accelerated_files": "20000", "opcache.memory_consumption": "256", "opcache.validate_timestamps": "0", "realpath_cache_size": "4096K", "realpath_cache_ttl": "600", "short_open_tag": "" } } 

PHPは各コンテナーで構成されます。



ログはstderrに書き込まれます。
/config/packages/prod/monolog.yaml


 monolog: handlers: main: type: stream path: "php://stderr" level: error console: type: console 

キャッシュは/ dev / shmに書き込まれます。
/src/Kernel.php


 ... class Kernel extends BaseKernel { public function getCacheDir() { if ($this->environment === 'prod') { return '/dev/shm/symfony-app/cache/' . $this->environment; } else { return $this->getProjectDir() . '/var/cache/' . $this->environment; } } } ... 

各docker-composeは、3つの主要なコンテナーを起動します。



要求処理は、2つのアプリケーションインスタンスに制限されます(プロセッサコアの数による)。


サービス


PHP FPM


PHPプロセスマネージャー。 Cで書かれた


長所:



短所:



docker-composeでアプリケーションを起動するコマンド:


 cd docker/php-fpm && docker-compose up -d 

PHP PPM


PHPプロセスマネージャー。 PHPで書かれています。


長所:



短所:



docker-composeでアプリケーションを起動するコマンド:


 cd docker/php-ppm && docker-compose up -d 

Nginxユニット


Nginxチームのアプリケーションサーバー。 Cで書かれた


長所:



短所:



nginx-unit設定ファイルから環境変数を渡すには、php.iniを修正する必要があります。


 ; Nginx Unit variables_order=E 

docker-composeでアプリケーションを起動するコマンド:


 cd docker/nginx-unit && docker-compose up -d 

React PHP


イベントプログラミング用のライブラリ。 PHPで書かれています。


長所:



短所:



ワーカーに--reboot-kernel-after-requestフラグを使用すると、 リクエストごとにSymfonyカーネルが再初期化されます。 このアプローチでは、メモリを監視する必要はありません。


労働者コード
 #!/usr/bin/env php <?php use App\Kernel; use Symfony\Component\Debug\Debug; use Symfony\Component\HttpFoundation\Request; require __DIR__ . '/../config/bootstrap.php'; $env = $_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? 'dev'; $debug = (bool)($_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? ('prod' !== $env)); if ($debug) { umask(0000); Debug::enable(); } if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) { Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST); } if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) { Request::setTrustedHosts(explode(',', $trustedHosts)); } $loop = React\EventLoop\Factory::create(); $kernel = new Kernel($env, $debug); $kernel->boot(); $rebootKernelAfterRequest = in_array('--reboot-kernel-after-request', $argv); /** @var \Psr\Log\LoggerInterface $logger */ $logger = $kernel->getContainer()->get('logger'); $server = new React\Http\Server(function (Psr\Http\Message\ServerRequestInterface $request) use ($kernel, $logger, $rebootKernelAfterRequest) { $method = $request->getMethod(); $headers = $request->getHeaders(); $content = $request->getBody(); $post = []; if (in_array(strtoupper($method), ['POST', 'PUT', 'DELETE', 'PATCH']) && isset($headers['Content-Type']) && (0 === strpos($headers['Content-Type'], 'application/x-www-form-urlencoded')) ) { parse_str($content, $post); } $sfRequest = new Symfony\Component\HttpFoundation\Request( $request->getQueryParams(), $post, [], $request->getCookieParams(), $request->getUploadedFiles(), [], $content ); $sfRequest->setMethod($method); $sfRequest->headers->replace($headers); $sfRequest->server->set('REQUEST_URI', $request->getUri()); if (isset($headers['Host'])) { $sfRequest->server->set('SERVER_NAME', current($headers['Host'])); } try { $sfResponse = $kernel->handle($sfRequest); } catch (\Exception $e) { $logger->error('Internal server error', ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); $sfResponse = new \Symfony\Component\HttpFoundation\Response('Internal server error', 500); } catch (\Throwable $e) { $logger->error('Internal server error', ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); $sfResponse = new \Symfony\Component\HttpFoundation\Response('Internal server error', 500); } $kernel->terminate($sfRequest, $sfResponse); if ($rebootKernelAfterRequest) { $kernel->reboot(null); } return new React\Http\Response( $sfResponse->getStatusCode(), $sfResponse->headers->all(), $sfResponse->getContent() ); }); $server->on('error', function (\Exception $e) use ($logger) { $logger->error('Internal server error', ['error' => $e->getMessage(), 'trace' => $e->getTraceAsString()]); }); $socket = new React\Socket\Server('tcp://0.0.0.0:9000', $loop); $server->listen($socket); $logger->info('Server running', ['addr' => 'tcp://0.0.0.0:9000']); $loop->run(); 

docker-composeでアプリケーションを起動するコマンド:


 cd docker/react-php && docker-compose up -d --scale php=2 

ロードランナー


WebサーバーとPHPプロセスマネージャー。 Golangで書かれています。


長所:



短所:



ワーカーに--reboot-kernel-after-requestフラグを使用すると、 リクエストごとにSymfonyカーネルが再初期化されます。 このアプローチでは、メモリを監視する必要はありません。


労働者コード
 #!/usr/bin/env php <?php use App\Kernel; use Spiral\Goridge\SocketRelay; use Spiral\RoadRunner\PSR7Client; use Spiral\RoadRunner\Worker; use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory; use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory; use Symfony\Component\Debug\Debug; use Symfony\Component\HttpFoundation\Request; require __DIR__ . '/../config/bootstrap.php'; $env = $_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? 'dev'; $debug = (bool)($_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? ('prod' !== $env)); if ($debug) { umask(0000); Debug::enable(); } if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) { Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST); } if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) { Request::setTrustedHosts(explode(',', $trustedHosts)); } $kernel = new Kernel($env, $debug); $kernel->boot(); $rebootKernelAfterRequest = in_array('--reboot-kernel-after-request', $argv); $relay = new SocketRelay('/tmp/road-runner.sock', null, SocketRelay::SOCK_UNIX); $psr7 = new PSR7Client(new Worker($relay)); $httpFoundationFactory = new HttpFoundationFactory(); $diactorosFactory = new DiactorosFactory(); while ($req = $psr7->acceptRequest()) { try { $request = $httpFoundationFactory->createRequest($req); $response = $kernel->handle($request); $psr7->respond($diactorosFactory->createResponse($response)); $kernel->terminate($request, $response); if($rebootKernelAfterRequest) { $kernel->reboot(null); } } catch (\Throwable $e) { $psr7->getWorker()->error((string)$e); } } 

docker-composeでアプリケーションを起動するコマンド:


 cd docker/road-runner && docker-compose up -d 

テスト中


テストはYandex Tankを使用して実行されました。
アプリケーションとYandex Tankは異なる仮想サーバー上にありました。


アプリケーションを備えた仮想サーバーの機能:
仮想化 :KVM
CPU :2コア
RAM :4096 MB
SSD :50 GB
接続 :100MBit
OS :CentOS 7(64x)


テスト済みのサービス:



テスト用に1000/10000 rps追加サービスphp-fpm-80
php-fpm構成が使用されました。


 pm = dynamic pm.max_children = 80 

Yandexタンクは、ターゲットで何回撃つ必要があるかを事前に決定し、カートリッジがなくなるまで停止しません。 サービスの応答速度によっては、テスト構成で指定されているよりもテスト時間が長くなる場合があります。 このため、異なるサービスのグラフィックスの長さは異なる場合があります。 サービスの応答が遅いほど、スケジュールは長くなります。


Yandex Tankの各サービスと構成について、1つのテストのみが実施されました。 このため、数値は不正確になる場合があります。 サービスの特性を相互に評価することが重要でした。


100 rps


Phantom Yandexタンクの構成


 phantom: load_profile: load_type: rps schedule: line(1, 100, 60s) const(100, 540s) 

詳細レポートリンク



応答時間のパーセンタイル


95%(ミリ秒)90%(ミリ秒)80%(ミリ秒)50%(ミリ秒)HTTP OK(%)HTTP OK(カウント)
php-fpm9.96.34.353.5910057030
php-ppm9.463.883.1610057030
nginx-unit116.64.433.6910057030
ロードランナー8.15.13.532.9210057030
ロードランナーリブート128.65.33.8510057030
反応php8.54.913.292.7410057030
react-php-reboot138.55.53.9510057030

モニタリング


CPU中央値(%)CPU最大(%)メモリ中央値(MB)最大メモリ(MB)
php-fpm9.1512.58880.32907.97
php-ppm7.0813.68901.72913.80
nginx-unit9.5612.54923.02943.90
ロードランナー5.578.61992.711,001.46
ロードランナーリブート9.1812.67848.43870.26
反応php4.536.581,004.681,009.91
react-php-reboot9.6112.67885.92892.52

グラフ



図1.1 1秒あたりの平均応答時間



図1.2 1秒あたりの平均プロセッサ負荷



図1.3 1秒あたりの平均メモリ消費量


500 rps


Phantom Yandexタンクの構成


 phantom: load_profile: load_type: rps schedule: line(1, 500, 60s) const(500, 540s) 

詳細レポートリンク



応答時間のパーセンタイル


95%(ミリ秒)90%(ミリ秒)80%(ミリ秒)50%(ミリ秒)HTTP OK(%)HTTP OK(カウント)
php-fpm138.45.33.69100285030
php-ppm1594.723.24100285030
nginx-unit1285.53.93100285030
ロードランナー9.663.712.83100285030
ロードランナーリブート14117.14.45100285030
反応php9.35.83.572.68100285030
react-php-reboot15127.24.21100285030

モニタリング


CPU中央値(%)CPU最大(%)メモリ中央値(MB)最大メモリ(MB)
php-fpm41.6848.331,006.061,015.09
php-ppm33.9048.901,046.321,055.00
nginx-unit42.1347.921,006.671,015.73
ロードランナー08/2406/281,035.861,044.58
ロードランナーリブート46.2352.04939.63948.08
反応php19.5723.421,049.831,060.26
react-php-reboot41.3047.89957.01958.56

グラフ



図2.1 1秒あたりの平均応答時間



図2.2 1秒あたりの平均プロセッサ負荷



図2.3 1秒あたりの平均メモリ消費量


1000 rps


Phantom Yandexタンクの構成


 phantom: load_profile: load_type: rps schedule: line(1, 1000, 60s) const(1000, 60s) 

詳細レポートリンク



応答時間のパーセンタイル


95%(ミリ秒)90%(ミリ秒)80%(ミリ秒)50%(ミリ秒)HTTP OK(%)HTTP OK(カウント)
php-fpm1105011050904019580.6772627
php-fpm-8031501375116515299.8589895
php-ppm278527402685254510090030
nginx-unit9880602110090030
ロードランナー27157.13.2110090030
ロードランナーリブート111011001085106010090030
反応php23135.62.8610090030
react-php-reboot2824191110090030

モニタリング


CPU中央値(%)CPU最大(%)メモリ中央値(MB)最大メモリ(MB)
php-fpm12.6678.25990.161,006.56
php-fpm-8083.7891.28746.01937.24
php-ppm66.1691.201,088.741,102.92
nginx-unit78.1188.771,010.151,062.01
ロードランナー42.9354.231,010.891,068.48
ロードランナーリブート77.6485.66976.441,044.05
反応php36.3946.311,018.031,088.23
react-php-reboot72.1181.81911.28961.62

グラフ



図3.1 1秒あたりの平均応答時間



図3.2 1秒あたりの平均応答時間(php-fpm、php-ppm、road-runner-rebootなし)



図3.3 1秒あたりの平均プロセッサ負荷



図3.4 1秒あたりの平均メモリ消費量


10000 rps


Phantom Yandexタンクの構成


 phantom: load_profile: load_type: rps schedule: line(1, 10000, 30s) const(10000, 30s) 

詳細レポートリンク



応答時間のパーセンタイル


95%(ミリ秒)90%(ミリ秒)80%(ミリ秒)50%(ミリ秒)HTTP OK(%)HTTP OK(カウント)
php-fpm110501105011050188070.466317107
php-fpm-80326031401360114599.619448301
php-ppm2755273026952605100450015
nginx-unit102010101000980100450015
ロードランナー640630615580100450015
ロードランナーリブート1130112011101085100450015
反応php1890109010455899.996449996
react-php-reboot3480307012559199.72448753

モニタリング


CPU中央値(%)CPU最大(%)メモリ中央値(MB)最大メモリ(MB)
php-fpm5.5779.35984.47998.78
php-fpm-8085.0592.19936.64943.93
php-ppm66.8682.411,089.311,097.41
nginx-unit86.1493.941,067.711,069.52
ロードランナー73.4182.721,129.481,134.00
ロードランナーリブート80.3286.29982.69984.80
反応php73.7682.181,101.711,105.06
react-php-reboot85.7791.92975.85978.42


図4.1 1秒あたりの平均応答時間



図4.2 1秒あたりの平均応答時間(php-fpm、php-ppmなし)



図4.3 1秒あたりの平均プロセッサ負荷



図4.4 1秒あたりの平均メモリ消費量


まとめ


以下は、負荷に応じたサービスの特性の変化を示すグラフです。 チャートを表示するとき、すべてのサービスがリクエストの100%に応答したわけではないことに注意してください。



図5.1応答時間の95%パーセンタイル



図5.2応答時間の95%パーセンタイル(php-fpmなし)



グラフ5.3最大CPU負荷



図5.4最大メモリ消費


私の意見では、最適なソリューション(コードを変更せずに)はNginx Unitプロセスマネージャーです。 応答速度に良い結果を示し、会社のサポートがあります。


いずれの場合でも、ワークロード、サーバーリソース、開発者の能力に応じて、開発アプローチとツールを個別に選択する必要があります。


UPD
テスト用に1000/10000 rps追加サービスphp-fpm-80
php-fpm構成が使用されました。


 pm = dynamic pm.max_children = 80 


Source: https://habr.com/ru/post/J431818/


All Articles