Bandingkan PHP FPM, PHP PPM, Nginx Unit, React PHP dan RoadRunner



Pengujian dilakukan menggunakan Yandex Tank.
Symfony 4 dan PHP 7.2 digunakan sebagai aplikasi.
Tujuannya adalah untuk membandingkan karakteristik layanan pada beban yang berbeda dan menemukan opsi terbaik.
Untuk kenyamanan, semuanya dikumpulkan dalam wadah buruh pelabuhan dan diangkat menggunakan komposisi buruh pelabuhan.
Di bawah kucing ada banyak tabel dan grafik.

Kode sumber ada di sini .
Semua contoh perintah yang dijelaskan dalam artikel harus dijalankan dari direktori proyek.


Aplikasi


Aplikasi ini berjalan pada Symfony 4 dan PHP 7.2.


Jawaban hanya satu rute dan kembali:


  • nomor acak;
  • lingkungan
  • pid dari proses;
  • nama layanan yang digunakannya;
  • variabel php.ini.

Contoh jawaban:


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 dikonfigurasi di setiap wadah:


  • OPcache diaktifkan;
  • cache bootstrap yang dikonfigurasi menggunakan komposer;
  • Pengaturan php.ini sejalan dengan praktik terbaik Symfony .

Log ditulis dalam stderr:
/config/packages/prod/monolog.yaml


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

Tembolok ditulis dalam / 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; } } } ... 

Setiap komposisi buruh pelabuhan meluncurkan tiga kontainer utama:


  • Nginx - membalikkan server proxy;
  • Kode aplikasi aplikasi yang disiapkan dengan semua dependensi;
  • PHP FPM \ Nginx Unit \ Road Runner \ React PHP - server aplikasi.

Pemrosesan permintaan terbatas pada dua instance aplikasi (berdasarkan jumlah inti prosesor).


Layanan


PHP FPM


Manajer proses PHP. Ditulis dalam C.


Pro:


  • tidak perlu melacak memori;
  • tidak perlu mengubah apa pun dalam aplikasi.

Cons:


  • PHP harus menginisialisasi variabel untuk setiap permintaan.

Perintah untuk meluncurkan aplikasi dengan komposisi buruh pelabuhan:


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

PHP PPM


Manajer proses PHP. Itu ditulis dalam PHP.


Pro:


  • menginisialisasi variabel sekali dan kemudian menggunakannya;
  • tidak perlu mengubah apa pun dalam aplikasi (ada modul yang siap pakai untuk Symfony / Laravel, Zend, CakePHP).

Cons:


  • harus mengikuti memori.

Perintah untuk meluncurkan aplikasi dengan komposisi buruh pelabuhan:


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

Unit nginx


Server Aplikasi dari Tim Nginx. Ditulis dalam C.


Pro:


  • Anda dapat mengubah konfigurasi menggunakan API HTTP;
  • Anda dapat menjalankan banyak instance dari satu aplikasi secara bersamaan dengan berbagai konfigurasi dan versi bahasa;
  • tidak perlu melacak memori;
  • tidak perlu mengubah apa pun dalam aplikasi.

Cons:


  • PHP harus menginisialisasi variabel untuk setiap permintaan.

Untuk meneruskan variabel lingkungan dari file konfigurasi unit nginx, Anda perlu memperbaiki php.ini:


 ; Nginx Unit variables_order=E 

Perintah untuk meluncurkan aplikasi dengan komposisi buruh pelabuhan:


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

Bereaksi PHP


Perpustakaan untuk pemrograman acara. Itu ditulis dalam PHP.


Pro:


  • menggunakan perpustakaan, Anda dapat menulis server yang akan menginisialisasi variabel hanya sekali dan terus bekerja dengan mereka.

Cons:


  • Anda harus menulis kode untuk server;
  • perlu melacak memori.

Jika Anda menggunakan flag --reboot-kernel-after-request untuk pekerja, maka Symfony Kernel akan diinisialisasi ulang untuk setiap permintaan. Dengan pendekatan ini, Anda tidak perlu memonitor memori.


