Slide 1

Slide 1 text

November 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF Async PHP Requests & Reactive Responses with PHP-FPM

Slide 2

Slide 2 text

HOLGER WOLTERSDORF CIO • FATHER • HUSBAND • PHP DEV WITH ♥ github.com/hollodotme @hollodotme

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

2018 SEPTEMBER 21st & 22nd

Slide 5

Slide 5 text

+ async

Slide 6

Slide 6 text

USE CASE A PDF CREATION SERVICE

Slide 7

Slide 7 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF USE CASE (PDF CREATION) 7 WEB-SERVICE

Slide 8

Slide 8 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF USE CASE (PDF CREATION) 8 WEB-SERVICE SEQUENTIAL PROCESSING IS OK(-ish)

Slide 9

Slide 9 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF USE CASE (PDF CREATION) 9 WEB-SERVICE BUT WHAT IF…

Slide 10

Slide 10 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF USE CASE (PDF CREATION) 10 PARALLELISM TO THE RESCUE

Slide 11

Slide 11 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WE TRIED… 11 exec('php "/create-pdf.php" > /dev/null 2>&1 &'); # OR shell_exec('php "/create-pdf.php" > /dev/null 2>&1 &'); # OR proc_open('php "/create-pdf.php"', $descriptorSpec ); PHP 4 - 2001

Slide 12

Slide 12 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WORKS, BUT… 12 ๏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 NIRVANA ๏DEBUGGING IS A NIGHTMARE (ESPECIALLY IN PRODUCTION)

Slide 13

Slide 13 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WE TRIED… 13 $scriptUrl = 'http:#//##www.yoursite.com/create-pdf.php'; $ch = curl_init(); curl_setopt( $ch, CURLOPT_URL, $scriptUrl ); 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

Slide 14

Slide 14 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WORKS, BUT… 14 ๏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 NIRVANA ๏CALLED SCRIPT MUST BE EXPOSED (UNDER DOMAIN’S DOCUMENT ROOT)

Slide 15

Slide 15 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WE TRIED VERY HARD… 15 $pdo#->query( "INSERT INTO queue (id, script, data) VALUES ('123', '/create-pdf.php', '{json}')" ); $ crontab -e #*/1 * * * * php "/path/to/queue-processor.php" +

Slide 16

Slide 16 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WORKS, BUT… 16 ๏ 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

Slide 17

Slide 17 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WE TRIED TO BE CLEVER… 17 function runInBackground() { include '/create-pdf.php'; } register_shutdown_function( 'runInBackground' ); header( 'Location: /show/user/a/page', true, 301 ); flush();

Slide 18

Slide 18 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WORKS, BUT… 18 ๏WEBSERVER INVOLVED ๏RESPONSE ENDS UP IN NIRVANA ๏MEMORY LEAKS FOR THE WIN! ๏PRETTY HARD ERROR HANDLING ๏NO EXECUTION TIME LIMIT

Slide 19

Slide 19 text

DON’T TRY TO BE CLEVER!

Slide 20

Slide 20 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WE COULD USE… 20 pthreads

Slide 21

Slide 21 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WE COULD USE… 21 pthreads ๏NEEDS CUSTOM PHP BUILD ๏NOT ALL EXTENSIONS ARE THREAD-SAFE ๏NOT WORKING IN WEB ENVIRONMENT ๏BASIC KNOWLEDGE ABOUT MULTI THREADING NEEDED ๏FEATURE AND CONFIG OVERHEAD FOR SIMPLE ASYNC TASKS ๏PROCESS / THREAD MANAGEMENT IS UP TO YOU

Slide 22

Slide 22 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WE COULD USE… 22 pcntl

Slide 23

Slide 23 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WE COULD USE… 23 pcntl ๏NEEDS CUSTOM PHP BUILD ๏NOT WORKING ON WINDOWS ๏BASIC KNOWLEDGE ABOUT UNIX PROCESSES NEEDED ๏PROCESS MANAGEMENT IS UP TO YOU

Slide 24

Slide 24 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WE COULD USE… 24 ๏NEEDS ANOTHER PIECE OF INFRASTRUCTURE + PHP EXTENSION ๏FEATURE RICH, BUT OVERLOADED FOR SIMPLE ASYNC TASKS ๏A LOT OF SETUP FOR LOCAL DEVELOPMENT

Slide 25

Slide 25 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WHAT DO WE WANT? 25 ๏ MAKE ASYNC CALLS TO PHP ๏ EVENTUALLY GET THE RESPONSES ๏ NO ADDITIONAL INFRASTRUCTURE ๏ NO ADDITIONAL EXTENSIONS ๏ WEB-REQUEST-LIKE DATA HANDLING (BECAUSE WE’RE USED TO IT) ๏ TAKE ADVANTAGE OF OPCACHE ๏ BACKGROUND WORKERS NOT EXPOSED TO "PUBLIC" ๏ TUNEABLE PROCESS MANAGEMENT

Slide 26

Slide 26 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF WHAT IF I TOLD YOU… 26 THERE IS A BULLET-PROOF PROCESS MANAGER SHIPPED WITH PHP … AND YOU’RE PROBABLY USING IT ALREADY.

Slide 27

Slide 27 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF 27 PHP-FPM (PHP FastCGI Process Manager)

Slide 28

Slide 28 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF 28 WEBSERVER
 (NGINX, APACHE) PHP-FPM HOW IT USUALLY WORKS POOL (WWW) 1..N PROCESSES PHP WEB CONTEXT

