Experimenting with RabbitMQ – HelloWorldExample


Preamble

This is part 1 of a series of blogposts about RabbitMQ. This series aims to provide more information (I cannot vouch for the accuracy of the information as I’m a beginner at RabbitMQ) concerning a series of posts by Derek Greer.

Part 1: Experimenting with RabbitMQ – HelloWorldExample

Part 2: Experimenting with RabbitMQ – LoggingApplication example

Part 3: Experimenting with RabbitMQ – Fanout exchange

Part 4: Experimenting with RabbitMQ – Topic exchange

————————

 

I am currently following the excellent series on RabbitMQ by Derek Greer. The latter give a very good introduction of the basic concepts of RabbitMQ. However, some of the implementation details have been glossed over and I aim to document some of these minor points as I understand them.

From his explanation of the “HelloWorldExample” he has got the following code:

Producer

class Program
{
  static void Main(string[] args)
  {
    var connectionFactory = new ConnectionFactory();
    IConnection connection = connectionFactory.CreateConnection();
    IModel channel = connection.CreateModel();
    channel.QueueDeclare("hello-world-queue", false, false, false, null);
    byte[] message = Encoding.UTF8.GetBytes("Hello, World!");
    channel.BasicPublish(string.Empty, "hello-world-queue", null, message);
    Console.WriteLine("Press any key to exit");
    Console.ReadKey();
    channel.Close();
    connection.Close();
  }
}

Consumer

class Program
{
  static void Main(string[] args)
  {
    var connectionFactory = new ConnectionFactory();
    IConnection connection = connectionFactory.CreateConnection();
    IModel channel = connection.CreateModel();
    channel.QueueDeclare("hello-world-queue", false, false, false, null);
    BasicGetResult result = channel.BasicGet("hello-world-queue", true);
    if (result != null)
    {
      string message = Encoding.UTF8.GetString(result.Body);
      Console.WriteLine(message);
    }
    Console.WriteLine("Press any key to exit");
    Console.ReadKey();
    channel.Close();
    connection.Close();
  }
}

This code works and runs fine but what caught my attention was the fact that both the Producer and Consumer create the queue:

channel.QueueDeclare("hello-world-queue", false, false, false, null);

Hence, the first conclusion that can be drawn is the fact that redeclaring the same queue does not create a new queue on the server.

Now the question is what would happen were I to run both applications but with different parameters in the queue declaration e.g. declaring this in the Consumer

channel.QueueDeclare("hello-world-queue", true, false, false, null);

and then running the Producer application first.

Then when running the Consumer, we encounter a RabbitMQ.Client.Exceptions.OperationInterruptedException with this message:

{"The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=406, text=\"PRECONDITION_FAILED - parameters for queue 'connect-to-ubuntu-queue' in vhost '/' not equivalent\", classId=50, methodId=10, cause="}

at

channel.QueueDeclare("hello-world-queue", true, false, false, null);

Hence the second conclusion is that once a queue has been declared, it is not possible to change its parameters programatically.

The next question, which really should have been the first one, is why do we then need to declare the queue in two places? So what I did was to delete the existing queue. Then, I commented out the queue declaration code in the Consumer application. Afterwards, I ran the Producer application as a result of which the queue was created and a message placed on the latter. Finally, I ran the Consumer application and that worked fine.

Hence the third conclusion is that we didn’t “really” need to declare the queue in both applications.

However, I believe that it was done so because the order of running the application is important. Had there been no existing queue on the RabbitMQ server and we tried to run the Consumer application without declaring the queue, we would have run into the RabbitMQ.Client.Exceptions.OperationInterruptedException with the reason:

{AMQP close-reason
, initiated by Peer
, code=404
, text="NOT_FOUND - no queue 'hello-world-queue' in vhost '/'"
, classId=60
, methodId=70
, cause=}

So my guess is that the author wanted to keep things simple and not have errors potentially coming up depending upon the order that the applications were run.

