قارن بين PHP FPM و PHP PPM و Nginx Unit و React PHP و RoadRunner



تم إجراء الاختبار باستخدام Yandex Tank.
تم استخدام Symfony 4 و PHP 7.2 كتطبيق.
كان الهدف هو مقارنة خصائص الخدمات بأحمال مختلفة وإيجاد الخيار الأفضل.
للراحة ، يتم جمع كل شيء في حاويات الإرساء ويتم رفعه باستخدام عامل الإرساء.
تحت القط هناك الكثير من الجداول والرسوم البيانية.

شفرة المصدر هنا .
يجب تنفيذ جميع أمثلة الأوامر الموضحة في المقالة من دليل المشروع.


التطبيق


يعمل التطبيق على Symfony 4 و PHP 7.2.


يجيب عن مسار واحد فقط ويعود:


  • رقم عشوائي
  • البيئة
  • معرف العملية
  • اسم الخدمة التي تعمل بها ؛
  • متغيرات php.ini.

الإجابة على سبيل المثال:


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 في كل حاوية:


  • يتم تمكين OPcache.
  • تكوين ذاكرة التخزين المؤقت bootstrap باستخدام الملحن.
  • تتوافق إعدادات php.ini مع أفضل ممارسات Symfony .

سجلات مكتوبة في 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; } } } ... 

يقوم كل عامل ميناء بإعداد ثلاثة حاويات رئيسية:


  • Nginx - الخادم الوكيل العكسي ؛
  • التطبيق - أعد رمز التطبيق مع جميع التبعيات.
  • PHP FPM \ Nginx Unit \ Road Runner \ React PHP - خادم التطبيق.

تقتصر معالجة الطلب على مثيلين للتطبيق (حسب عدد مراكز المعالج).


الخدمات


PHP FPM


مدير عملية PHP. مكتوب في C.


الايجابيات:


  • لا حاجة لتتبع الذاكرة ؛
  • لا حاجة لتغيير أي شيء في التطبيق.

سلبيات:


  • يجب على PHP تهيئة المتغيرات لكل طلب.

أمر تشغيل التطبيق باستخدام عامل التهيئة:


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

PHP PPM


مدير عملية PHP. هو مكتوب في PHP.


الايجابيات:


  • تهيئة المتغيرات مرة واحدة ثم استخدامها ؛
  • لا حاجة لتغيير أي شيء في التطبيق (هناك وحدات جاهزة لـ Symfony / Laravel و Zend و CakePHP).

سلبيات:


  • بحاجة إلى متابعة الذاكرة.

أمر تشغيل التطبيق باستخدام عامل التهيئة:


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

وحدة Nginx


خادم التطبيق من فريق Nginx. مكتوب في C.


الايجابيات:


  • يمكنك تغيير التكوين باستخدام HTTP API ؛
  • يمكنك تشغيل مثيلات متعددة لتطبيق واحد في وقت واحد مع تكوينات مختلفة وإصدارات اللغة ؛
  • لا حاجة لتتبع الذاكرة ؛
  • لا حاجة لتغيير أي شيء في التطبيق.

سلبيات:


  • يجب على PHP تهيئة المتغيرات لكل طلب.

لتمرير متغيرات البيئة من ملف تكوين nginx-unit ، تحتاج إلى إصلاح php.ini:


 ; Nginx Unit variables_order=E 

أمر تشغيل التطبيق باستخدام عامل التهيئة:


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

تتفاعل PHP


مكتبة لبرمجة الحدث. هو مكتوب في PHP.


الايجابيات:


  • باستخدام المكتبة ، يمكنك كتابة خادم يقوم بتهيئة المتغيرات مرة واحدة فقط ومواصلة العمل معها.

سلبيات:


  • يجب عليك كتابة رمز للخادم ؛
  • بحاجة إلى تتبع الذاكرة.

إذا استخدمت علامة --reboot-kernel-after-request للعامل ، فسيتم إعادة تهيئة Symfony Kernel لكل طلب. مع هذا النهج ، لا تحتاج إلى مراقبة الذاكرة.


كود العامل
 #!/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(); 

أمر تشغيل التطبيق باستخدام عامل التهيئة:


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

عداء الطريق


خادم الويب ومدير عملية PHP. مكتوب في Golang.


الايجابيات:


  • يمكنك كتابة عامل يقوم بتهيئة المتغيرات مرة واحدة فقط ومواصلة العمل معها.

سلبيات:


  • يجب عليك كتابة الكود للعامل ؛
  • بحاجة إلى تتبع الذاكرة.

إذا استخدمت علامة --reboot-kernel-after-request للعامل ، فسيتم إعادة تهيئة Symfony Kernel لكل طلب. مع هذا النهج ، لا تحتاج إلى مراقبة الذاكرة.


كود العامل
 #!/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); } } 

أمر تشغيل التطبيق باستخدام عامل التهيئة:


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

اختبار


