/examples/PHP/peering3.php
PHP | 272 lines | 186 code | 36 blank | 50 comment | 33 complexity | 31e4c2789fc5b43d68fe50b9750901c9 MD5 | raw file
- <?php
- /*
- * Broker peering simulation (part 3)
- * Prototypes the full flow of status and tasks
- * @author Ian Barber <ian(dot)barber(at)gmail(dot)com>
- */
- include "zmsg.php";
- define("NBR_CLIENTS", 10);
- define("NBR_WORKERS", 3);
- /*
- * Request-reply client using REQ socket
- * To simulate load, clients issue a burst of requests and then
- * sleep for a random period.
- */
- function client_thread($self) {
- $context = new ZMQContext();
- $client = new ZMQSocket($context, ZMQ::SOCKET_REQ);
- $endpoint = sprintf("ipc://%s-localfe.ipc", $self);
- $client->connect($endpoint);
-
- $monitor = new ZMQSocket($context, ZMQ::SOCKET_PUSH);
- $endpoint = sprintf("ipc://%s-monitor.ipc", $self);
- $monitor->connect($endpoint);
- $readable = $writeable = array();
-
- while(true) {
- sleep(mt_rand(0, 4));
- $burst = mt_rand(1, 14);
- while($burst--) {
- // Send request with random hex ID
- $task_id = sprintf("%04X", mt_rand(0, 10000));
- $client->send($task_id);
-
- // Wait max ten seconds for a reply, then complain
- $poll = new ZMQPoll();
- $poll->add($client, ZMQ::POLL_IN);
- $events = $poll->poll($readable, $writeable, 10 * 1000000);
- if($events > 0) {
- foreach($readable as $socket) {
- $zmsg = new Zmsg($socket);
- $zmsg->recv();
- // Worker is supposed to answer us with our task id
- assert($zmsg->body() == $task_id);
- }
- } else {
- $monitor->send(sprintf("E: CLIENT EXIT - lost task %s", $task_id));
- exit();
- }
- }
- }
- }
- // Worker using REQ socket to do LRU routing
- function worker_thread ($self) {
- $context = new ZMQContext();
- $worker = $context->getSocket(ZMQ::SOCKET_REQ);
- $endpoint = sprintf("ipc://%s-localbe.ipc", $self);
- $worker->connect($endpoint);
- // Tell broker we're ready for work
- $worker->send("READY");
-
- while(true) {
- $zmsg = new Zmsg($worker);
- $zmsg->recv();
-
- sleep(mt_rand(0,2));
- $zmsg->send();
- }
- }
- // First argument is this broker's name
- // Other arguments are our peers' names
- if($_SERVER['argc'] < 2) {
- echo "syntax: peering2 me {you}...", PHP_EOL;
- exit();
- }
- $self = $_SERVER['argv'][1];
- for($client_nbr = 0; $client_nbr < NBR_CLIENTS; $client_nbr++) {
- $pid = pcntl_fork();
- if($pid == 0) {
- client_thread($self);
- return;
- }
- }
- for($worker_nbr = 0; $worker_nbr < NBR_WORKERS; $worker_nbr++) {
- $pid = pcntl_fork();
- if($pid == 0) {
- worker_thread($self);
- return;
- }
- }
- printf ("I: preparing broker at %s... %s", $self, PHP_EOL);
- // Prepare our context and sockets
- $context = new ZMQContext();
- // Bind cloud frontend to endpoint
- $cloudfe = $context->getSocket(ZMQ::SOCKET_ROUTER);
- $endpoint = sprintf("ipc://%s-cloud.ipc", $self);
- $cloudfe->setSockOpt(ZMQ::SOCKOPT_IDENTITY, $self);
- $cloudfe->bind($endpoint);
- // Connect cloud backend to all peers
- $cloudbe = $context->getSocket(ZMQ::SOCKET_ROUTER);
- $cloudbe->setSockOpt(ZMQ::SOCKOPT_IDENTITY, $self);
- for ($argn = 2; $argn < $_SERVER['argc']; $argn++) {
- $peer = $_SERVER['argv'][$argn];
- printf ("I: connecting to cloud backend at '%s'%s", $peer, PHP_EOL);
- $endpoint = sprintf("ipc://%s-cloud.ipc", $peer);
- $cloudbe->connect($endpoint);
- }
- // Bind state backend / publisher to endpoint
- $statebe = new ZMQSocket($context, ZMQ::SOCKET_PUB);
- $endpoint = sprintf("ipc://%s-state.ipc", $self);
- $statebe->bind($endpoint);
- // Connect statefe to all peers
- $statefe = $context->getSocket(ZMQ::SOCKET_SUB);
- $statefe->setSockOpt(ZMQ::SOCKOPT_SUBSCRIBE, "");
- for ($argn = 2; $argn < $_SERVER['argc']; $argn++) {
- $peer = $_SERVER['argv'][$argn];
- printf ("I: connecting to state backend at '%s'%s", $peer, PHP_EOL);
- $endpoint = sprintf("ipc://%s-state.ipc", $peer);
- $statefe->connect($endpoint);
- }
- // Prepare monitor socket
- $monitor = new ZMQSocket($context, ZMQ::SOCKET_PULL);
- $endpoint = sprintf("ipc://%s-monitor.ipc", $self);
- $monitor->bind($endpoint);
- // Prepare local frontend and backend
- $localfe = new ZMQSocket($context, ZMQ::SOCKET_ROUTER);
- $endpoint = sprintf("ipc://%s-localfe.ipc", $self);
- $localfe->bind($endpoint);
- $localbe = new ZMQSocket($context, ZMQ::SOCKET_ROUTER);
- $endpoint = sprintf("ipc://%s-localbe.ipc", $self);
- $localbe->bind($endpoint);
- // Interesting part
- // -------------------------------------------------------------
- // Publish-subscribe flow
- // - Poll statefe and process capacity updates
- // - Each time capacity changes, broadcast new value
- // Request-reply flow
- // - Poll primary and process local/cloud replies
- // - While worker available, route localfe to local or cloud
- // Queue of available workers
- $local_capacity = 0;
- $cloud_capacity = 0;
- $worker_queue = array();
- $readable = $writeable = array();
- while(true) {
- $poll = new ZMQPoll();
- $poll->add($localbe, ZMQ::POLL_IN);
- $poll->add($cloudbe, ZMQ::POLL_IN);
- $poll->add($statefe, ZMQ::POLL_IN);
- $poll->add($monitor, ZMQ::POLL_IN);
- $events = 0;
- // If we have no workers anyhow, wait indefinitely
- try {
- $events = $poll->poll($readable, $writeable, $local_capacity ? 1000000 : -1);
- } catch(ZMQPollException $e) {
- break;
- }
- // Track if capacity changes during this iteration
- $previous = $local_capacity;
- foreach($readable as $socket) {
- $zmsg = new Zmsg($socket);
-
- // Handle reply from local worker
- if($socket === $localbe) {
- // Use worker address for LRU routing
- $zmsg->recv();
- $worker_queue[] = $zmsg->unwrap();
- $local_capacity++;
- if($zmsg->body() == "READY") {
- $zmsg = null; // Don't route it
- }
- }
- // Or handle reply from peer broker
- else if($socket === $cloudbe) {
- // We don't use peer broker address for anything
- $zmsg->recv()->unwrap();
- }
- // Handle capacity updates
- else if($socket === $statefe) {
- $zmsg->recv();
- $cloud_capacity = $zmsg->body();
- $zmsg = null;
- }
- // Handle monitor message
- else if($socket === $monitor) {
- $zmsg->recv();
- echo $zmsg->body(), PHP_EOL;
- $zmsg = null;
- }
-
- if($zmsg) {
- // Route reply to cloud if it's addressed to a broker
- for($argn = 2; $argn < $_SERVER['argc']; $argn++) {
- if($zmsg->address() == $_SERVER['argv'][$argn]) {
- $zmsg->set_socket($cloudfe)->send();
- $zmsg = null;
- }
- }
- }
-
- // Route reply to client if we still need to
- if($zmsg) {
- $zmsg->set_socket($localfe)->send();
- }
- }
- // Now route as many clients requests as we can handle
- // - If we have local capacity we poll both localfe and cloudfe
- // - If we have cloud capacity only, we poll just localfe
- // - Route any request locally if we can, else to cloud
- while($local_capacity + $cloud_capacity) {
- $poll = new ZMQPoll();
- $poll->add($localfe, ZMQ::POLL_IN);
- if($local_capacity) {
- $poll->add($cloudfe, ZMQ::POLL_IN);
- }
- $reroutable = false;
- $events = $poll->poll($readable, $writeable, 0);
- if($events > 0) {
- foreach($readable as $socket) {
- $zmsg = new Zmsg($socket);
- $zmsg->recv();
-
- if($local_capacity) {
- $zmsg->wrap(array_shift($worker_queue), "");
- $zmsg->set_socket($localbe)->send();
- $local_capacity--;
- }
- else {
- // Route to random broker peer
- printf ("I: route request %s to cloud...%s", $zmsg->body(), PHP_EOL);
- $zmsg->wrap($_SERVER['argv'][mt_rand(2, ($_SERVER['argc']-1))]);
- $zmsg->set_socket($cloudbe)->send();
- }
- }
- } else {
- break; // No work, go back to backends
- }
- }
-
- if ($local_capacity != $previous) {
- // Broadcast new capacity
- $zmsg = new Zmsg($statebe);
- $zmsg->body_set($local_capacity);
- // We stick our own address onto the envelope
- $zmsg->wrap($self)->send();
- }
- }