Currently RabbitMQ official Demo messages to outgoing clients are based on short connections, for example:

ConnectionFactory cf = new ConnectionFactory();
cf.Uri = serverAddress;
using (IConnection conn = cf.CreateConnection()){
    using (IModel ch = conn.CreateModel()) {
        if(exchange ! =""){
            ch.ExchangeDeclare(exchange, exchangeType);
        }
        ch.BasicPublish(exchange,routingKey,null,Encoding.UTF8.GetBytes(message)); }}Copy the code

We also adopted this method at the beginning, but when we did the pressure test, we found that it was very time-consuming to create Connection and Channel each time. Under the condition of large concurrency, it usually took about 8 milliseconds, and if it was slow, it was usually dozens of milliseconds. Therefore, I specially checked the data and came to the following conclusions:

1. Rabbit Client provides the following connection modes:

RabbitMQ provides:

A Connection object is a TCP Connection object.

Channels object, a virtual connection. The virtual Connection is established in the TCP Connection of the Connection object above. All data flows are in channels. The virtual connections of each Connection object are also limited. If the Channel object of a single Connnection is out of the specified range, there will also be performance problems. In addition, multiple virtual connections on a TCP Connection, in fact, when transmitting data, the virtual Connection transmitting data still monopolizes the TCP Connection. Other virtual connections are waiting in a queue.

2. RabbitMQ recommended client connection mode:

Create multiple channels on a Connection object, and then share the created channels when the program sends data. However, when using a specific single Channel, it is necessary to ensure the exclusive use of a single Chanel thread, and do not let multiple threads use a Channel at the same time. This can lead to concurrency errors. When a single Channel is used, add a lock to solve the problem. The official documentation and code examples are as follows:

Sharing Channels Between Threads
As a rule of thumb, IModel instances should not be used by more than one thread simultaneously: application code should maintain a clear notion of thread ownership for IModel instances. If more than one thread needs to access a particular IModel instances, the application should enforce mutual exclusion itself. One way of achieving this is forall users of an IModel to lock the instance itself: IModel ch = RetrieveSomeSharedIModelInstance(); The lock (ch) {ch. BasicPublish (...). ; }Copy the code

3. Problems existing in the RabbitMQ Client

3.1 Time consuming of Connection object creation and destruction and management problems:

The Connection object creates a TCP Connection. The creation and destruction of A TCP Connection itself is time-consuming. The solution is to create a Connection object and keep it open. MQ is also recommended to create multiple chnanels on a Connection object for fast data transfer. The specific implementation method is to create a static Connection object, but the problem with this method is that if I need to connect to multiple MQ servers, if MY side is divided according to the business, Different business data need to be transmitted to different MQ servers, such as order data and chat data, respectively sent to different MQ servers. If there is only a static Connection object, how can it be connected to two servers at the same time? Another problem, if the client sends a large amount of data now, A Connection is really unable to transmit, and although Connection can be shared with everyone, but specific transmission, or a specific transmission Channel will monopolize the TCP Connection in the whole Connection, so that a large amount of transmission, Connection is busy. It still leads to congestion.

Solution: Create a Connection pool. If different servers have different Connection objects, if one Connection object cannot be transmitted, there will be multiple Connection objects transmitting data at the same time. After multiple connections are created in a connection pool, if a connection has been idle for more than the specified time, the pool dispose and remove the connection from the pool to ensure that invalid connections are not occupied for a long time.

3.2 Time-consuming problems of creating and destroying Channel objects and management problems

The Channel object is the soft Connection on the TCP Connection of the Connection object. It is the Channel object that our program specifically uses for data transmission. I have recorded the creation and destruction time of the Channel object, which is also very long. I examined the source code of MQ Clinet and found that when a Channel was created and destroyed, the MQ Client sent data to the MQ server twice to inform the MQ server of the data protocol, message format, and other communication related information that the current Channel would use for subsequent data transmission. When destroyed, data is re-sent to the MQ server, informing it to disconnect the Channel, and freeing resources on the MQ server for the Channel. Under normal circumstances, two TCP data transfers can take about 1 millisecond, which is acceptable, but under high concurrency, two TCP data transfers can be time-consuming, and if the MQ server is under pressure and does not respond to the client request, the client will wait and the overall time will be longer. MQ also officially recommends sharing channels, rather than creating and destroying them every time.

Now the problem comes, how can I realize the shared Channel? I checked the source code of MQ Client, the Connection object does have a collection to store all channels, but there is no way for me to use and manage the Chanel in it.

The specific reason is unknown, interested friends can check the source of MQ Client. In addition, if I create multiple channels, how to destroy the Channel that is no longer in use after the idle time exceeds the specified time? In addition, I have checked the source code, if we do not set the length of the Connection object Channel pool, The number of channels in a Connection object can be increased indefinitely, because a Channel actually monopolizes the TCP Connection during transmission. If channels are increased indefinitely, this will cause congestion in the TCP. If I set the Channel pool length, If the number of channels I create exceeds the length of the Channel pool, the MQ Client will throw an exception indicating that the length of the Channel pool is out of bounds. In this case, when I create a Channel, I need to determine the length of the Channel pool to prevent the Channel pool from exceeding the length. Based on these issues, I developed a Channel pool outside to create and manage Channel objects for individual Connection objects. If the number of Chnanel pools reaches the specified number, a new Connection object is created and a Channel is created in the new Connection object. If the idle time of a Channel reaches the specified time, the Channel object is destroyed in the background. If all the channels of a Connection object are destroyed and the idle time of the Connection object is reached, the Connection object is also destroyed.

The performance test results are as follows: I used 200 threads to send 200,000 and 600,000 messages and conducted pressure tests, as well as the heartbeat function and the active Dispose function that idle for more than the specified time. Number of Connection pools: 4, number of Channel pools: 15 Total time: 218.731 seconds, average number of send messages per second: 913