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.

8ad631306f5ab343446a967b98e64c0e?s=128

Holger Woltersdorf

May 31, 2017
Tweet

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. None
  4. 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
  5. PHP & ASYNC

  6. 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
  7. 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)
  8. 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
  9. 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)
  10. 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" +
  11. 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
  12. 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();
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. hollodotme/fast-cgi-client

  21. 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
  22. 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 );
  23. 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
  24. 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 );
  25. 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
  26. 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();
  27. 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; }
  28. 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}";
  29. 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();
  30. 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 );
  31. 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; } }
  32. 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"; }
  33. 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
  34. 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 '.'; }
  35. 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 '.'; }
  36. 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
  37. 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 ); } }
  38. USE CASES

  39. ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • MAY

    31st 2017 • Péhápkaři. • PRAGUE HOLGER WOLTERSDORF MESSAGE QUEUE WORKERS 39
  40. 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] );
  41. QUESTIONS?

  42. THANK YOU! github.com/hollodotme @hollodotme / @F9T3ch fortuneglobe.com phpug-dresden.org @phpugdd HOLGER

    WOLTERSDORF icehawk.github.io speakerdeck.com/hollodotme Slides available at:
  43. 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