Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHP Need for Speed Using Swoole

PHP Need for Speed Using Swoole

Implementing Event-Driven PHP Engine in PHP Frameworks

Peter Kokot

April 25, 2017
Tweet

More Decks by Peter Kokot

Other Decks in Technology

Transcript

  1. Intro • Hello • Traditional PHP Request Response Cycle •

    Event Driven PHP Engines • Swoole • Using PHP frameworks with Swoole • Laravel with Laravoole • Symfony example • StackPHP
  2. Swoole is an event-driven, asynchronous & concurrent networking communication engine

    with higher performance written only in C for PHP. Swoole includes components for different purposes: TCP/UDP Server and Client, Task Worker, Database Connection Pooling, Millisecond Timer, Event, Async IO, Async Http/WebSocket Client, Async Redis Client, Async MySQL Client, and Async DNS Requiring. > www.swoole.com
  3. Why Swoole? • Written in C language - Excellent performance

    • Multiple fully working event driven solutions
  4. Swoole PHP Extension Installation $ sudo pecl install swoole $

    echo "extension=swoole.so" | tee php.ini or $ echo "extension=swoole.so" | tee /usr/local/etc/php/conf.d/swoole.ini
  5. Running Swoole HTTP Server #!/usr/bin/env php <?php $http = new

    swoole_http_server ('127.0.0.1', 9501); $http->on('request', function ($request, $response) { $response->header('Content-Type' , 'text/html; charset=utf-8' ); $response->end('<h1>Hello PHP-SI.</h1>' ); }); $http->start(); Run PHP script with PHP-CLI $ php server.php
  6. Unix Domain Socket #!/usr/bin/env php <?php $http = new swoole_http_server

    ('127.0.0.1', 9501); $http->addlistener("/run/php/swoole.sock" , 0, SWOOLE_UNIX_STREAM); $http->on('request', function ($request, $response) { $response->header('Content-Type' , 'text/html; charset=utf-8' ); $response->end('<h1>Hello PHP-SI.</h1>' ); }); $http->start();
  7. Unix Domain Socket #!/usr/bin/env php <?php $http = new swoole_http_server

    ('127.0.0.1', 9501); $http->addlistener("/run/php/swoole.sock" , 0, SWOOLE_UNIX_STREAM); $http->on('request', function ($request, $response) { $response->header('Content-Type' , 'text/html; charset=utf-8' ); $response->end('<h1>Hello PHP-SI.</h1>' ); }); $http->start();
  8. Nginx upstream swoole { server unix:/run/php/swoole.sock fail_timeout=0; } server {

    listen 80 default_server; root /var/www/html; index index.php index.html index.htm index.nginx-debian.html; server_name _; location / { try_files $uri $uri/ =404; } location ~ \.php$ { proxy_set_header Connection "keep-alive"; proxy_set_header X-Forwarded-For $remote_addr; proxy_pass http://swoole; proxy_redirect off; } }
  9. Laravel Framework https://github.com/garveen/laravoole $ composer require garveen/laravoole Register Laravel service

    provider: 'providers' => [ ... Laravoole\ LaravooleServiceProvider ::class, ], $ php artisan laravoole [start | stop | reload | reload_task | restart | quit]
  10. Symfony /** * Run Swoole HTTP server. * php bin/console

    swoole:start --host=[HOST] --port=[PORT] --env=[ENV] */ class SwooleCommand extends Command { //... protected function configure() { $this ->setName('swoole:start') ->setDescription('Start Swoole HTTP Server.') ->addOption('host', null, InputOption::VALUE_OPTIONAL, 'Host for server', '127.0.0.1') ->addOption('port', null, InputOption::VALUE_OPTIONAL, 'Port for server', 9501) ; }
  11. Symfony class SwooleCommand extends Command { //... protected function execute(InputInterface

    $input, OutputInterface $output) { $http = new \swoole_http_server($input->getOption('host'), $input->getOption('port')); $this->driver = new SymfonyDriver(); $this->driver->boot(); $http->on('request', function(\swoole_http_request $request, \swoole_http_response $response) { $this->driver->preHandle(); $response = $this->driver->handle($request, $response); $this->driver->postHandle(); }); $http->start(); } }
  12. Symfony Driver /** * Driver for running Symfony with Swoole.

    */ class SymfonyDriver { private $kernel; private $symfonyRequest; private $symfonyResponse; public function boot(){} public function preHandle(){} public function postHandle(){} public function handle(\swoole_http_request $request, \swoole_http_response $response){} }
  13. Booting Symfony Driver class SymfonyDriver { //... public function boot()

    { $loader = require __DIR__.'/../app/autoload.php'; include_once __DIR__.'/../var/bootstrap.php.cache'; $this->kernel = new \AppKernel('prod', false); $this->kernel->loadClassCache(); return; } //… }
  14. Before Request public function preHandle() { // Resets Kernels startTime,

    so Symfony can correctly calculate the execution time Utils::hijackProperty($this->kernel, 'startTime', microtime(true)); Utils::bindAndCall(function() { // init bundles $this->initializeBundles(); // init container $this->initializeContainer(); }, $this->kernel); }
  15. After Request public function postHandle() { // Reset the stopwatch

    in debug toolbar in case it is used (development environment) if ($this->kernel->getContainer()->has('debug.stopwatch')) { $this->kernel->getContainer()->get('debug.stopwatch')->__construct(); } // Resets profiler so the debug toolbar is visible in other requests as well. if ($this->kernel->getContainer()->has('profiler')) { $this->kernel->getContainer()->get('profiler')->enable(); // PropelLogger if ($this->kernel->getContainer()->has('propel.logger')) { $propelLogger = $this->kernel->getContainer()->get('propel.logger'); Utils::hijackProperty($propelLogger, 'queries', []); }
  16. After Request //Doctrine\Bundle\DoctrineBundle\DataCollector\DoctrineDataCollector if ($this->kernel->getContainer()->get('profiler')->has('db')) { Utils::bindAndCall(function () { //$logger:

    \Doctrine\DBAL\Logging\DebugStack foreach ($this->loggers as $logger){ Utils::hijackProperty($logger, 'queries', []); } }, $this->kernel->getContainer()->get('profiler')->get('db'), null, 'Symfony\Bridge\Doctrine\DataCollector\DoctrineDataCollector'); }
  17. After Request // EventDataCollector if ($this->kernel->getContainer()->get('profiler')->has('events')) { Utils::hijackProperty($this->kernel->getContainer()->get('profiler')->get('events'), 'data', array[

    'called_listeners' => [], 'not_called_listeners' => [], ]); } // TwigDataCollector if ($this->kernel->getContainer()->get('profiler')->has('twig')) { Utils::bindAndCall(function () { Utils::hijackProperty($this->profile, 'profiles', []); }, $this->kernel->getContainer()->get('profiler')->get('twig')); }
  18. After Request // Logger if ($this->kernel->getContainer()->has('logger')) { $logger = $this->kernel->getContainer()->get('logger');

    Utils::bindAndCall(function () { if ($debugLogger = $this->getDebugLogger()) { // DebugLogger Utils::hijackProperty($debugLogger, 'records', []); } }, $this->kernel->getContainer()->get('logger')); }
  19. After Request //SwiftMailer logger //Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector if ($this->kernel->getContainer()->hasParameter('swiftmailer.mailers')) { $mailers =

    $this->kernel->getContainer()->getParameter('swiftmailer.mailers'); foreach ($mailers as $name => $mailer) { $loggerName = sprintf('swiftmailer.mailer.%s.plugin.messagelogger', $name); if ($this->kernel->getContainer()->has($loggerName)) { /** @var \Swift_Plugins_MessageLogger $logger */ $logger = $this->kernel->getContainer()->get($loggerName); $logger->clear(); } } }
  20. Handle Request public function handle(\swoole_http_request $request, \swoole_http_response $response) { $rq

    = new Request(); $this->symfonyRequest = $rq->createSymfonyRequest($request); $this->symfonyResponse = $this->kernel->handle($this->symfonyRequest); foreach ($this->symfonyResponse->headers->getCookies() as $cookie) { $response->header('Set-Cookie', $cookie); } foreach ($this->symfonyResponse->headers as $name => $values) { $name = implode('-', array_map('ucfirst', explode('-', $name))); foreach ($values as $value) { $response->header($name, $value); } } $response->end($this->symfonyResponse->getContent()); return $response; }
  21. Create Symfony Request from Swoole Request $_SERVER = isset($swooleRequest->server) ?

    array_change_key_case($swooleRequest->server, CASE_UPPER) : []; if (isset($swooleRequest->header)) { $headers = []; foreach ($swooleRequest->header as $k => $v) { $k = str_replace('-', '_', $k); $headers['http_' . $k] = $v; } $_SERVER += array_change_key_case($headers, CASE_UPPER); } $_GET = isset($swooleRequest->get) ? $swooleRequest>get : []; $_POST = isset($swooleRequest->post) ? $swooleRequest->post : []; $_COOKIE = isset($swooleRequest->cookie) ? $swooleRequest->cookie : []; $files = $swooleRequest->files ?? [];
  22. Create Symfony Request from Swoole Request $symfonyRequest = new Symfony\Component\HttpFoundation\Request(

    $_GET, $_POST, [], $_COOKIE, $files, $_SERVER ); if (0 === strpos($symfonyRequest->headers->get('Content-Type'), 'application/json')) { $data = json_decode($swooleRequest->rawContent(), true); $symfonyRequest->request->replace(is_array($data) ? $data : array()); } return $symfonyRequest;
  23. StackPHP A stack middleware object follows these 3 conventions: •

    Implements the HttpKernelInterface • Decorated application is the first constructor argument • Handle call must be decorated and delegated to the decorated application
  24. Profiling PHP Swoole HTTP Server $http = new swoole_http_server('127.0.0.1', 9501);

    $http->on('request', function ($request, $response) { $enableProfiling = isset($request->header['X-Blackfire-Query']); if ($enableProfiling) { $blackfire = new Blackfire\Client(); $probe = $blackfire->createProbe(); } $response->header('Content-Type', 'text/html; charset=utf-8'); $response->end('<h1>Hello PHP-SI.</h1>'); if ($enableProfiling) { $blackfire->endProbe($probe); } }); $http->start();
  25. Friendly Cautions • Use Reverse Proxy such as Nginx •

    Memory leaks • Documentation • Running PHP with event-driven engines is not widely used yet