تم إجراء الاختبار باستخدام Yandex Tank.
كان التطبيق و Yandex Tank على خوادم افتراضية مختلفة.


ميزات الخادم الظاهري مع التطبيق:
الافتراضية : KVM
وحدة المعالجة المركزية : 2 النوى
ذاكرة الوصول العشوائي : 4096 ميغابايت
SSD : 50 جيجابايت
اتصال : 100MBit
نظام التشغيل : CentOS 7 (64x)


الخدمات التي تم اختبارها:


  • php-fpm
  • php-ppm
  • وحدة nginx
  • عداء الطريق
  • إعادة تشغيل الطريق (مع العلم --reboot-kernel-after-request )
  • رد فعل فب
  • رد فعل php-reboot (مع العلم --reboot-kernel-after-request )

للاختبارات 1000/10000 rps المضافة خدمة php-fpm-80
تم استخدام تكوين php-fpm لذلك:


 pm = dynamic pm.max_children = 80 

يحدد Yandex Tank مقدمًا عدد المرات التي يحتاج فيها لإطلاق النار على الهدف ، ولا يتوقف حتى نفاد الخراطيش. اعتمادًا على سرعة استجابة الخدمة ، قد يكون وقت الاختبار أطول من المحدد في تكوين الاختبار. لهذا السبب ، قد يكون لرسومات الخدمات المختلفة أطوال مختلفة. كلما كانت استجابة الخدمة أبطأ ، سيكون الجدول الزمني أطول.


لكل خدمة وتكوين Yandex Tank ، تم إجراء اختبار واحد فقط. بسبب هذا ، قد تكون الأرقام غير دقيقة. كان من المهم تقييم خصائص الخدمات بالنسبة لبعضها البعض.


100 دورة في الدقيقة


فانتوم ياندكس تانك التكوين


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

تقرير مفصل الروابط



في المئة من زمن الاستجابة


95 ٪ (مللي ثانية)90٪ (مللي ثانية)80٪ (مللي ثانية)50٪ (مللي ثانية)HTTP موافق (٪)موافق HTTP (العد)
php-fpm9.96.34.353.5910057030
php-ppm9.463.883.1610057030
وحدة nginx116.64.433.6910057030
عداء الطريق8.15.13.532.9210057030
الطريق عداء - إعادة التشغيل128.65.33.8510057030
رد فعل فب8.54.913.292.7410057030
رد فعل فب إعادة التشغيل138.55.53.9510057030

الرصد


متوسط ​​وحدة المعالجة المركزية (٪)وحدة المعالجة المركزية كحد أقصى (٪)الذاكرة الوسيطة (MB)الذاكرة كحد أقصى (ميغابايت)
php-fpm9.1512.58880.32907.97
php-ppm7.0813.68901.72913.80
وحدة nginx9.5612.54923.02943.90
عداء الطريق5.578.61992.711،001.46
الطريق عداء - إعادة التشغيل9.1812.67848.43870.26
رد فعل فب4.536.581004.681009.91
رد فعل فب إعادة التشغيل9.6112.67885.92892.52

الرسوم البيانية



الرسم البياني 1.1 متوسط ​​زمن الاستجابة في الثانية الواحدة



الرسم البياني 1.2 متوسط ​​تحميل المعالج في الثانية



الرسم البياني 1.3 متوسط ​​استهلاك الذاكرة في الثانية الواحدة


500 روبية


فانتوم ياندكس تانك التكوين


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

تقرير مفصل الروابط



في المئة من زمن الاستجابة


95 ٪ (مللي ثانية)90٪ (مللي ثانية)80٪ (مللي ثانية)50٪ (مللي ثانية)HTTP موافق (٪)موافق HTTP (العد)
php-fpm138.45.33.69100285030
php-ppm1594.723.24100285030
وحدة nginx1285.53.93100285030
عداء الطريق9.663.712.83100285030
الطريق عداء - إعادة التشغيل14117.14.45100285030
رد فعل فب9.35.83.572.68100285030
رد فعل فب إعادة التشغيل15127.24.21100285030

الرصد


متوسط ​​وحدة المعالجة المركزية (٪)وحدة المعالجة المركزية كحد أقصى (٪)الذاكرة الوسيطة (MB)الذاكرة كحد أقصى (ميغابايت)
php-fpm41.6848.331006.061015.09
php-ppm33.9048.901،046.321،055.00
وحدة nginx42.1347.921006.671015.73
عداء الطريق08/2406/281،035.861،044.58
الطريق عداء - إعادة التشغيل46.2352.04939.63948.08
رد فعل فب19.5723.421،049.831،060.26
رد فعل فب إعادة التشغيل41.3047.89957.01958.56

الرسوم البيانية



الرسم البياني 2.1 متوسط ​​زمن الاستجابة في الثانية الواحدة



الرسم البياني 2.2 متوسط ​​تحميل المعالج في الثانية



الرسم البياني 2.3 متوسط ​​استهلاك الذاكرة في الثانية الواحدة


