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-fpm 9.9 6.3 4.35 3.59 100 57030
php-ppm 9.4 6 3.88 3.16 100 57030
nginx-unit 11 6.6 4.43 3.69 100 57030
ロードランナー 8.1 5.1 3.53 2.92 100 57030
ロードランナーリブート 12 8.6 5.3 3.85 100 57030
反応php 8.5 4.91 3.29 2.74 100 57030
react-php-reboot 13 8.5 5.5 3.95 100 57030


モニタリング



CPU中央値(%) CPU最大(%) メモリ中央値(MB) 最大メモリ(MB)
php-fpm 9.15 12.58 880.32 907.97
php-ppm 7.08 13.68 901.72 913.80
nginx-unit 9.56 12.54 923.02 943.90
ロードランナー 5.57 8.61 992.71 1,001.46
ロードランナーリブート 9.18 12.67 848.43 870.26
反応php 4.53 6.58 1,004.68 1,009.91
react-php-reboot 9.61 12.67 885.92 892.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-fpm 13 8.4 5.3 3.69 100 285030
php-ppm 15 9 4.72 3.24 100 285030
nginx-unit 12 8 5.5 3.93 100 285030
ロードランナー 9.6 6 3.71 2.83 100 285030
ロードランナーリブート 14 11 7.1 4.45 100 285030
反応php 9.3 5.8 3.57 2.68 100 285030
react-php-reboot 15 12 7.2 4.21 100 285030


モニタリング



CPU中央値(%) CPU最大(%) メモリ中央値(MB) 最大メモリ(MB)
php-fpm 41.68 48.33 1,006.06 1,015.09
php-ppm 33.90 48.90 1,046.32 1,055.00
nginx-unit 42.13 47.92 1,006.67 1,015.73
ロードランナー 08/24 06/28 1,035.86 1,044.58
ロードランナーリブート 46.23 52.04 939.63 948.08
反応php 19.57 23.42 1,049.83 1,060.26
react-php-reboot 41.30 47.89 957.01 958.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-fpm 11050 11050 9040 195 80.67 72627
php-fpm-80 3150 1375 1165 152 99.85 89895
php-ppm 2785 2740 2685 2545 100 90030
nginx-unit 98 80 60 21 100 90030
ロードランナー 27 15 7.1 3.21 100 90030
ロードランナーリブート 1110 1100 1085 1060 100 90030
反応php 23 13 5.6 2.86 100 90030
react-php-reboot 28 24 19 11 100 90030


モニタリング



CPU中央値(%) CPU最大(%) メモリ中央値(MB) 最大メモリ(MB)
php-fpm 12.66 78.25 990.16 1,006.56
php-fpm-80 83.78 91.28 746.01 937.24
php-ppm 66.16 91.20 1,088.74 1,102.92
nginx-unit 78.11 88.77 1,010.15 1,062.01
ロードランナー 42.93 54.23 1,010.89 1,068.48
ロードランナーリブート 77.64 85.66 976.44 1,044.05
反応php 36.39 46.31 1,018.03 1,088.23
react-php-reboot 72.11 81.81 911.28 961.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-fpm 11050 11050 11050 1880 70.466 317107
php-fpm-80 3260 3140 1360 1145 99.619 448301
php-ppm 2755 2730 2695 2605 100 450015
nginx-unit 1020 1010 1000 980 100 450015
ロードランナー 640 630 615 580 100 450015
ロードランナーリブート 1130 1120 1110 1085 100 450015
反応php 1890 1090 1045 58 99.996 449996
react-php-reboot 3480 3070 1255 91 99.72 448753


モニタリング



CPU中央値(%) CPU最大(%) メモリ中央値(MB) 最大メモリ(MB)
php-fpm 5.57 79.35 984.47 998.78
php-fpm-80 85.05 92.19 936.64 943.93
php-ppm 66.86 82.41 1,089.31 1,097.41
nginx-unit 86.14 93.94 1,067.71 1,069.52
ロードランナー 73.41 82.72 1,129.48 1,134.00
ロードランナーリブート 80.32 86.29 982.69 984.80
反応php 73.76 82.18 1,101.71 1,105.06
react-php-reboot 85.77 91.92 975.85 978.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
      
      






All Articles