Having said that, this is probably where the Single Responsibility Principle comes into play i.e. the creation of the queue should not be in the same class as the production and consumption of the message. So, it would make sense to declare the queues in the application’s startup.

Queue parameters

channel.QueueDeclare("hello-world-queue", true, false, false, null);

What I then decided to do was to test the “durability” parameter of the queue using the Producer application. Once I set it to “true”, when I looked at the queue in the web interface, the “D” parameter was set:

Durability

Then I stopped and restarted RabbitMQ (Windows: rabbitmq-service.bat stop/start; Ubuntu: sudo invoke-rc.d rabbitmq-server stop/start/). I had erroneously put it in my head that what would happen was that the message would not have been droppped. However, as Derek Greer correctly points out, what this “durability” parameter ensures is that it is the queue that is durable i.e. it will still be there after a RabbitMQ restart.

As far as what the “exclusive” flag does, here is what the author has to say about it:

The exclusive flag pertains to whether a queue can be declared by more than one channel.  
Since messages are actually published to exchanges, not queues, there may be cases where 
you only want a single client instance to be capable of declaring a queue bound to a 
particular exchange (for example, when using queues to facilitate an RPC call).

Therefore, I decided to create the queue in the Producer as follows:

channel.QueueDeclare("hello-world-queue", true, true, false, null);

What happened then was something that I found to be quite surprising in how things were operating. So based upon the description given for the “exclusive” flag, what I did was what I had been doing in some of the previous cases. First, I’d run the “Producer” application and as per usual, the queue and the message would be created. However, as soon as I “pressed any key” to end the application, the queue together with the message disappeared despite the “durability” parameter being set.

Upon further reflection, it does make sense that this particular behaviour was taking place. According to the author as to the purpose of the “exclusive” flag, it is reasonable for the queue to be dropped after the application is run because otherwise we might be in a situation whereby we have many inaccessible queues left in the system following the termination of the application that had initially set up that “exclusive” queue.

I also experimented with the “exclusive” flag by running the “Producer” application and not terminating it. Then, I ran the “Consumer” application and regardless of what parameters I created the “Consumer” queue with, I got a  RabbitMQ.Client.Exceptions.OperationInterruptedException with the message:

{AMQP close-reason
, initiated by Peer
, code=405
, text="RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'hello-world-queue' in vhost '/'"
, classId=50
, methodId=10
, cause=}

I then carried out the same experiment but commented out the queue declaration in the “Consumer” application and although I got the same exception, the reason differed slightly although I am not sure what the significance of it is.

{AMQP close-reason
, initiated by Peer
, code=405
, text="RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'hello-world-queue' in vhost '/'"
, classId=60
, methodId=70
, cause=}

Also one thing which is worthy of note is the fact that the web interface does not have an “E” or anything for that matter to represent the fact that this is an “exclusive” queue.

As for the “auto-delete” flag, here is what Derek Greer has to say about it:

Lastly, the auto-delete flag pertains to whether the queue is automatically deleted once all consumers are removed.  
This flag can be a little misleading since we might suppose setting this to true would cause our “hello-world-queue” 
to be automatically deleted after our application exists.  In fact, it wouldn’t be.
There are two different approaches to receiving messages: pulling and pushing.  With the pull API, messages are 
retrieved on demand through a call to BasicGet().  This is the method used in our example.  With the pull API, 
available messages are delivered to a local queue when a Consumer is bound to the channel.  When setting the auto-delete
flag to true, a queue will be deleted once it detects that all consumers have been removed.  While we named the project 
which receives messages from our queue “Consumer”, the pull API doesn’t actually declare a consumer to retrieve messages.  
Therefore, the server is never alerted to the fact that the queue is no longer being used.

As we are using the pull API, even after setting the “auto-delete” flag, the queue remains after both the “Producer” and “Consumer” applications have run.

Advertisements
Posted in .NET, ALT.NET, RabbitMQ

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: