382 lines
12 KiB
Plaintext
382 lines
12 KiB
Plaintext
|
|
Learning RabbitMQ, part 1 ("Hello world!")
|
|
==========================================
|
|
|
|
|
|
{% dot -Gsize="10,1.3" -Grankdir=LR %}
|
|
digraph G {
|
|
P1 [label="P", {{ dotstyle.producer }}];
|
|
Q1 [label="{<s>||||<e>}", {{ dotstyle.queue }}];
|
|
C1 [label="C", {{ dotstyle.consumer }}];
|
|
|
|
P1 -> Q1 -> C1;
|
|
}
|
|
{% enddot %}
|
|
|
|
|
|
|
|
Throughout this tutorial, we'll teach you the basic concepts required for
|
|
creating RabbitMQ applications. The tutorial will be illustrated with
|
|
code snippets written in [Python](http://www.python.org/) and executed on Linux.
|
|
But don't worry if you don't know this language - the core ideas are the same
|
|
for other languages.
|
|
|
|
|
|
This tutorial consists of three parts:
|
|
|
|
* First we're going to write the simplest possible "Hello World" example.
|
|
* Next we'll try to use Rabbit as [a simple "Work queue" server]({{ python_two_url }}).
|
|
* Finally, we'll discuss how to [broadcast a message]({{ python_three_url }}).
|
|
|
|
You need to have RabbitMQ server installed to go through this tutorial.
|
|
If you haven't installed it yet you can follow the
|
|
[installation instructions](http://www.rabbitmq.com/install.html).
|
|
You can tell RabbitMQ is installed by running:
|
|
|
|
$ sudo rabbitmqctl status
|
|
|
|
If you have installed RabbitMQ you should see something like:
|
|
|
|
Status of node rabbit@example ...
|
|
[{running_applications,[{os_mon,"CPO CXC 138 46","2.2.5"},
|
|
{sasl,"SASL CXC 138 11","2.1.9"},
|
|
{mnesia,"MNESIA CXC 138 12","4.4.13"},
|
|
{stdlib,"ERTS CXC 138 10","1.16.5"},
|
|
{kernel,"ERTS CXC 138 10","2.13.5"}]},
|
|
{nodes,[{disc,[rabbit@example]}]},
|
|
{running_nodes,[rabbit@example]}]
|
|
...done.
|
|
|
|
|
|
> #### Where to get help
|
|
>
|
|
> If you're having trouble going through this tutorial you can post a message to
|
|
> [rabbitmq-discuss mailing list](https://lists.rabbitmq.com/cgi-bin/mailman/listinfo/rabbitmq-discuss)
|
|
> or join the [#rabbitmq](irc://irc.freenode.net/rabbitmq) irc channel.
|
|
|
|
|
|
Introduction
|
|
------------
|
|
|
|
RabbitMQ is a message broker. The principle idea is pretty simple: it accepts
|
|
and forwards messages. You can think about it as a post office: when you send
|
|
mail to the post box you're pretty sure that Mr. Postman will eventually
|
|
deliver the mail to your recipient. Using this metaphor RabbitMQ is a post box,
|
|
a post office and a postman.
|
|
|
|
The major difference between RabbitMQ and the post office is the fact that it
|
|
doesn't deal with the paper, instead it accepts, stores and forwards binary
|
|
blobs of data - _messages_.
|
|
|
|
RabbitMQ uses a specific jargon. For example:
|
|
|
|
* _Producing_ means nothing more than sending. A program that sends messages
|
|
is a _producer_. We'll draw it like that, with "P":
|
|
|
|
{% dot -Gsize="10,0.3" -Grankdir=LR%}
|
|
digraph G {
|
|
P1 [label="P", {{ dotstyle.producer }}];
|
|
}
|
|
{% enddot %}
|
|
|
|
* _A queue_ is the name for a mailbox. It lives inside
|
|
RabbitMQ. Although messages flow through RabbitMQ and your
|
|
applications, they can be stored only inside a _queue_. A _queue_
|
|
is not bound by any limits, it can store how many messages you
|
|
like - it's essentially an infinite buffer. Many _producers_ can send
|
|
messages that go to the one queue, many _consumers_ can try to
|
|
receive data from one _queue_. A queue will be drawn as like that, with
|
|
its name above it:
|
|
|
|
{% dot -Gsize="10,0.9" -Grankdir=LR%}
|
|
digraph G {
|
|
subgraph cluster_Q1 {
|
|
label="queue_name";
|
|
color=transparent;
|
|
Q1 [label="{<s>||||<e>}", {{ dotstyle.queue }}];
|
|
};
|
|
}
|
|
{% enddot %}
|
|
|
|
* _Consuming_ has a similar meaning to receiving. A _consumer_ is a program
|
|
that mostly waits to receive messages. On our drawings it's shown with "C":
|
|
|
|
{% dot -Gsize="10,0.3" -Grankdir=LR %}
|
|
digraph G {
|
|
C1 [label="C", {{ dotstyle.consumer }}];
|
|
}
|
|
{% enddot %}
|
|
|
|
|
|
Hello World!
|
|
------------
|
|
|
|
Our "Hello world" won't be too complex - let's send a message, receive
|
|
it and print it on the screen. To do so we need two programs: one that
|
|
sends a message and one that receives and prints it.
|
|
|
|
|
|
Our overall design will look like:
|
|
{% dot -Gsize="10,1.3" -Grankdir=LR %}
|
|
digraph G {
|
|
P1 [label="P", {{ dotstyle.producer }}];
|
|
subgraph cluster_Q1 {
|
|
label="test";
|
|
color=transparent;
|
|
Q1 [label="{<s>||||<e>}", {{ dotstyle.queue }}];
|
|
};
|
|
C1 [label="C", {{ dotstyle.consumer }}];
|
|
|
|
P1 -> Q1 -> C1;
|
|
}
|
|
{% enddot %}
|
|
|
|
Producer sends messages to the "test" queue. The consumer receives
|
|
messages from that queue.
|
|
|
|
> #### RabbitMQ libraries
|
|
>
|
|
> RabbitMQ speaks a protocol called AMQP. To use Rabbit you'll need a library
|
|
> that understands the same protocol as Rabbit. There is a choice of libraries
|
|
> for almost every programming language. For python it's not different and there
|
|
> is a bunch of libraries to choose from:
|
|
>
|
|
> * [py-amqplib](http://barryp.org/software/py-amqplib/)
|
|
> * [txAMQP](https://launchpad.net/txamqp)
|
|
> * [pika](http://github.com/tonyg/pika)
|
|
>
|
|
> In this tutorial we're going to use `pika`. To install it you can use
|
|
> [`pip`](http://pip.openplans.org/) package management tool:
|
|
>
|
|
> $ sudo pip install -e git+http://github.com/tonyg/pika.git#egg=pika
|
|
>
|
|
> If you don't have `pip`, you may need to install it.
|
|
>
|
|
> * On Ubuntu:
|
|
>
|
|
> $ sudo apt-get install python-pip
|
|
>
|
|
> * On Debian:
|
|
>
|
|
> $ sudo apt-get install python-setuptools
|
|
> $ sudo easy_install pip
|
|
|
|
### Sending
|
|
|
|
{% dot -Gsize="10,1.3" -Grankdir=LR %}
|
|
digraph G {
|
|
P1 [label="P", {{ dotstyle.producer }}];
|
|
subgraph cluster_Q1 {
|
|
label="test";
|
|
color=transparent;
|
|
Q1 [label="{<s>||||<e>}", {{ dotstyle.queue }}];
|
|
};
|
|
|
|
P1 -> Q1;
|
|
}
|
|
{% enddot %}
|
|
|
|
|
|
Our first program `send.py` will send a single message to the queue.
|
|
The first thing we need to do is to establish a connection with
|
|
RabbitMQ server.
|
|
|
|
{% highlight python %}
|
|
#!/usr/bin/env python
|
|
import pika
|
|
|
|
connection = pika.AsyncoreConnection(pika.ConnectionParameters(
|
|
'127.0.0.1',
|
|
credentials = pika.PlainCredentials('guest', 'guest'))
|
|
channel = connection.channel()
|
|
{% endhighlight %}
|
|
|
|
We're connected now. Next, before sending we need to make sure the
|
|
recipient queue exists. If we send a message to non-existing location,
|
|
RabbitMQ will just trash the message. Let's create a queue to which
|
|
the message will be delivered, let's name it _test_:
|
|
|
|
{% highlight python %}
|
|
channel.queue_declare(queue='test')
|
|
{% endhighlight %}
|
|
|
|
|
|
At that point we're ready to send a message. Our first message will
|
|
just contain a string _Hello World!_ and we want to send it to our
|
|
_test_ queue.
|
|
|
|
In RabbitMQ a message can never be send directly to the queue, it always
|
|
needs to go through an _exchange_. But let's not get dragged by the
|
|
details - you can read more about _exchanges_ in [the third part of this
|
|
tutorial]({{ python_three_url }}). All we need to know now is how to use a default exchange
|
|
identified by an empty string. This exchange is special - it
|
|
allows us to specify exactly to which queue the message should go.
|
|
The queue name needs to be specified in the `routing_key` parameter:
|
|
|
|
{% highlight python %}
|
|
channel.basic_publish(exchange='',
|
|
routing_key='test',
|
|
body='Hello World!')
|
|
print " [x] Sent 'Hello World!'"
|
|
{% endhighlight %}
|
|
|
|
|
|
### Receiving
|
|
|
|
|
|
{% dot -Gsize="10,1.3" -Grankdir=LR %}
|
|
digraph G {
|
|
rankdir=LR;
|
|
|
|
subgraph cluster_Q1 {
|
|
label="test";
|
|
color=transparent;
|
|
Q1 [label="{<s>||||<e>}", {{ dotstyle.queue }}];
|
|
};
|
|
C1 [label="C", {{ dotstyle.consumer }}];
|
|
|
|
Q1 -> C1;
|
|
}
|
|
{% enddot %}
|
|
|
|
|
|
Our second program `receive.py` will receive messages from the queue and print
|
|
them on the screen.
|
|
|
|
Again, first we need to connect to RabbitMQ server. The code
|
|
responsible for connecting to Rabbit is the same as previously.
|
|
|
|
The next step, just like before, is to make sure that the
|
|
queue exists. Creating a queue using `queue_declare` is idempotent - we can
|
|
run the command as many times you like, and only one will be created.
|
|
|
|
{% highlight python %}
|
|
channel.queue_declare(queue='test')
|
|
{% endhighlight %}
|
|
|
|
You may ask why to declare the queue again - we have already declared it
|
|
in our previous code. We could have avoided that if we were sure
|
|
that the queue already exists. For example if `send.py` program was
|
|
run before. But we're not yet sure which
|
|
program to run first. In such cases it's a good practice to repeat
|
|
declaring the queue in both programs.
|
|
|
|
> #### Listing queues
|
|
>
|
|
> You may want to see what queues does RabbitMQ store and how many
|
|
> messages are in them. You can do it using the `rabbitmqctl` tool:
|
|
>
|
|
> $ sudo rabbitmqctl list_queues
|
|
> Listing queues ...
|
|
> test 0
|
|
> ...done.
|
|
|
|
|
|
|
|
Receiving messages from the queue is more complex. It works by subscribing
|
|
a `callback` function to a queue. Whenever we receive
|
|
a message, this `callback` function is called by the Pika library.
|
|
In our case this function will print on the screen the contents of
|
|
the message.
|
|
|
|
{% highlight python %}
|
|
def callback(ch, method, header, body):
|
|
print " [x] Received %.20r" % (body,)
|
|
{% endhighlight %}
|
|
|
|
|
|
Next, we need to tell RabbitMQ that this particular callback function should
|
|
receive messages from our _test_ queue:
|
|
|
|
{% highlight python %}
|
|
channel.basic_consume(callback,
|
|
queue='test',
|
|
no_ack=True)
|
|
{% endhighlight %}
|
|
|
|
For that command to succeed we must be sure that a queue which we want
|
|
to subscribe to exists. Fortunately we're confident about that - we've
|
|
created a queue above - using `queue_declare`.
|
|
|
|
The `no_ack` parameter will be described [later on]({{ python_two_url }}).
|
|
|
|
And finally, we enter a never-ending loop that waits for data and runs callbacks
|
|
whenever necessary.
|
|
|
|
{% highlight python %}
|
|
print ' [*] Waiting for messages. To exit press CTRL+C'
|
|
pika.asyncore_loop()
|
|
{% endhighlight %}
|
|
|
|
|
|
### Putting it all together
|
|
|
|
|
|
Full code for `send.py`:
|
|
{% highlight python linenos=true %}
|
|
#!/usr/bin/env python
|
|
import pika
|
|
|
|
connection = pika.AsyncoreConnection(pika.ConnectionParameters(
|
|
host='127.0.0.1',
|
|
credentials=pika.PlainCredentials('guest', 'guest')))
|
|
channel = connection.channel()
|
|
|
|
|
|
channel.queue_declare(queue='test')
|
|
|
|
channel.basic_publish(exchange='',
|
|
routing_key='test',
|
|
body='Hello World!')
|
|
print " [x] Sent 'Hello World!'"
|
|
{% endhighlight %}
|
|
[(send.py source)]({{ examples_url }}/python/send.py)
|
|
|
|
|
|
Full `receive.py` code:
|
|
{% highlight python linenos=true %}
|
|
#!/usr/bin/env python
|
|
import pika
|
|
|
|
connection = pika.AsyncoreConnection(pika.ConnectionParameters(
|
|
host='127.0.0.1',
|
|
credentials=pika.PlainCredentials('guest', 'guest')))
|
|
channel = connection.channel()
|
|
|
|
|
|
channel.queue_declare(queue='test')
|
|
|
|
print ' [*] Waiting for messages. To exit press CTRL+C'
|
|
|
|
def callback(ch, method, header, body):
|
|
print " [x] Received %.20r" % (body,)
|
|
|
|
channel.basic_consume(callback,
|
|
queue='test',
|
|
no_ack=True)
|
|
|
|
pika.asyncore_loop()
|
|
{% endhighlight %}
|
|
[(receive.py source)]({{ examples_url }}/python/receive.py)
|
|
|
|
|
|
Now we can try out our programs. First, let's send a message using our
|
|
`send.py` program:
|
|
|
|
$ python send.py
|
|
[x] Sent 'Hello World!'
|
|
|
|
Let's receive it:
|
|
|
|
$ python receive.py
|
|
[*] Waiting for messages. To exit press CTRL+C
|
|
[x] Received 'Hello World!'
|
|
|
|
Hurray! We were able to send our first message through RabbitMQ. As you might
|
|
have noticed, the `receive.py` program doesn't exit. It will stay ready to
|
|
receive further messages. Try to run `send.py` again in a new terminal!
|
|
|
|
We've learned how to send and receive a message from a named
|
|
queue. It's time to move on to [part 2]({{ python_two_url }})
|
|
and build a simple _task queue_.
|