IT Articles

I.T. articles including tutorials and technologies evaluations.

RabbitMQ is a great option for the implementation of an AMQP (Advanced Message Queuing Protocol) queuing system.

Why would you need a queuing system? So that you can offload heavy tasks to be processed in a separate process asynchronously and avoid blocking your client, on your website for instance.

Examples of use cases are (but not limited to):

  • Processing a file. It is usually ok to send a file synchronously but if you need to process that file and convert it for different types of rendering or data extraction then you might not want to lock the user while forcing him to wait for the file to be ready/done with. 
  • Messaging/Sending of emails. Sending emails through POP3 or IMAP could take a while and considerably slow down your server for a task that could easily be offloaded to a separate process.
  • Perform heavy calculations. For instance running a bulk load of reporting and operations that would involve the interaction with databases

So why this article? Well the great getting started articles at RabbitMQ are really straight forward but some of the set up is not done for you. This setup/article configures the php containers with the right modules and enables them so that the AMQP library can run from PHP.

As I like to dockerise everything here is my set up ready for you to checkout and run on your machine.

  1. Checkout (git clone) the repo at https://github.com/bizmate/getting-started-rabbitmq-php-dockerised
  2. Start the cluster/stack. On the root of the project run make up. This operation starts a few containers, after pulling and building the images it needs, including 
    1. 1 sender, script able to send a message to the queue,
    2. 5 workers waiting for RabbitMQ to send them the messages from the queue.
    3. Composer downloads the amqp library used to interact with RabbitMQ as a dependency inside the vendor folder
    4. Finally RabbitMQ with a few plugins enabled so that an instance of the Admin is available at http://localhost:15672/ where load information, queues and more can be inspected in realtime.
  3. Listen to the docker containers output. One of the greatest thing about Docker is that it is all about processes. You can see the messages/logs spit by each container by running make tail . This command will output everything the containers are doing. This is including the messages being echoed by the send.php, the output from the RabbitMQ server when it starts and it receives all messages and sends it in the queue to the workers, etc
  4. Feed a few messages to the queue. To do this run the command make push_jobs. This steps runs forever unless stopped (for instance but running CTRL + C) and it keeps running the send.php file. This file sends an Hello World! message the the hello queue as shown here.
  5. You can now see a few messages still being left in the queue when visiting http://localhost:15672/#/queues . 

The receivers/workers are randomly delayed so you can see a few messages piling up in the queue. A sample output from the docker logs is

 

php_receiver_4 | [x] Host: 3160348a0f9f Waiting: 26 seconds. Received msg: Hello World!
rabbitmq_1 | 2018-02-19 21:45:54.389 [info] <0.10243.0> closing AMQP connection <0.10243.0> (172.20.0.8:33396 -> 172.20.0.3:5672, vhost: '/', user: 'guest')
rabbitmq_1 | 2018-02-19 21:45:54.543 [info] <0.10256.0> accepting AMQP connection <0.10256.0> (172.20.0.8:33398 -> 172.20.0.3:5672)
rabbitmq_1 | 2018-02-19 21:45:54.549 [info] <0.10256.0> connection <0.10256.0> (172.20.0.8:33398 -> 172.20.0.3:5672): user 'guest' authenticated and granted access to vhost '/'
rabbitmq_1 | 2018-02-19 21:45:54.588 [warning] <0.10256.0> closing AMQP connection <0.10256.0> (172.20.0.8:33398 -> 172.20.0.3:5672, vhost: '/', user: 'guest'):
rabbitmq_1 | client unexpectedly closed TCP connection
php_receiver_1 | [x] Done
php_receiver_4 | [x] Done

The receive.php core part is implemented in these lines https://github.com/bizmate/getting-started-rabbitmq-php-dockerised/blob/master/src/receive.php#L19-L26 and as you can see it waits for a random number of seconds before echoing the message received by the send.php/queue.

Thats all folks!

I hope you find this article useful. 

If you are looking for a PHP/DevOps/Docker consultant feel free to drop me a message from the contact us link. 

Guzzle is a great wrapper to run Curl requests from your PHP applications

As part of my development requirements for MyReviews.link, I had to implement a fast concurrent way to perform http requests to several servers. 

With Guzzle Promises this can be achieved in a very simplified way. Here is the example

 

$client = new GuzzleClient(['timeout' => 12.0]); // see how i set a timeout

//
$requestPromises = [];
$sitesArray = SiteEntity->getAll(); // returns an array with objects that contain a domain

foreach($sitesArray as $site)
{
$requestPromises[$site->getDomain()] = $client->getAsync('http://' . $site->getDomain());
}

$results = GuzzlePromise\settle($requestPromises)->wait();

foreach($results as $domain => $result)
{
$site = $sitesArray[$domain];
$this->logger->info('Crawler FetchHomePages: domain check ' . $domain);

if($result['state'] === 'fulfilled')
{
$response = $result['value'];
if($response->getStatusCode() == 200)
{
$site->setHtml( $response->getBody() );
}
else{
$site->setHtml( $response->getStatusCode() );
}
}
else if($result['state'] === 'rejected'){ // notice that if call fails guzzle returns is as state rejected with a reason.

$site->setHtml('ERR: ' . $result['reason'] );
}
else{
$site->setHtml('ERR: unknown exception ' );
$this->logger->err('Crawler FetchHomePages: unknown fetch fail domain: ' . $domain );
}

$this->entityManager->persist($site); // this is a call to Doctrines entity manager
}

I hope the above helped.

Feel free to send me a message through the contact us link, Github or Linkedin

Popular Links