asynchronous php and real-time messaging

45
asynchronous

Upload: steve-rhoades

Post on 21-Apr-2017

224 views

Category:

Engineering


3 download

TRANSCRIPT

asynchronous

steve rhoadestwitter+github @steverhoades

Asynchronous PHP

page scraper$urls = ['www.amazon.com', ...]; !foreach($urls as $url) { $content = file_get_contents( "http://$url", false, $context );}

request timewww.amazon.com: 0.80137www.reddit.com: 0.21584www.hackernews.com: 1.80921www.google.com: 0.29365www.yahoo.com: 1.39217!

Total time in seconds: 4.51274

time

call 2

Netw

ork

call 1Ne

twork

call 3

Netw

ork

blocking i/o

* Assuming ~1GB/sec SSD source: https://gist.github.com/jboner/2841832

Send 1K bytes over Gbps network 0.01 ms

Read 4K randomly from SSD* 0.15 ms

Read 1 MB sequentially from memory 0.25 ms

Round trip within same datacenter 0.5 ms

Read 1 MB sequentially from SSD* 1 ms

Disk seek 10 ms

Read 1MB sequentially from disk 20 ms

Send packet CA->Netherlands->CA 150 ms

I/O is slow.

var urls = ['http://www.amazon.com', ...];!for(var i in urls) { http.get(urls[i], function(response) { var str = ''; response.on('data', function (chunk) { str += chunk; });! response.on('end', function () { // do something with data }); });}

page scraper

www.amazon.com: 0.75559www.reddit.com: 0.33153www.hackernews.com: 0.57661www.google.com: 0.48226www.yahoo.com: 0.23333!

Total time: 0.76421

request time

call 2call 1 call 3create

create

non-blocking i/o

create

time

resp

resp

resp

reqreq

req

$urls = ['www.amazon.com',...];!foreach($urls as $ip => $url) { // create a stream that returns immediately // STREAM_CLIENT_CONNECT _MUST_ be passed // STREAM_CLIENT_ASYNC_CONNECT says create connection // asynchronously $socket = stream_socket_client( "tcp://$ip:80", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT ); // set stream as non-blocking with 0, default is 1 stream_set_blocking($socket, 0);! // sockets = read streams, requests = write streams $sockets[(int) $socket] = $socket; $requests[(int) $socket] = $socket; }

non-blocking i/o

while(!empty($sockets)) { //run loop $read = $sockets; $write = $requests; $except = NULL; ! // check the multiplexer for stream events $ready = stream_select($read, $write, $except, 0); foreach($read as $readFd) { // .. read } foreach($write as $writeFd) { // .. write } }

$urls = ['www.amazon.com',...];

foreach($urls as $ip => $url) { // create a stream that returns immediately // STREAM_CLIENT_CONNECT _MUST_ be passed // STREAM_CLIENT_ASYNC_CONNECT says create connection // asynchronously $socket = stream_socket_client( "tcp://$ip:80", $errno, $errstr, 0, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT

// set stream as non-blocking with 0, default is 1 stream_set_blocking($socket, 0);

// sockets = read streams, requests = write streams $sockets[(int) $socket] = $socket; $requests[(int) $socket] = $socket;

non-blocking i/o

non-blocking i/owww.reddit.com: 0.18858www.hackernews.com: 0.37317www.google.com: 0.10562www.yahoo.com: 0.10172www.amazon.com: 0.68584!

Total time: 0.69041(PHP Blocking Example: 4.51274)

event-drivennon-blocking i/o

Igor Wiedler

Christopher Boden#reactphp

igorw/evenementEventEmitter

on($event, callable $listener)$this->on('pipe', array($this, 'handleEvent'));

emit($event, array $arguments = [])$this->emit('data', array($data, $this));

once($event, callable $listener)$this->once('init', array($this, 'configure'));

event loop

service request

dispatcher

ticknextTickfutureTick

demultiplexertimers & streams

event handlercallback

even

t loop

periodic timers$loop->addPeriodicTimer(1, function($timer) { echo "Yes, I am annoying =)" . PHP_EOL;});!

one off timers$loop->addTimer(1, function($timer) { echo "I'm a one off timer.” . PHP_EOL;});

interval in seconds

callback Timer object

// cancel that annoying timer $timer->cancel(); });

timers

STREAMSStream($stream, $loop) events:

data, close, error, drain

$readStream = new Stream(fopen($file,"r"),$loop);$readStream->on('data',function($data, $stream) { //do something with $data});

readableStream

writeableStreamemit->(‘pipe’)

STREAMS & PIPING

on->(‘drain’)resume()on->(‘data’)

write($data)end()

end()pause()

if $data > $limit

$readStream->pipe($writeStream);

Promises/A

states

“A promise represents the eventual value returned from the single completion of an operation.”

“once fulfilled or rejected the promise’s value shall not be changed”

pending, fulfilled, rejected

http://wiki.commonjs.org/wiki/Promises/A

working with a Promise $loop = React\EventLoop\Factory::create(); $factory = new React\Dns\Resolver\Factory(); $dns = $factory->create('8.8.8.8', $loop);! $dns ->resolve('github.com') ->then(function ($ip) { echo "Host: $ip\n"; } ); $loop->run();

Deferred

working with Promise\all()

$promises = array( $file1->read(), $file2->read());!Promise\all($promises)->then(function($v){});Promise\race($promises)->then(function($v){});Promise\some($promises, 4)->then(function($v){});

$file1 = new file('test_file.txt', "r", $loop);$file2 = new file('test_file2.txt', "r", $loop);

Server events:connection, error

SOCKETS

Connectionextends Stream

events: data, close, error, drain

$loop = React\EventLoop\Factory::create(); $socket = new React\Socket\Server($loop); !$socket->on('connection', function($conn) { echo $conn->getRemoteAddress() . " connected" . PHP_EOL; $conn->on('data', function($data) { echo "Data received for connection" . PHP_EOL; }); }); !$socket->listen(4000); $loop->run();

page scraper$factory = new React\Dns\Resolver\Factory();$dns = $factory->create('8.8.8.8', $loop);$urls = ['www.amazon.com',...];$msg = "GET / HTTP/1.0\r\nAccept: */*\r\n\r\n";!foreach($urls as $url) { $connector = new React\SocketClient\Connector($loop, $dns); $connector->create($url, 80)->then( function (React\Stream\Stream $stream) use ($msg){ $stream->write($msg); $stream->on('data', function($data, $stream) { // buffer data } ); $stream->on('end', function($stream) { // do something with the data $stream->close(); }); } );}$loop->run();

request timewww.reddit.com: 0.47400www.hackernews.com: 0.41715www.google.com: 0.16216www.yahoo.com: 0.15773www.amazon.com: 0.65287!

Total time: 0.69455(PHP Blocking Example: 4.51274)

Messaging

Messaging Techniques

polling long polling (hanging GET)

pre-

Messaging Techniques

long polling (hanging GET)

streaming

pre-

Server Sent Eventssupported by all major browsers

• automatically re-connects • uni-directional • send arbitrary events

var source = new EventSource('stream.php');

source.addEventListener('message', function(e) { console.log(e.data);}, false);

demoserver sent events

WebSocketssupported by all major browsers

• new URI schemes ws and wss • bi-directional, full-duplex • send messages independently

WebSocketssupported by all major browsers

• multiplayer games • chat applications • social streams • auctions

WebSockets APIsupported by all major browsers

var ws = new WebSocket("ws://www.websockets.org"); ws.onopen = function(e) { console.log("Connection open ..."); }; ws.onmessage = function(e) { console.log( "Received Message: " + e.data); }; ws.onclose = function(e) { console.log("Connection closed."); }; ws.send("Hello WebSockets!"); ws.close();

RatchetWebSocket Support for • HTTP Server • handles WebSockets • supports the WAMP 1.0 protocol

$socketServer = new React\Socket\Server($loop);$socketServer->listen('8080', '0.0.0.0');$websocketServer = new IoServer( new HttpServer( new WsServer( $myWsApp ) ), $socketServer, $loop);

RatchetWebSocket Server

onOpen($conn)

onClose($conn)

onError($from, $error)

onMessage($from, $msg)

demoweb sockets

WAMPWeb Application Messaging Protocol

autobahn.js

Remote Procedure Calls Publish & Subscribe

$socketServer = new React\Socket\Server($loop);$socketServer->listen(8080, '0.0.0.0');$wampServer = new IoServer( new HttpServer( new WsServer( new WampServer( $myWampApp ) ) ), $socketServer, $loop);

Ratchet

onCall($conn, $id, $topic, $params)

onPublish($conn, $topic, $event)

onSubscribe($conn, $topic)

onUnsubscribe($conn, $topic)

Ratchet$conn->callResult($id,$this->playerData);wamp connection

$topic->broadcast($event, array($conn->WAMP->sessionId));topic$conn->callError($id, $topic, 'You are not allowed to make calls');

demoWAMP

results based on: http://philsturgeon.uk/blog/2013/11/benchmarking-codswallop-nodejs-v-php

OTHER INTERESTING LIBRARIES:

AMP: dispatch blocking calls to worker threads.https://github.com/rdlowrey/Amp

INTERESTING READS:COOPERATIVE MULTI-TASKING USING COROUTINES IN PHPhttp://bit.ly/1nTAV4e - nikic

Q & A

http://socketo.me http://reactphp.org

Code Examples:https://github.com/steverhoades/drupalcampla

steve rhoadestwitter+github @steverhoades