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

ASYNC PHP Requests & Reactive Responses

ASYNC PHP Requests & Reactive Responses

There are many approaches to execute PHP sub-tasks asynchronously or to parallelise PHP execution.
While some solutions require extra extensions, individual PHP builds or a lot of process control management, this talk will show you how to configure and use the built-in PHP FastCGI Process Manager (php-fpm) to execute requests asynchronously in an isolated, tunable process pool and eventually handle their responses in a reactive way.

Holger Woltersdorf

May 31, 2017
Tweet

More Decks by Holger Woltersdorf

Other Decks in Programming

Transcript

  1. May 31st 2017 • Péhápkaři • Prague HOLGER WOLTERSDORF Async

    PHP Requests & Reactive Responses with PHP-FPM
  2. HOLGER WOLTERSDORF CIO • FATHER • HUSBAND • PHP DEV

    WITH ♥ github.com/hollodotme @hollodotme
  3. SEPTEMBER 22nd ARNE BLANKERTS thePHPcc SEBASTIAN HEUER kartenmacherei JAN BURKL

    ZEND MARCO PIVETTA Ocramius STEPHAN HOCHDÖRFER bitExpert ALEXANDER MIERTSCH prooph software CHRISTIAN LÜCK reactPHP
  4. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WE TRIED… 6 <?php exec( 'php "/path/to/script.php" > /dev/null 2>&1 &', $output, $return_var ); # OR shell_exec('php "/path/to/script.php" > /dev/null 2>&1 &') PHP 4 - 2001
  5. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WORKS, BUT… 7 ๏ PHP SCRIPT IS CALLED IN CLI MODE (DIFFERENT ENVIRONMENT) ๏ COMMAND GETS QUIET MESSY WHEN A LOT OF DATA SHOULD BE PASSED ๏ DATA HANDLING IN CALLED SCRIPT BASED ON $ARGV ARRAY ๏ RESPONSE ENDS UP IN NIRWANA ๏ DEBUGGING IS A NIGHTMARE (ESPECIALLY IN PRODUCTION)
  6. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WE TRIED… 8 <?php $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, 'http://www.yoursite.com/background-script.php' ); curl_setopt( $ch, CURLOPT_FRESH_CONNECT, true ); curl_setopt( $ch, CURLOPT_TIMEOUT_MS, 1 ); curl_exec( $ch ); curl_close( $ch ); PHP 5.2.3 - 2009
  7. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WORKS, BUT… 9 ๏ WEBSERVER ALWAYS INVOLVED (= OVERHEAD + ERROR SOURCE) ๏ MAYBE A LOAD BALANCER INVOLVED, TOO ๏ AT LEAST 2 ENVIRONMENTS TO MAINTAIN ๏ CURL EXTENSION NEEDED ๏ RESPONSE ENDS UP IN NIRWANA ๏ CALLED SCRIPT MUST BE EXPOSED (UNDER DOMAIN’S DOCUMENT ROOT)
  8. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WE TRIED VERY HARD… 10 <?php $pdo->query( "INSERT INTO queue (id, script, data) VALUES ('123', '/path/to/script.php', '{json}')" ); #/>$ crontab -e */1 * * * * php "/path/to/queue-processor.php" +
  9. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WORKS, BUT… 11 ๏ NO ON-DEMAND EXECUTION ๏ NEEDS A LOT OF LOCKING AND LOGGING ๏ RAISE CONDITIONS FOR THE WIN! ๏ ERRORS CAN PILE UP UNTIL SERVER IS DEAD ๏ HEAVY DATABASE LOAD FOR TECHNICALLY ELUSIVE DATA (HINT: BAD IDEA) ๏ MAINTENANCE OUTSIDE PHP PROJECT NEEDED (CRONTAB) ๏ HARD TO TEST
  10. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WE TRIED TO BE CLEVER… 12 <?php function runInBackground() { # Do your background processing here... } register_shutdown_function('runInBackground'); header('Location: /show/user/a/page.php', true, 301); flush();
  11. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WORKS, BUT… 13 ๏ WEBSERVER INVOLVED ๏ RESPONSE ENDS UP IN NIRWANA ๏ MEMORY LEAKS FOR THE WIN! ๏ PRETTY HARD ERROR HANDLING ๏ NO EXECUTION TIME LIMIT ๏ WTF? - DON’T TRY TO BE CLEVER
  12. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WE COULD USE… 14 pthreads ๏ NEEDS CUSTOM PHP BUILD ๏ NOT ALL EXTENSIONS ARE THREAD-SAFE ๏ BASIC KNOWLEDGE ABOUT MULTI THREADING NEEDED ๏ FEATURE AND CONFIG OVERHEAD FOR SIMPLE ASYN TASKS ๏ PROCESS / THREAD MANAGEMENT IS UP TO YOU
  13. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WE COULD USE… 15 pcntl ๏ NEEDS CUSTOM PHP BUILD ๏ NOT WORKING ON WINDOWS ๏ BASIC KNOWLEDGE ABOUT UNIX PROCESSES NEEDED ๏ PROCESS MANAGEMENT IS UP TO YOU
  14. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WE COULD USE… 16 ๏ NEEDS ANOTHER PIECE OF INFRASTRUCTURE + PHP EXTENSION ๏ FEATURE RICH, BUT OVERLOADED FOR SIMPLE ASYNC TASKS ๏ A LOT OF SETUP FOR LOCAL DEVELOPMENT
  15. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WHAT DO WE WANT? 17 ๏ MAKE ASYNC CALLS TO PHP ๏ EVENTUALLY GET THE RESPONSE ๏ NO ADDITIONAL INFRASTRUCTURE ๏ NO ADDITIONAL EXTENSIONS ๏ WEB-REQUEST LIKE DATA HANDLING (BECAUSE WE’RE USED TO IT) ๏ BACKGROUND WORKERS NOT EXPOSED TO "PUBLIC" ๏ TUNEABLE PROCESS MANAGEMENT
  16. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF THE IDEA… 18 TALK BY ARNE BLANKERTS AT CONFOO / IPC16
  17. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WHAT I FOUND… 19 ๏ SEEMED OUTDATED AND UNMAINTAINED ๏ BUT DID A GOOD PART OF THE JOB PIERRICK CHARRON ๏ DESIGNED TO SEND SINGLE SYNCHRONOUS AND ASYNCHRONOUS REQUEST
  18. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF WHAT I DID… 21 ๏ UPDATED PHP LANGUAGE LEVEL TO 7.X ๏ Releases v1.x for PHP 7.0.x ๏ Releases v2.x for PHP 7.1.x ๏ SPLIT CODE INTO MULTIPLE CLASSES ๏ ADDED UNIT AND INTEGRATION TESTS ๏ POLISHED THE CONNECTION, CLIENT, REQUEST & RESPONSE INTERFACES ๏ ADDED FEATURES FOR MULTI-REQUESTS AND LOOP INTEGRATION ๏ ADDED USAGE DOCUMENTATION
  19. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF CONNECT TO UNIX DOMAIN SOCKET 22 <?php declare(strict_types=1); namespace YourVendor\YourProject; use hollodotme\FastCGI\Client; use hollodotme\FastCGI\SocketConnections\UnixDomainSocket; $connection = new UnixDomainSocket( # Socket path to php-fpm 'unix:///var/run/php/php7.1-fpm.sock', # Connect timeout in milliseconds (default: 5000) 5000, # Read/write timeout in milliseconds (default: 5000) 5000 ); $client = new Client( $connection );
  20. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF PHP-FPM POOL CONFIG UNIX DOMAIN SOCKET 23 ; Pool name [background] ; Process ownership user = www-data group = www-data ; Socket path listen = /var/run/php/php7.1-fpm-background.sock ; Socket ownership listen.owner = www-data listen.group = www-data ; Process management pm = ondemand ; Maximum of children that can be alive at the same time pm.max_children = 5 ; Number of seconds after which an idle children will be killed pm.process_idle_timeout = 10s
  21. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF CONNECT TO NETWORK SOCKET 24 <?php declare(strict_types=1); namespace YourVendor\YourProject; use hollodotme\FastCGI\Client; use hollodotme\FastCGI\SocketConnections\NetworkSocket; $connection = new NetworkSocket( # Hostname or IP '127.0.0.1', # Port 9000, # Connect timeout in milliseconds (default: 5000) 5000, # Read/write timeout in milliseconds (default: 5000) 5000 ); $client = new Client( $connection );
  22. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF PHP-FPM POOL CONFIG NETWORK SOCKET 25 ; Pool name [background] ; Process ownership user = www-data group = www-data ; Socket IP and Port listen = 127.0.0.1:9001 ; Socket ownership listen.owner = www-data listen.group = www-data ; Process management pm = ondemand ; Maximum of children that can be alive at the same time pm.max_children = 5 ; Number of seconds after which an idle children will be killed pm.process_idle_timeout = 10s
  23. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF SEND A SYNCHRONOUS REQUEST 26 <?php declare(strict_types=1); # Content-type: ’application/x-www-form-urlencoded’ (default) $content = http_build_query( ['key' => 'value'] ); # Simulate HTTP Verbs: GET, POST, PUT, PATCH, DELETE $request = new PostRequest( '/path/to/script.php', $content ); $response = $client->sendRequest( $request ); echo $response->getBody();
  24. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF RESPONSE INTERFACE 27 <?php declare(strict_types=1); namespace hollodotme\FastCGI\Interfaces; interface ProvidesResponseData { public function getRequestId() : int; public function getHeaders() : array; public function getHeader( string $headerKey ) : string; public function getBody() : string; public function getRawResponse() : string; public function getDuration() : float; }
  25. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF ASYNC FIRE & FORGET 28 <?php declare(strict_types=1); $client = new Client( new NetworkSocket( '127.0.0.1', 9000 ) ); $content = http_build_query( ['key' => 'value'] ); $request = new PostRequest( '/path/to/script.php', $content ); $requestId = $client->sendAsyncRequest( $request ); echo "Request sent, got ID: {$requestId}";
  26. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF ASYNC REQUEST + WAIT FOR RESPONSE 29 <?php declare(strict_types=1); # ... $requestId = $client->sendAsyncRequest( $request ); echo "Request sent, got ID: {$requestId}"; # Do something in the meanwhile here ... $response = $client->readResponse( $requestId ); # Blocking call until response is received or read timed out echo $response->getBody();
  27. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF USING RESPONSE CALLBACKS 30 <?php declare(strict_types=1); # Response callbacks expects a ProvidesResponseData instance # ...variadic function $request->addResponseCallbacks( function( ProvidesResponseData $response ) { echo $response->getBody(); } ); # Failure callbacks expects a \Throwable instance # ...variadic function $request->addFailureCallbacks( function ( \Throwable $throwable ) { echo $throwable->getMessage(); } ); $requestId = $client->sendAsyncRequest( $request );
  28. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF USING RESPONSE CALLBACKS 31 <?php declare(strict_types=1); # ... $client->waitForResponse( $requestId ); # Inner loop # ... is the same as while(true) # Outer loop { if ( $client->hasResponse( $requestId ) ) { $client->handleResponse( $requestId ); break; } }
  29. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF MULTIPLE REQUESTS - ORDERED RESPONSES 32 <?php declare(strict_types=1); $requestIds = []; $requestIds[] = $client->sendAsyncRequest( $request1 ); $requestIds[] = $client->sendAsyncRequest( $request2 ); $requestIds[] = $client->sendAsyncRequest( $request3 ); echo 'Sent requests: ' . implode( ', ', $requestIds ) . "\n"; # Do something else here in the meanwhile ... # Blocking call until all responses are received or read timed out foreach ($client->readResponses(5000, ...$requestIds) as $response) { echo $response->getBody() . "\n"; }
  30. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF MULTIPLE REQUESTS - ORDERED RESPONSES 33 # PRINTS Response #1 Response #2 Response #3 ๏ NO MATTER HOW LONG EACH WORKER TOOK TO RESPOND
  31. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF MULTIPLE REQUESTS - REACTIVE RESPONSES 34 <?php declare(strict_types=1); $requestIds = []; $requestIds[] = $client->sendAsyncRequest( $request1 ); $requestIds[] = $client->sendAsyncRequest( $request2 ); $requestIds[] = $client->sendAsyncRequest( $request3 ); echo 'Sent requests: ' . implode( ', ', $requestIds ) . "\n"; # Do something else here in the meanwhile ... while ( $client->hasUnhandledResponses() ) { # read all ready responses foreach ( $client->readReadyResponses() as $response ) { echo $response->getBody() . "\n"; }
 echo '.'; }
  32. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF MULTIPLE REQUESTS - REACTIVE RESPONSES 35 <?php declare(strict_types=1); # ... is the same as while ( $client->hasUnhandledResponses() ) { $readyRequestIds = $client->getRequestIdsHavingResponse(); # read all ready responses foreach ( $readyRequestIds as $requestId ) { $response = $client->readResponse( $requestId ); echo $response->getBody() . "\n"; }
 echo '.'; }
  33. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF MULTIPLE REQUESTS - ORDERED RESPONSES 36 # PRINTS .....Response #2 .......Response #3 ..........Response #1 ๏ ORDERED BY RESPONSE TIME
  34. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF MULTIPLE REQUESTS - REACTIVE CALLBACKS 37 <?php declare(strict_types=1); $client->waitForResponses(); # Inner loop # ... is the same as while ( $client->hasUnhandledResponses() ) # Outer loop { $client->handleReadyResponses(); } # ... is the same as while ( $client->hasUnhandledResponses() ) # Outer loop { $readyRequestIds = $client->getRequestIdsHavingResponse(); foreach ( $readyRequestIds as $requestId ) { $client->handleResponse( $requestId ); } }
  35. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF MESSAGE QUEUE WORKERS 39
  36. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF TRIGGER API - PDF CREATION 40 <?php declare(strict_types=1); $documentId = DocumentId::generate(); $content = http_build_query( [ 'documentId' => $documentId, #... ] ); $request = new PostRequest( '/pdf-generator.php', $content ); # Delegate heavy PDF generation to another PHP process $client->sendAsyncRequest( $request ); # Immediate respond to client with generated document ID header('Content-type: application/json', true, 200); echo json_encode( ['documentId' => $documentId] );
  37. THANK YOU! github.com/hollodotme @hollodotme / @F9T3ch fortuneglobe.com phpug-dresden.org @phpugdd HOLGER

    WOLTERSDORF icehawk.github.io speakerdeck.com/hollodotme Slides available at:
  38. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF LINKS / REFERENCES 43 ๏ PHP USERGROUP DRESDEN e.V. http://phpug-dresden.org ๏ PHP Developer Day 2017: https://bit.ly/PHPDD17EN ๏ Asynchronous cURL Requests: http://www.paul-norman.co.uk/2009/06/asynchronous-curl-requests/ ๏ Arne Blankerts’ talk about nodejs + PHP: https://thephp.cc/dates/2016/02/confoo/just-married-node-js-and-php ๏ Original PHP FastCGI Client by Pierrick Charron: https://github.com/adoy/PHP-FastCGI-Client/ ๏ PHP FastCGI Client: https://github.com/hollodotme/fast-cgi-client ๏ FastCGI Specification: http://www.mit.edu/~yandros/doc/specs/fcgi-spec.html ๏ Experimental use-case with rabbitMQ: https://hollo.me/php/experimental-async-php-volume-2.html