Reactive PHP
ARKADIUSZ KONDAS
Data Scientist
@ Buddy
Zend Certified Engineer
- Data Scientist
- Software Architect
- Event Stormer
- 11 years of experience
@ ArkadiuszKondas
arkadiuszkondas.com
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.2877
Blocking 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 ms
Lets multiply all these durations by a billion
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
Minute
Main memory reference 100 s Brushing your teeth
Compress 1K bytes with Zippy 50 min One episode of a TV show (including ad breaks)
Hour
Send 2K bytes over 1 Gbps network 5.5 hr From lunch to end of work day
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
Week
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
Year
Send packet CA->Netherlands->CA 4.8 years Average time it takes to complete a bachelor's degree
Decade
C10K 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
https://help.dreamhost.com/hc/en-us/articles/215945987-Web-server-performance-comparison
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.2877
Non-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) {
}
}
}
NodeJS
No JS!
Event-driven, non-blocking I/O with PHP.
ReactPHP
- 100% pure PHP
- no extensions
- no magic
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();
EventLoop
Asynchronous != Parallel
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, close
Writable Stream
interface WritableStreamInterface
{
public function isWritable();
public function write($data);
public function end($data = null);
public function close();
}
Events: drain, error, close, pipe
Stream 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
$connection->on('data', function ($data) use ($server, $connection) {
$data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data));
if ($data === '') {
return;
}
$data = parse_url($connection->getRemoteAddress(), PHP_URL_HOST)
. ': ' . $data . PHP_EOL;
foreach ($server->getConnections() as $client) {
if($connection !== $client) {
$client->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
bit.ly/phpsmasher
ReactPHP on the battlefield
Ratchet
http://socketo.me/
Predis\Async
https://github.com/nrk/predis-async
- https://github.com/reactphp/zmq
- https://github.com/friends-of-reactphp/mysql
- https://github.com/reactphp/stomp
- https://github.com/voryx/Thruway
- https://github.com/reactphp/http-client
- https://github.com/apisearch-io/symfony-async-kernel
- ...
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
References
- https://reactphp.org/
- https://thetechsolo.wordpress.com/2016/02/29/scalable-io-events-vs-multithreading-based/
- https://github.com/php-pm/php-pm
- https://github.com/clue/php-redis-server
- https://redis.io/topics/benchmarks
- https://gist.github.com/jboner/2841832 (Latency Numbers Every Programmer Should Know)
- https://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html
Thanks for listening
@ ArkadiuszKondas
https://github.com/akondas
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,080