Reactive PHP
ARKADIUSZ KONDAS
Lead Software Architect
@ Proget Sp. z o.o.
Zend Certified Engineer
Code Craftsman
Blogger
Ultra Runner
@ ArkadiuszKondas


itcraftsman.pl


Zend Certified Architect

Agenda:
- The genesis ...
- The rise ...
- The pleasure ...

To block or not to block (I/O)
PHP page scraper
$urls = ['amazon.com', 'ebay.com', 'allegro.pl'];
foreach ($urls as $url) {
    $content = file_get_contents(
        sprintf('http://%s', $url)
    );
}
amazon.com:    1.3739
ebay.com:      1.4950
allegro.pl:    0.4184
Total time:    3.2877Blocking I/O

I/O is slow
Latency numbers every programmer should know
L1 cache reference ......................... 0.5 ns
Branch mispredict ............................ 5 ns
L2 cache reference ........................... 7 ns
Mutex lock/unlock ........................... 25 ns
Main memory reference ...................... 100 ns             
Compress 1K bytes with Zippy ............. 3,000 ns  =   3 µs
Send 2K bytes over 1 Gbps network ....... 20,000 ns  =  20 µs
SSD random read ........................ 150,000 ns  = 150 µs
Read 1 MB sequentially from memory ..... 250,000 ns  = 250 µs
Round trip within same datacenter ...... 500,000 ns  = 0.5 ms
Read 1 MB sequentially from SSD* ..... 1,000,000 ns  =   1 ms
Disk seek ........................... 10,000,000 ns  =  10 ms
Read 1 MB sequentially from disk .... 20,000,000 ns  =  20 ms
Send packet CA->Netherlands->CA .... 150,000,000 ns  = 150 msLatency numbers every programmer should know
L1 cache reference                  0.5 s         One heart beat (0.5 s)
Branch mispredict                   5 s           Yawn
L2 cache reference                  7 s           Long yawn
Mutex lock/unlock                   25 s          Making a coffee
Main memory reference               100 s         Brushing your teeth
Compress 1K bytes with Zippy        50 min        One episode of a TV show 
                                                  (including ad breaks)
Send 2K bytes over 1 Gbps network   5.5 hr        From lunch to end of work day
SSD random read                     1.7 days      A normal weekend
Read 1 MB sequentially from memory  2.9 days      A long weekend
Round trip within same datacenter   5.8 days      A medium vacation
Read 1 MB sequentially from SSD    11.6 days      Waiting for almost 2 weeks 
                                                  for a delivery
Disk seek                           16.5 weeks    A semester in university
Read 1 MB sequentially from disk    7.8 months    Almost producing a new 
                                                  human being
The above 2 together                1 year
Send packet CA->Netherlands->CA     4.8 years     Average time it takes to 
                                                  complete a bachelor's degreeC10K Problem
You can buy a 1000MHz machine with 2 gigabytes of RAM and an 1000Mbit/sec Ethernet card for $1200 or so. Let's see - at 20000 clients, that's 50KHz, 100Kbytes, and 50Kbits/sec per client.
It shouldn't take any more horsepower than that to take four kilobytes from the disk and send them to the network once a second for each of twenty thousand clients.
So hardware is no longer the bottleneck. (Dan Kegel)
10–12 million connections (MigratoryData, 12 cores, using Java on Linux
https://mrotaru.wordpress.com/2013/10/10/scaling-to-12-million-concurrent-connections-how-migratorydata-did-it/
Non-blocking I/O
Threads

Apache
Multiplexing
kevent/select/pool

Apache vs Nginx

NodeJS - page scraper
var http = require('http');
var urls = ['amazon.com', 'ebay.com', 'allegro.pl'];
for (let i in urls) {
    http.get('http://' + urls[i], function(response){
        var str = '';
        response.on('data', function(chunk) {
            str += chunk;
        });
        response.on('end', function(){});
    });
}
allegro.pl:   0.3901
amazon.com:   0.4755
ebay.com:     0.6653
Total time:   0.6756
amazon.com:    1.3739
ebay.com:      1.4950
allegro.pl:    0.4184
Total time:    3.2877Non-blocking I/O

select/poll
SELECT(2)                                    Linux Programmer's Manual                          
NAME
       select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
SYNOPSIS
       /* According to POSIX.1-2001, POSIX.1-2008 */
       #include <sys/select.h>
       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
       int pselect(int nfds, fd_set *readfds, fd_set *writefds,
                   fd_set *exceptfds, const struct timespec *timeout,
                   const sigset_t *sigmask);
DESCRIPTION
       select()  and  pselect()  allow a program to monitor multiple file descriptors, 
       waiting until one or more of the file descriptors become "ready" for some class
       of I/O operation (e.g., input possible).  A file descriptor  is  considered  
       ready if it is possible to perform a corresponding I/O operation (e.g., read(2) 
       without blocking, or a sufficiently small write(2)).
Tools in PHP
- pthreads
- popen()
- proc_open()
- pcntl_fork()
 
- socket_select()
- stream_select()
- stream_set_blocking()
- curl_multi_select()
Non-blocking I/O in PHP
<?php
$readable = [stream_socket_client(), stream_socket_client()];
$writable = $writeStreams;
$except = [];
while(true) {
    if (stream_select($readable, $writable, $except, 1)) {
        foreach ($readable as $stream) {
    
        }
        foreach ($writable as $stream) {
    
        }
    }
}[] == ![]
true

Welcome to JS
NodeJS
No JS!

Event-driven, non-blocking I/O with PHP.

Architecture
EventEmmiter
EventLoop
Stream
Socket
ChildProcess
DNS
Http
HttpClient
Stomp
ØMQ
Whois
Redis
WebSockets
AR.drone
Promise
Filesystem
EventEmitter
$emitter = new Evenement\EventEmitter();
$emitter->on('data', function ($data) {
    echo $data;
});
$emitter->emit('data', [$data]);EventLoop
- core low level component
- Reactor pattern :)
- wait for streams to become
	- writeable