Kode Pekerja
 #!/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(); 

Perintah untuk meluncurkan aplikasi dengan komposisi buruh pelabuhan:


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

Pelari jalan


Server web dan pengelola proses PHP. Ditulis dalam Golang.


Pro:


  • Anda dapat menulis pekerja yang akan menginisialisasi variabel hanya sekali dan terus bekerja dengan mereka.

Cons:


  • Anda harus menulis kode untuk pekerja;
  • perlu melacak memori.

Jika Anda menggunakan flag --reboot-kernel-after-request untuk pekerja, maka Symfony Kernel akan diinisialisasi ulang untuk setiap permintaan. Dengan pendekatan ini, Anda tidak perlu memonitor memori.


Kode Pekerja
 #!/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); } } 

Perintah untuk meluncurkan aplikasi dengan komposisi buruh pelabuhan:


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

Pengujian


Pengujian dilakukan menggunakan Yandex Tank.
Aplikasi dan Yandex Tank berada di server virtual yang berbeda.


Fitur server virtual dengan aplikasi:
Virtualisasi : KVM
CPU : 2 core
RAM : 4096 MB
SSD : 50 GB
Koneksi : 100MBit
OS : CentOS 7 (64x)


Layanan yang Diuji:


  • php-fpm
  • php-ppm
  • nginx-unit
  • pelari jalan
  • road-runner-reboot (dengan flag --reboot-kernel-after-request )
  • reaksi-php
  • react-php-reboot (dengan flag --reboot-kernel-after-request )

Untuk pengujian 1000/10000 rps ditambahkan layanan php-fpm-80
Konfigurasi php-fpm digunakan untuk itu:


 pm = dynamic pm.max_children = 80 

Yandex Tank menentukan terlebih dahulu berapa kali perlu menembak target, dan tidak berhenti sampai kartrid habis. Tergantung pada kecepatan respons layanan, waktu pengujian mungkin lebih lama daripada yang ditentukan dalam konfigurasi pengujian. Karena itu, gambar dari berbagai layanan mungkin memiliki panjang yang berbeda. Semakin lambat layanan merespons, semakin lama jadwalnya.


Untuk setiap layanan dan konfigurasi Yandex Tank, hanya satu tes yang dilakukan. Karena itu, angka mungkin tidak akurat. Penting untuk mengevaluasi karakteristik layanan relatif satu sama lain.


100 rps


Konfigurasi Tank Phantom Yandex


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

Tautan Laporan Detail



Persentil waktu respons


95% (ms)90% (ms)80% (ms)50% (ms)HTTP OK (%)HTTP OK (jumlah)
php-fpm9.96.34.353.5910057030
php-ppm9.463.883.1610057030
nginx-unit116.64.433.6910057030
pelari jalan8.15.13.532.9210057030
road-runner-reboot128.65.33.8510057030
reaksi-php8.54.913.292.7410057030
bereaksi-php-reboot138.55.53,9510057030

Pemantauan


median cpu (%)cpu max (%)memory median (MB)memory max (MB)
php-fpm9.1512.58880.32907,97
php-ppm7.0813.68901.72913.80
nginx-unit9.5612.54923.02943.90
pelari jalan5.578.61992,711,001.46
road-runner-reboot9.1812.67848.43870.26
reaksi-php4.536.581,004.681,009.91
bereaksi-php-reboot9.6112.67885.92892.52

Grafik



Bagan 1.1 Rata-rata waktu respons per detik



Bagan 1.2 Rata-rata beban prosesor per detik



Bagan 1.3 Konsumsi memori rata-rata per detik


500 rps


Konfigurasi Tank Phantom Yandex


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

Tautan Laporan Detail



Persentil waktu respons


95% (ms)90% (ms)80% (ms)50% (ms)HTTP OK (%)HTTP OK (jumlah)
php-fpm138.45.33.69100285030
php-ppm1594.723.24100285030
nginx-unit1285.53.93100285030
pelari jalan9.663.712.83100285030
road-runner-reboot14117.14.45100285030
reaksi-php9.35.83.572.68100285030
bereaksi-php-reboot15127.24.21100285030

