Upload
jason-lotito
View
2.174
Download
2
Tags:
Embed Size (px)
DESCRIPTION
PHP, RabbitMQ, and You talk given at MidwestPHP 2014 Video of the screens can be found here http://www.youtube.com/watch?v=Nh5oFSXEg6k This includes the videos of the sample code.
Citation preview
PHP, RabbitMQ, and You#mwphp14 #rabbitmq @jasonlotito
MidwestPHP 2014 - RabbitMQ What will we be covering?
1. What is RabbitMQ
2. Technology Overview
3. Publishers
4. Consumers
5. Exchanges
6. Queues
7. Bindings
8. Carrot to make things easy
9. Publish events from the web
10.Multiple consumers
11.Management UI Publishing
12.Consumers Publishing
PHP, RabbitMQ, and You#mwphp14 #rabbitmq @jasonlotito
Jason LotitoSenior Architect @ MeetMe
@jasonlotito.com github.com/[email protected]
!
Senior Architect means people can blame me when things don’t work as expected.
When things work, it’s because they worked around my code.
Who has worked with RabbitMQ in production?
Raise your hands. The only audience participation part, I promise.
Part 1 Crash Course In RabbitMQ
1. What is RabbitMQ
2. Technology Overview
3. Publishers
4. Consumers
5. Exchanges
6. Queues
7. Bindings
– RabbitMQ In Action*, Manning
“RabbitMQ is an open source message broker and queueing server that can be used to let
disparate applications share data via a common protocol, or to simply queue jobs for processing
by distributed workers.
”
Where RabbitMQ Sits(P) Producer/Publisher - (X) Exchange - (C) Consumer
Event Occurs in Application(P) Producer/Publisher - (X) Exchange - (C) Consumer
Message is Sent to Exchange(P) Producer/Publisher - (X) Exchange - (C) Consumer
Message is Sent to Queue(P) Producer/Publisher - (X) Exchange - (C) Consumer
Exchanges connect to Queues through Bindings
Message is Sent to Consumer(P) Producer/Publisher - (X) Exchange - (C) Consumer
– Me, Now
“Where as a database handles your data, a message queue handles your events.”
A database handles nouns. A message queue handles verbs.
But enough talkLet’s see some code!
composer.json"require": { "videlalvaro/php-amqplib": "2.2.*"}
We are starting with the publisher
Publisher: send.php<?php// Setup, $ php send.php whatever you want to sendrequire_once 'vendor/autoload.php'; $config = require('config.php'); use PhpAmqpLib\Connection\AMQPConnection; use PhpAmqpLib\Message\AMQPMessage; // Message Prep$connection = new AMQPConnection($config['mq']['host'], $config['mq']['port'], $config['mq']['user'], $config['mq']['pass']); $channel = $connection->channel(); $message = join(' ', array_splice($argv, 1)); $message = empty($message) ? 'Hello world!' : $message; // Publish Message$channel->basic_publish(new AMQPMessage( $message ), '', 'hello'); echo " [x] Sent '$message'\n"; $channel->close(); $connection->close();
Publisher: send.php<?php// Setup, $ php send.php whatever you want to sendrequire_once 'vendor/autoload.php'; $config = require('config.php'); use PhpAmqpLib\Connection\AMQPConnection; use PhpAmqpLib\Message\AMQPMessage; // Message Prep$connection = new AMQPConnection($config['mq']['host'], $config['mq']['port'], $config['mq']['user'], $config['mq']['pass']); $channel = $connection->channel(); $message = join(' ', array_splice($argv, 1)); $message = empty($message) ? 'Hello world!' : $message; // Publish Message$channel->basic_publish(new AMQPMessage( $message ), '', 'hello'); echo " [x] Sent '$message'\n"; $channel->close(); $connection->close();
Publisher: send.php<?php// Setup, $ php send.php whatever you want to sendrequire_once 'vendor/autoload.php'; $config = require('config.php'); use PhpAmqpLib\Connection\AMQPConnection; use PhpAmqpLib\Message\AMQPMessage; // Message Prep$connection = new AMQPConnection($config['mq']['host'], $config['mq']['port'], $config['mq']['user'], $config['mq']['pass']); $channel = $connection->channel(); $message = join(' ', array_splice($argv, 1)); $message = empty($message) ? 'Hello world!' : $message; // Publish Message$channel->basic_publish(new AMQPMessage( $message ), '', 'hello'); echo " [x] Sent '$message'\n"; $channel->close(); $connection->close();
Publisher: send.php<?php// Setup, $ php send.php whatever you want to sendrequire_once 'vendor/autoload.php'; $config = require('config.php'); use PhpAmqpLib\Connection\AMQPConnection; use PhpAmqpLib\Message\AMQPMessage; // Message Prep$connection = new AMQPConnection($config['mq']['host'], $config['mq']['port'], $config['mq']['user'], $config['mq']['pass']); $channel = $connection->channel(); $message = join(' ', array_splice($argv, 1)); $message = empty($message) ? 'Hello world!' : $message; // Publish Message$channel->basic_publish(new AMQPMessage( $message ), '', 'hello'); echo " [x] Sent '$message'\n"; $channel->close(); $connection->close();
Publisher: send.php<?php// Setup, $ php send.php whatever you want to sendrequire_once 'vendor/autoload.php'; $config = require('config.php'); use PhpAmqpLib\Connection\AMQPConnection; use PhpAmqpLib\Message\AMQPMessage; // Message Prep$connection = new AMQPConnection($config['mq']['host'], $config['mq']['port'], $config['mq']['user'], $config['mq']['pass']); $channel = $connection->channel(); $message = join(' ', array_splice($argv, 1)); $message = empty($message) ? 'Hello world!' : $message; // Publish Message$channel->basic_publish(new AMQPMessage( $message ), '', 'hello'); echo " [x] Sent '$message'\n"; $channel->close(); $connection->close();
Now we create a consumer
Consumer: receive.php<?phprequire_once 'vendor/autoload.php'; $config = require('config.php'); use PhpAmqpLib\Connection\AMQPConnection; $connection = new AMQPConnection($config['mq']['host'], $config['mq']['port'], $config['mq']['user'], $config['mq']['pass']); $channel = $connection->channel(); $channel->queue_declare('hello', false, false, false, true); echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; $handler = function($message) use($channel){ echo sprintf('Message: %s' . PHP_EOL, $message->body); }; $channel->basic_consume('hello', false, true, true, false, false, $handler); $channel->wait();
Consumer: receive.php<?phprequire_once 'vendor/autoload.php'; $config = require('config.php'); use PhpAmqpLib\Connection\AMQPConnection; $connection = new AMQPConnection($config['mq']['host'], $config['mq']['port'], $config['mq']['user'], $config['mq']['pass']); $channel = $connection->channel(); $channel->queue_declare('hello', false, false, false, true); echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; $handler = function($message) use($channel){ echo sprintf('Message: %s' . PHP_EOL, $message->body); }; $channel->basic_consume('hello', false, true, true, false, false, $handler); $channel->wait();
Consumer: receive.php<?phprequire_once 'vendor/autoload.php'; $config = require('config.php'); use PhpAmqpLib\Connection\AMQPConnection; $connection = new AMQPConnection($config['mq']['host'], $config['mq']['port'], $config['mq']['user'], $config['mq']['pass']); $channel = $connection->channel(); $channel->queue_declare('hello', false, false, false, true); echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; $handler = function($message) use($channel){ echo sprintf('Message: %s' . PHP_EOL, $message->body); }; $channel->basic_consume('hello', false, true, true, false, false, $handler); $channel->wait();
Consumer: receive.php<?phprequire_once 'vendor/autoload.php'; $config = require('config.php'); use PhpAmqpLib\Connection\AMQPConnection; $connection = new AMQPConnection($config['mq']['host'], $config['mq']['port'], $config['mq']['user'], $config['mq']['pass']); $channel = $connection->channel(); $channel->queue_declare('hello', false, false, false, true); echo ' [*] Waiting for messages. To exit press CTRL+C', "\n"; $handler = function($message) use($channel){ echo sprintf('Message: %s' . PHP_EOL, $message->body); }; $channel->basic_consume('hello', false, true, true, false, false, $handler); $channel->wait();
Message is Sent to Exchange(P) Producer/Publisher - (X) Exchange - (C) Consumer
Exchange TypesDirect, Fanout, and Topic
Queue bound to many exchanges
$msg = new AMQPMessage( $message ); $channel->basic_publish($msg, '', ‘messages.new');
* matches one word # matches zero or more words
A word is delineated by .
*, #, and .
*.new messages.*
NOT *.messages.*
messages.new matches
NOT spam.* spam.*.* spam.#
spam.message.new matches
Now Let’s Create an Exchange and a Queue
Using the Management UIrabbitmq-plugins enable rabbitmq_management
http://localhost:15672
We’ve Created EverythingPublishers, Exchanges, Bindings, Queues, and Consumers
So what can we do with this?
Part 2 PHP & RabbitMQ Together
1. Carrot to make things easy
2. Publish events from the web
3. Multiple consumers
4. Management UI Publishing
5. Consumers Publishing
Carrotgithub.com/jasonlotito/Carrot
Carrot Consumer Code<?phprequire_once 'vendor/autoload.php'; use Carrot\Consumer; $queue = 'new_messages'; $handler = function($msg){ echo $msg, PHP_EOL; return true; }; (new Consumer())->listenTo($queue, $handler) ->listenAndWait(); !
Carrot Publisher Code
<?phprequire 'vendor/autoload.php'; use Carrot\Publisher; $msg = implode(' ', array_splice($argv, 1)); (new Publisher('messages')) ->publish('message.new', $msg);
Make publishing easy
Make consuming easier
github.com/jasonlotito/midwest-rabbitmq/tree/carrot
Adding the Web
Publisher$publisher = new Publisher('messages'); $sendCount = (int) (isset($_POST['simulatedMessageCount']) ? $_POST['simulatedMessageCount'] : 1); for($x = 0; $x<$sendCount; $x++){ if(isset($_POST['simulateWork'])) { usleep(500000); $msg = ['comment' => $_POST['comment'] . " $x"]; $publisher->eventuallyPublish('message.new', $msg); } else { $msg = ['comment' => $_POST['comment'] . " $x"]; $publisher->publish('message.new', $msg); } }
Let’s use the written Carrot Consumer Code
batch_basic_publishpublic function eventuallyPublish($routingKey, $message) { $msg = $this->buildMessage($message); $channel = $this->getChannel(); $channel->batch_basic_publish($msg, $this->exchange, $routingKey); $this->registerShutdownHandler(); } public function finallyPublish() { if ($this->doBatchPublish) { $this->doBatchPublish = false; $this->getChannel()->publish_batch(); } } !// register finallyPublish private function registerShutdownHandler();
Publish from the web Let’s add another consumer
Without changing existing code
send text messages
$queueName = 'messages_for_nexmo'; (new Consumer()) ->bind($queueName, 'messages', 'message.new') ->listenTo($queueName, function($msg) use($key, $secret, $phoneNumber) { $msg = json_decode($msg); $urlString = 'https://rest.nexmo.com/sms/json?api_key=%s&api_secret=%s' . '&from=17088568489&to=%s&text=%s'; $preparedMessage = urlencode($msg->comment); $url = sprintf($urlString, $key, $secret, $phoneNumber, $preparedMessage); $res = file_get_contents($url); $result = json_decode($res); $messageResult = $result->messages[0]; echo "Message Result: " . ($messageResult->status === '0' ? 'Message Sent' : 'Message not sent') . PHP_EOL; return $messageResult->status === '0'; })->listenAndWait();
send text messages
$queueName = 'messages_for_nexmo'; (new Consumer()) ->bind($queueName, 'messages', 'message.new') ->listenTo($queueName, function($msg) use($key, $secret, $phoneNumber) { $msg = json_decode($msg); $urlString = 'https://rest.nexmo.com/sms/json?api_key=%s&api_secret=%s' . '&from=17088568489&to=%s&text=%s'; $preparedMessage = urlencode($msg->comment); $url = sprintf($urlString, $key, $secret, $phoneNumber, $preparedMessage); $res = file_get_contents($url); $result = json_decode($res); $messageResult = $result->messages[0]; echo "Message Result: " . ($messageResult->status === '0' ? 'Message Sent' : 'Message not sent') . PHP_EOL; return $messageResult->status === '0'; })->listenAndWait();
send text messages
$queueName = 'messages_for_nexmo'; (new Consumer()) ->bind($queueName, 'messages', 'message.new') ->listenTo($queueName, function($msg) use($key, $secret, $phoneNumber) { $msg = json_decode($msg); $urlString = 'https://rest.nexmo.com/sms/json?api_key=%s&api_secret=%s' . '&from=17088568489&to=%s&text=%s'; $preparedMessage = urlencode($msg->comment); $url = sprintf($urlString, $key, $secret, $phoneNumber, $preparedMessage); $res = file_get_contents($url); $result = json_decode($res); $messageResult = $result->messages[0]; echo "Message Result: " . ($messageResult->status === '0' ? 'Message Sent' : 'Message not sent') . PHP_EOL; return $messageResult->status === '0'; })->listenAndWait();
Both queues get the message
Let’s add another layer
Yo dawg, let’s have a consumer publish
Send an email After the text message
Create a new Exchange
Update text message consumer
$publisher = new Publisher('sms'); (new Consumer()) ->bind($queueName, 'messages', 'message.new') ->listenTo($queueName, function($msg) use($key, $secret, $phoneNumber, $publisher) { // existing code $successful = $messageResult->status === '0'; if ($successful) { $publisher->publish(‘sms.sent', ['message' => $msg->comment]); } return $successful; })->listenAndWait();
Let’s write the email consumerAnd I’ll also show you how to easily test them
Email Consumer
(new Consumer()) ->bind('send_email', 'emails', '*.send') ->bind('send_email', 'sms', '*.sent') ->listenTo('send_email', function($message){ $msg = json_decode($message); mail('[email protected]', 'MidwestPHP RabbitMQ Talk', $msg->message); echo 'Message sent: ' . $msg->message . PHP_EOL; return true; })->listenAndWait();
Email consumer
(new Consumer()) ->bind('send_email', 'emails', '*.send') ->bind('send_email', 'sms', '*.sent') ->listenTo('send_email', function($message){ $msg = json_decode($message); mail('[email protected]', 'MidwestPHP RabbitMQ Talk', $msg->message); echo 'Message sent: ' . $msg->message . PHP_EOL; return true; })->listenAndWait();
Now, let’s see it from the beginning
rabbitmq.org
Thank you. Review: joind.in/10558#midwestphp #rabbitmq
Questions? Feel free to stop and ask me, email, tweet, @[email protected]