1000 روبية


فانتوم ياندكس تانك التكوين


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

تقرير مفصل الروابط



في المئة من زمن الاستجابة


95 ٪ (مللي ثانية)90٪ (مللي ثانية)80٪ (مللي ثانية)50٪ (مللي ثانية)HTTP موافق (٪)موافق HTTP (العد)
php-fpm1105011050904019580.6772627
php-fpm-8031501375116515299.8589895
php-ppm278527402685254510090030
وحدة nginx9880602110090030
عداء الطريق27157.13.2110090030
الطريق عداء - إعادة التشغيل111011001085106010090030
رد فعل فب23135.62.8610090030
رد فعل فب إعادة التشغيل2824191110090030

الرصد


متوسط ​​وحدة المعالجة المركزية (٪)وحدة المعالجة المركزية كحد أقصى (٪)الذاكرة الوسيطة (MB)الذاكرة كحد أقصى (ميغابايت)
php-fpm12.6678.25990.161006.56
php-fpm-8083.7891.28746.01937.24
php-ppm66.1691.201،088.741102.92
وحدة nginx78.1188.771،010.151،062.01
عداء الطريق42.9354.231،010.891،068.48
الطريق عداء - إعادة التشغيل77.6485.66976.441،044.05
رد فعل فب36.3946.311018.031،088.23
رد فعل فب إعادة التشغيل72.1181.81911.28961.62

الرسوم البيانية



الرسم البياني 3.1 متوسط ​​زمن الاستجابة في الثانية الواحدة



الرسم البياني 3.2 متوسط ​​زمن الاستجابة في الثانية (بدون php-fpm ، php-ppm ، إعادة تشغيل الطريق)



الرسم البياني 3.3 متوسط ​​تحميل المعالج في الثانية



الرسم البياني 3.4 متوسط ​​استهلاك الذاكرة في الثانية الواحدة


10000 دورة في الدقيقة


فانتوم ياندكس تانك التكوين


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

تقرير مفصل الروابط



في المئة من زمن الاستجابة


95 ٪ (مللي ثانية)90٪ (مللي ثانية)80٪ (مللي ثانية)50٪ (مللي ثانية)HTTP موافق (٪)موافق HTTP (العد)
php-fpm110501105011050188070.466317107
php-fpm-80326031401360114599.619448301
php-ppm2755273026952605100450015
وحدة nginx102010101000980100450015
عداء الطريق640630615580100450015
الطريق عداء - إعادة التشغيل1130112011101085100450015
رد فعل فب1890109010455899.9964،49996
رد فعل فب إعادة التشغيل3480307012559199.72448753

الرصد


متوسط ​​وحدة المعالجة المركزية (٪)وحدة المعالجة المركزية كحد أقصى (٪)الذاكرة الوسيطة (MB)الذاكرة كحد أقصى (ميغابايت)
php-fpm5.5779.35984.47998.78
php-fpm-8085.0592.19936.64943.93
php-ppm66.8682.411089.311،097.41
وحدة nginx86.1493.941،067.711،069.52
عداء الطريق73.4182.721،129.481134.00
الطريق عداء - إعادة التشغيل80.3286.29982.69984.80
رد فعل فب73.7682.181101.711105.06
رد فعل فب إعادة التشغيل85.7791.92975.85978.42


الرسم البياني 4.1 متوسط ​​زمن الاستجابة في الثانية الواحدة



الرسم البياني 4.2 متوسط ​​زمن الاستجابة في الثانية (بدون php-fpm ، php-ppm)



الرسم البياني 4.3 متوسط ​​تحميل المعالج في الثانية



الرسم البياني 4.4 متوسط ​​استهلاك الذاكرة في الثانية الواحدة


ملخص


فيما يلي الرسوم البيانية التي توضح التغير في خصائص الخدمات حسب الحمل. عند عرض الرسوم البيانية ، تجدر الإشارة إلى أن الخدمات لم تستجب لجميع الطلبات بنسبة 100 ٪.



الرسم البياني 5.1 95 ٪ المئوية من وقت الاستجابة



الرسم البياني 5.2 95 ٪ من وقت الاستجابة (بدون php-fpm)



الرسم البياني 5.3 تحميل وحدة المعالجة المركزية القصوى



الرسم البياني 5.4 الحد الأقصى لاستهلاك الذاكرة


الحل الأمثل (بدون تغيير الكود) ، في رأيي ، هو مدير عمليات Nginx Unit. إنها تظهر نتائج جيدة في سرعة الاستجابة وتحظى بدعم الشركة.


في أي حال ، يجب تحديد نهج وأدوات التطوير بشكل فردي ، اعتمادًا على أعباء العمل وموارد الخادم وقدرات المطور.


محدث
للاختبارات 1000/10000 rps المضافة خدمة php-fpm-80
تم استخدام تكوين php-fpm لذلك:


 pm = dynamic pm.max_children = 80 

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


All Articles