- readable
 
- use Factory to create instance, implementations:
	- StreamSelectLoop
- LibEventLoop, LibEvLoop, ExtEventLoop
 
- usually just past the loop around
- run forever (unless stopped or done)
- timers
	- once
- perodic
 
EventLoop
$loop = React\EventLoop\Factory::create();
$server = stream_socket_server('tcp://127.0.0.1:8080');
stream_set_blocking($server, 0);
$loop->addReadStream($server, function ($server) use ($loop) {
    $conn = stream_socket_accept($server);
    $loop->addWriteStream($conn, function ($conn)  {
    });
});
$loop->addPeriodicTimer(5, function () {
    $memory = memory_get_usage() / 1024;
    $formatted = number_format($memory, 3).'K';
    echo "Current memory usage: {$formatted}\n";
});
$loop->run();Stream
- read file content in chunks
- types
	- readable (e.g. STDIN)
- writeable (e.g. STDOUT)
- duplex (e.g. TCP/IP connection)
 
- very similar to the streams found in PHP itself, but have an interface more suited for async, non-blocking I/O
Readable Stream
interface ReadableStreamInterface
{
    public function isReadable();
    public function pause();
    public function resume();
    public function close();
    public function pipe(WritableStreamInterface $dest, array $options = array());
}
Events: data, error, end, closeWritable Stream
interface WritableStreamInterface
{
    public function isWritable();
    public function write($data);
    public function end($data = null);
    public function close();
}
Events: drain, error, close, pipeStream pipe FTW
use React\EventLoop\Factory;
use React\Stream\ReadableResourceStream;
use React\Stream\WritableResourceStream;
$loop = Factory::create();
$source = new ReadableResourceStream(
    fopen('omg.txt', 'r'), $loop
);
$dest = new WritableResourceStream(
    fopen('wtf.txt', 'w'), $loop
);
$source->pipe($dest);
$loop->run();Socket
- abstract for stream_socket_server
- streaming plaintext TCP/IP and secure TLS socket serve
- more usable interface for a socket-layer server based on the EventLoop and Stream components.
Socket
$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server(8080, $loop);
$socket->on('connection', function (ConnectionInterface $conn) {
    $conn->write("Hello " . $conn->getRemoteAddress() . "!\n");
    $conn->write("Welcome to this amazing server!\n");
    $conn->write("Here's a tip: don't say anything.\n");
    $conn->on('data', function ($data) use ($conn) {
        $conn->close();
    });
});
$loop->run();Promise
- placeholder for a single future result
- states
	- pending
- fulfilled (success)
- rejected (failure)
 
Deferred
- computation or unit of work that may not have completed yet
- resolve($value)
- reject($reason)
- progress($data)
Promise
$urls = ['...', '...'];
$promises = [];
foreach($urls as $url) {
    $deferred = new Promise\Deferred();
    $request = $client->request('GET', $url);
    $request->on('response', 
    function ($response) use ($deferred, $url) {
        $response->on('end', function ($data) use ($deferred, $url) {
            $deferred->resolve($url);
        });            
    });
    $request->end();
    $promises[] = $deferred->promise();
}
Promise\race($promises)->then(function($value) {
	echo $value . " wins the race!" . PHP_EOL;
});Promise example
$loop = \React\EventLoop\Factory::create();
$factory = new \React\Dns\Resolver\Factory();
$dns = $factory->create('8.8.8.8', $loop);
$dns->resolve($argv[1])
    ->then(
        function ($ip) { echo "Host: $ip\n"; },
        function ($error) { echo "Error: {$error->getMessage()}\n"; }
    );
