RabbitMQ channel creation guidelines
Asked Answered
P

6

17

I'm writing a simple class that my apps will use to send and receive messages using RabbitMQ. I've read as many how-tos, blog posts, white papers and the likes about RabbitMQ as I could find. Most of the examples have the connection and channel wrapped in a using block, and contradict it by saying that you should probably implement them as a singleton. Specifically, regarding the channel, I've seen comments saying that you shouldn't have more than a single thread using a single channel at the same time.

I'm writing my library in C#. It's a singleton having a static connection connected on first instantiation.

I thought about doing the same for the channel, but I intend to use the same library to allow publishing/subscribing to multiple exchanges/queues. Both publishing and subscribing might be done from multiple threads.

And finally my question: How should I implement channel creation? Per message? Have each consumer have a unique private channel, publisher sync access to a single unique channel? You catch my drift. Please keep in mind that I'm intending to use a single server, with several dozens of consumers/publishers, not much more.

Pneumograph answered 15/4, 2011 at 18:54 Comment(0)
H
9

Edit (2016-1-26): Channels are NOT thread safe. The documentation on that has changed between April and May 2015. The new text:

Channel instances must not be shared between threads. Applications should prefer using a Channel per thread instead of sharing the same Channel across multiple threads. While some operations on channels are safe to invoke concurrently, some are not and will result in incorrect frame interleaving on the wire. Sharing channels between threads will also interfere with * Publisher Confirms.

From your question it sounds like you don't have a predefined, fixed number of threads that do mostly publishing / subscribing to RabbitMQ (in which case you might consider creating a channel as part of the initialization of the thread, or using a ThreadLocal<IModel>).

If concurrent RabbitMQ operations are rare or message sizes always small, you might get away with simply putting a lock(channel) around all your RabbitMQ pub/sub operations. If you need multiple requests to be transmitted in an interleaved fashion - that's what channels are for in the first place - using arbitrary threads, you might want to create a channel pool, e.g. a ConcurrentQueue<IModel> where you Enqueue unused channels and Dequeue for the time you need them. Channel creation is very low-overhead, and I have the feeling, from performance tests, that the process of channel creation does not involve any network io, i.e. it seems a channel gets automatically created in the RabbitMQ server on first use by a client. Edit: Thanks Pang, There is no need to open a channel per operation and doing so would be very inefficient, since opening a channel is a network roundtrip.


OLD (pre 2016-1-26): The now mostly obsolete details of the Java and .net implementations:

Re: channels and multiple threads, which is a bit confusing due to its dependence on the implementation.

Java implementation: Channels are thread safe:

Channel instances are safe for use by multiple threads.

But:

confirms are not handled properly when a Channel is shared between multiple threads

.net implementation: Channels are not thread safe:

If more than one thread needs to access a particular IModel instances, the application should enforce mutual exclusion itself.

Symptoms of incorrect serialisation of IModel operations include, but are not limited to,

• invalid frame sequences being sent on the wire

• NotSupportedExceptions being thrown ...

So in addition to Robin's useful answer, which applies regardless of whether it's thread safe or not, in the .net implementation, you can't just share a channel.

Hujsak answered 24/7, 2013 at 9:19 Comment(3)
You can share a Connection. You "can't" share an IModel (Channel) on top of a Connection.Owen
@Owen Thanks. I fixed the wording.Hujsak
Can anyone tell if this is still applicable?Goddord
M
9

With ASP.NET Core, there is ObjectPool that you can leverage on. Create an IPooledObjectPolicy

    using Microsoft.Extensions.ObjectPool;  
    using Microsoft.Extensions.Options;  
    using RabbitMQ.Client;  

    public class RabbitModelPooledObjectPolicy : IPooledObjectPolicy<IModel>  
    {  
        private readonly RabbitOptions _options;  

        private readonly IConnection _connection;  

        public RabbitModelPooledObjectPolicy(IOptions<RabbitOptions> optionsAccs)  
        {  
            _options = optionsAccs.Value;  
            _connection = GetConnection();  
        }  

        private IConnection GetConnection()  
        {  
            var factory = new ConnectionFactory()  
            {  
                HostName = _options.HostName,  
                UserName = _options.UserName,  
                Password = _options.Password,  
                Port = _options.Port,  
                VirtualHost = _options.VHost,  
            };  

            return factory.CreateConnection();  
        }  

        public IModel Create()  
        {  
            return _connection.CreateModel();  
        }  

        public bool Return(IModel obj)  
        {  
            if (obj.IsOpen)  
            {  
                return true;  
            }  
            else  
            {  
                obj?.Dispose();  
                return false;  
            }  
        }  
    }  

Then configure dependency injection for ObjectPool

services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
services.AddSingleton(s =>
{
   var provider = s.GetRequiredService<ObjectPoolProvider>();
   return provider.Create(new RabbitModelPooledObjectPolicy());
});

You can then inject ObjectPool<IModel>, and use it

var channel = pool.Get();
try
{
    channel.BasicPublish(...);
}
finally
{
    pool.Return(channel);
}

Sources:

https://www.c-sharpcorner.com/article/publishing-rabbitmq-message-in-asp-net-core/

https://developpaper.com/detailed-explanation-of-object-pools-various-usages-in-net-core/

Monadism answered 30/9, 2019 at 17:3 Comment(0)
P
4

It clarifies aqmp internals. Currently, my understanding is:

A. I can hold a single, shared, tcp connection to the server from each application (as a static shared resource)

B. I should either create a channel for each "task" (one for listening to queue X and one for publishing to exchange Y, etc. assuming these "tasks" can be executed in parallel)

C. Or I can use one channel for everything within a single app, while making sure access to it is synchronized - using some locking mechanism, assuming that the actual time spans the channel is used (locked) are relatively very short.

Pneumograph answered 15/4, 2011 at 22:46 Comment(0)
B
2

I can't comment on the specifics of a C# implementation, but it may help to know that Amqp channels are designed to share a single TCP connection, i.e. to enable multiplexing. A single channel can only send one or receive one message at once, but a connection can receive messages on different channels simultaneously. Image you've got 2 large 1GB files that you send over Amqp to a single consumer, it's possible that the messages will be split up in to 10K chunks and sent in an interleaved fashion. You can manipulate the default Amqp message size when you're setting up the connection, this has a bearing on whether and when you're likely to experience interleaving; AFAIK this feature is intended to help prevent starvation when multiple consumers share a connection and one consumer receives large messages.

HTH.

Barnaul answered 15/4, 2011 at 19:33 Comment(0)
U
2

You are correct that the Channel is NOT threadsafe and should not be accessed by more than one thread.

If you're planning on using different queues, you can have multiple queues with a single channel. But if you're planning on multiple exchanges (not sure why you would want more than one here), you would be forced to either keep track of multiple exchanges and channels in your singleton, or offload that responsibility to the callers.

I would build your singleton such as it has an observer pattern and hold all the RabbitMQ stuff in there with references to your callers. You would also then need to marshall calls to a single thread that deals with this or you could risk problems with the channel objects.

Urinalysis answered 14/5, 2011 at 18:20 Comment(0)
J
0

Concurrency Considerations for Consumers There is a number of concurrency-related topics for a library user to consider. Sharing Channels Between Threads IModel instance usage by more than one thread simultaneously should be avoided. Application code should maintain a clear notion of thread ownership for IModel instances. This is a hard requirement for publishers: sharing a channel (an IModel instance) for concurrent publishing will lead to incorrect frame interleaving at the protocol level. Channel instances must not be shared by threads that publish on them.

RabbitMQ Concurrency Considerations for Consumers

Janerich answered 2/5, 2023 at 19:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.