Pemantauan


median cpu (%)cpu max (%)memory median (MB)memory max (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
pelari jalan08/2406/281,035.861,044.58
road-runner-reboot46.2352.04939.63948.08
reaksi-php19.5723.421,049.831,060.26
bereaksi-php-reboot41.3047.89957.01958.56

Grafik



Bagan 2.1 Rata-rata waktu respons per detik



Bagan 2.2 Rata-rata beban prosesor per detik



Bagan 2.3 Konsumsi memori rata-rata per detik


1000 rps


Konfigurasi Tank Phantom Yandex


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

Tautan Laporan Detail



Persentil waktu respons


95% (ms)90% (ms)80% (ms)50% (ms)HTTP OK (%)HTTP OK (jumlah)
php-fpm1105011050904019580.6772627
php-fpm-8031501375116515299.8589895
php-ppm278527402685254510090030
nginx-unit9880602110090030
pelari jalan27157.13.2110090030
road-runner-reboot111011001085106010090030
reaksi-php23135.62.8610090030
bereaksi-php-reboot2824191110090030

Pemantauan


median cpu (%)cpu max (%)memory median (MB)memory max (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
pelari jalan42,9354.231,010.891,068.48
road-runner-reboot77.6485.66976.441,044,05
reaksi-php36.3946.311,018.031,088.23
bereaksi-php-reboot72.1181.81911.28961.62

Grafik



Bagan 3.1 Rata-rata waktu respons per detik



Bagan 3.2 Rata-rata waktu respons per detik (tanpa php-fpm, php-ppm, road-runner-reboot)



Bagan 3.3 Rata-rata beban prosesor per detik



Grafik 3.4 Konsumsi memori rata-rata per detik


10.000 rps


Konfigurasi Tank Phantom Yandex


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

Tautan Laporan Detail



Persentil waktu respons


95% (ms)90% (ms)80% (ms)50% (ms)HTTP OK (%)HTTP OK (jumlah)
php-fpm110501105011050188070.466317107
php-fpm-80326031401360114599.619448301
php-ppm2755273026952605100450015
nginx-unit102010101000980100450015
pelari jalan640630615580100450015
road-runner-reboot1130112011101085100450015
reaksi-php1890109010455899,9964.49996
bereaksi-php-reboot3480307012559199,72448753

Pemantauan


median cpu (%)cpu max (%)memory median (MB)memory max (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
pelari jalan73.4182,721,129.481,134.00
road-runner-reboot80.3286.29982.69984.80
reaksi-php73.7682.181,101.711,105,06
bereaksi-php-reboot85.7791.92975.85978.42


Bagan 4.1 Rata-rata Waktu Respons per Detik



Bagan 4.2 Rata-rata waktu respons per detik (tanpa php-fpm, php-ppm)



Bagan 4.3 Rata-rata beban prosesor per detik



Bagan 4.4 Konsumsi memori rata-rata per detik


Ringkasan


Berikut adalah grafik yang menunjukkan perubahan dalam karakteristik layanan tergantung pada beban. Saat melihat grafik, perlu dipertimbangkan bahwa tidak semua layanan menjawab 100% dari permintaan.



Bagan 5.1 95% persentil dari waktu respons



Bagan 5.2 persen persentil dari waktu respons (tanpa php-fpm)



Grafik 5.3 Beban CPU Maksimum



Bagan 5.4 Konsumsi memori maksimum


Solusi optimal (tanpa mengubah kode), menurut saya, adalah manajer proses Unit Nginx. Ini menunjukkan hasil yang baik dalam kecepatan respons dan mendapat dukungan dari perusahaan.


Dalam kasus apa pun, pendekatan pengembangan dan alat-alat perlu dipilih secara terpisah, tergantung pada beban kerja Anda, sumber daya server dan kemampuan pengembang.


UPD
Untuk pengujian 1000/10000 rps ditambahkan layanan php-fpm-80
Konfigurasi php-fpm digunakan untuk itu:


 pm = dynamic pm.max_children = 80 

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


All Articles