Slide 29

Slide 29 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF HOW IT USUALLY WORKS 29 PID TIME COMMAND 1365 00:00:01 php-fpm: master process (/etc/php/7.1/fpm/php-fpm.conf) 1719 00:00:00 php-fpm: pool ##www 1720 00:00:00 php-fpm: pool ##www 4529 00:00:01 php-fpm: pool ##www LOOKS FAMILIAR?

Slide 30

Slide 30 text

hollodotme/fast-cgi-client

Slide 31

Slide 31 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF 31 WEBSERVER
 (NGINX, APACHE) PHP-FPM HOW IT WORKS POOL (WWW) 1..N PROCESSES POOL (BACKGROUND)
 0..N PROCESSES PHP WEB CONTEXT

Slide 32

Slide 32 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF 32 WEBSERVER
 (NGINX, APACHE) PHP-FPM HOW IT WORKS POOL (WWW) 1..N PROCESSES POOL (BACKGROUND)
 0..N PROCESSES QUEUE PHP WEB CONTEXT

Slide 33

Slide 33 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF PHP-FPM POOL CONFIG UNIX DOMAIN SOCKET 33 ; 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 = 100 ; Number of seconds after which an idle children will be killed pm.process_idle_timeout = 10s

Slide 34

Slide 34 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF CONNECT TO UNIX DOMAIN SOCKET 34

Slide 35

Slide 35 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF CONNECT TO NETWORK SOCKET 35

Slide 36

Slide 36 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF PHP-FPM POOL CONFIG NETWORK SOCKET 36 ; 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 = 100 ; Number of seconds after which an idle children will be killed pm.process_idle_timeout = 10s

Slide 37

Slide 37 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF SEND A SYNCHRONOUS REQUEST 37 'value'] ); # Simulate HTTP Verbs: GET, POST, PUT, PATCH, DELETE $request = new PostRequest( '/create-pdf.php', $content ); $response = $client#->sendRequest( $request ); echo $response#->getBody();

Slide 38

Slide 38 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF RESPONSE INTERFACE 38

Slide 39

Slide 39 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF ASYNC REQUEST (FIRE & FORGET) 39 'value'] ); $request = new PostRequest( '/create-pdf.php', $content ); $requestId = $client#->sendAsyncRequest( $request ); echo "Request sent, got ID: {$requestId}";

Slide 40

Slide 40 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF ASYNC REQUEST + WAIT FOR RESPONSE 40 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();

Slide 41

Slide 41 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF USING RESPONSE CALLBACKS 41 addResponseCallbacks( function( ProvidesResponseData $response ) { echo $response#->getBody(); } ); # Failure callbacks expect a \Throwable instance # ##...variadic function $request#->addFailureCallbacks( function ( \Throwable $throwable ) { echo $throwable#->getMessage(); } ); $requestId = $client#->sendAsyncRequest( $request );

Slide 42

Slide 42 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF USING PASS THROUG CALLBACKS 42 addPassThroughCallbacks( function( string $buffer ) { echo $buffer; } ); $requestId = $client#->sendAsyncRequest( $request );

Slide 43

Slide 43 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF USING RESPONSE CALLBACKS 43 waitForResponse( $requestId ); # Inner loop # ##... is the same as while(true) # Outer loop { if ( $client#->hasResponse( $requestId ) ) { $client#->handleResponse( $requestId ); break; } }

Slide 44

Slide 44 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF MULTIPLE REQUESTS - ORDERED RESPONSES 44 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"; }

Slide 45

Slide 45 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF MULTIPLE REQUESTS - ORDERED RESPONSES 45 # PRINTS Response #1 Response #2 Response #3 ๏ NO MATTER HOW LONG EACH WORKER TOOK TO RESPOND

Slide 46

Slide 46 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF MULTIPLE REQUESTS - REACTIVE RESPONSES 46 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 '.'; }

Slide 47

Slide 47 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF MULTIPLE REQUESTS - REACTIVE RESPONSES 47 hasUnhandledResponses() ) { $readyRequestIds = $client#->getRequestIdsHavingResponse(); # read all ready responses foreach ( $readyRequestIds as $requestId ) { $response = $client#->readResponse( $requestId ); echo $response#->getBody() . "\n"; }
 echo '.'; }

Slide 48

Slide 48 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF MULTIPLE REQUESTS - REACTIVE RESPONSES 48 # PRINTS .....Response #2 .......Response #3 ..........Response #1 ๏ ORDERED BY RESPONSE TIME

Slide 49

Slide 49 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF MULTIPLE REQUESTS - REACTIVE CALLBACKS 49 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 ); } }

Slide 50

Slide 50 text

DEMO!

Slide 51

Slide 51 text

QUESTIONS?

Slide 52

Slide 52 text

THANK YOU! github.com/hollodotme @hollodotme / @F9T3ch fortuneglobe.com phpug-dresden.org @phpugdd HOLGER WOLTERSDORF icehawk.github.io speakerdeck.com/hollodotme Slides available at:

Slide 53

Slide 53 text

ASYNC PHP REQUESTS AND REACTIVE RESPONSES WITH PHP-FPM • NOVEMBER 21st 2017 • PHP USERGROUP MÜNSTER HOLGER WOLTERSDORF LINKS / REFERENCES 53 ๏ PHP USERGROUP DRESDEN e.V. http://phpug-dresden.org ๏ 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 ๏ PHP FastCGI Client Demo: https://github.com/hollodotme/fast-cgi-client-demo ๏ 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