$loop->run();php bin/promise.php 4developers.pl
Host: 87.98.239.5
php bin/promise.php aaa           
Error: DNS Request did not return valid answer.
Avoid blocking
- loop must not be blocked
- most of functions assume blocking by default!
- anything above 1ms is blocking (?)
- alternative:
	- single result: Promise
- events: Stream
 
- need a blocking function?
	- fork off!
 
Fork off
$loop = React\EventLoop\Factory::create();
$process = new React\ChildProcess\Process('echo foo');
$process->start($loop);
$process->stdout->on('data', function ($chunk) {
    echo $chunk;
});
$process->on('exit', function($exitCode, $termSignal) {
    echo 'Process exited with code ' . $exitCode . PHP_EOL;
});
$loop->run();Show me the example
Canonical: chat server
<?php
$client->on('data', function ($data) use ($client, &$clients) {
    $data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data));
    if ($data === '') {
        return;
    }
    $data = $client->getRemoteAddress() . ': ' . $data . PHP_EOL;
    $otherClients = array_filter($clients, function (ConnectionInterface $c) use ($client) {
        return $c!==$client;
    });
    array_walk($otherClients, function(ConnectionInterface $c) use ($data){
        $c->write($data);
    });
});Canonical: chat server
reactphp/http-client


Text
VS
https://philsturgeon.uk/php/2013/11/12/benchmarking-codswallop-nodejs-v-php/
clue/php-redis-server
- Redis - written in C
- Implementations written in pure PHP
- Simple to add new commands
- How fast could is PHP ...
php-smasher
https://github.com/itcraftsmanpl/php-smasher

ReactPHP on the battlefield
Ratchet

http://socketo.me/
Predis\Async
https://github.com/nrk/predis-async

React/ZMQ
https://github.com/reactphp/zmq

React/STOMP
https://github.com/reactphp/stomp

php-ar-drone
https://github.com/jolicode/php-ar-drone

php-ar-drone
$drone->on('landed', function() {
    // do something
});
$drone
    ->after(3, function () use ($drone) {
        $drone->up(0.6);
    })
    ->after(4, function () use ($drone) {
        $drone->stop();
    })
    ->after(1, function () use ($drone) {
        $drone->left(0.3);
    })
$drone->start();Still not enough?
Life of a PHP request
- Parsing
- AST (Abstract Syntax Tree)
- bytcode
- execution
opcache

please enable opcache
Why so long ...


Life of a PHP request
- Initialization
	- autoloading
- memory allocation
		- DI
 
 
- Useful execution

reactphp/http

PPM - PHP Process Manager

https://github.com/php-pm/php-pm
PPM
- work as process manager on top of React PHP
- written in pure PHP
- much like PHP-FPM
- works best with applications that use request-response frameworks like Symfony's HTTPKernel
PHP Process Manager

PPM - caveats!
- debugging
- memory leaks (restarts works nice)

Summary
Summary
- React is real deal
- React doesn't make your code faster
- React is ready for production
- Consider using React when:
	- have to wait
- have to access network
 
Alternative

amphp
Asynchronous Multitasking PHP: Hypertext Preprocessor
https://github.com/amphp


Amp\Thread
function slowAddition($x, $y) {
    sleep(1);
    return $x + $y;
}
$dispatcher = new Amp\Thread\Dispatcher;
$a = $dispatcher->call('slowAddition', 1, 5);
$b = $dispatcher->call('slowAddition', 10, 10);
$c = $dispatcher->call('slowAddition', 11, 31);
$comboPromise = Amp\all([$a, $b, $c]);
list($a, $b, $c) = $comboPromise->wait();Swoole
https://github.com/swoole/swoole-src

Swoole is an event-driven asynchronous & concurrent networking communication framework with high performance written only in C for PHP.
More to read
http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html
Cooperative multitasking using coroutines (in PHP!)
by Nikita Popov
Q&A
Thanks for listening
@ ArkadiuszKondas

https://github.com/itcraftsmanpl/php-smasher
Reactive PHP
By Arkadiusz Kondas
Reactive PHP
Programowanie sterowane zdarzeniami (event-driven) jest znane i używane w aplikacjach klient / serwer oraz bibliotek asynchronicznych. RactPHP (zbliżony do node.js lub Python Twisted), przenosi PHP na nowy poziom prędkości ze wszystkimi niezbędnym do tego funkcjami. Podczas prezentacji wprowadzę pojęcie event loop, non-blocking IO i programowania asynchronicznego w PHP, a także przedstawię sposoby gdzie ta technika może przydać się w twoim stosie technologicznym.
- 2,882
 
   
   
  