RabbitMQ隨筆

石曼迪發表於2017-01-20

不管是官方還是能搜到的文章,使用MQ的基本思路都是這樣:

        static void Main(string[] args)
        {
           //通過工廠建立連線
            using (IConnection connection = factory.CreateConnection())
            {
                //通過連線建立會話,這裡還有可能是Channel
                using (ISession session = connection.CreateSession())
                {
                    while (true)或者一個for 迴圈傳送100萬個訊息
                    {
                          //建立一個msg
                      string  message = "Hello World";
                         //傳送
                       xxx.Send(message);
                    }
                }
            }
        }

那麼問題來了:

這個"Hello World"怎麼傳進去?如何對外提供服務?

然後會發現有些客戶端SDK是這麼處理的:

       public static void SendMsg()
        {
            MQAPI("Hello World");
        }

        private static void MQAPI(string message)
        {
            IConnectionFactory factory;
            //通過工廠建立連線
            using (IConnection connection = factory.CreateConnection())
            {
                //通過連線建立Session會話
                using (ISession session = connection.CreateSession())
                {
                       //建立一個msg
                      string  message = message;

                      //傳送
                      xxx.Send(message);
                }
            }
        }

去公開一個介面呼叫SendMsg吧。

看起來似乎解決了這個問題,但是實際測試下會嚇一跳:後者的QPS僅為前者的40%左右,這是不能容忍的。

那麼接下來大家肯定會從SQLConnection的經驗得出一個解決方案:

將connection抽出來,那麼session或者Channel呢?

我們通過研究RabbitMQ的連結客戶端和服務端連結過程(這個過程較為複雜,寫了幾遍都刪了)得出如下結論:

  1. 先建立Connection連結,這個連結就是一個TCP連結,Producer和Consumer都是通過TCP連線到RabbitMQ Server 的。經過connection.start -> connection.start_ok -> connection.secure -> connection.secure_ok -> connection.tune -> connection.tune_ok(這時rabbit會建立一個心跳程式)-> connection.open -> connection.open_ok後,客戶端與rabbit之間就認為已經建立 了連線。
  2. 再建立Channels: 虛擬連線。它建立在上述的TCP連線中。資料流動都是在Channel中進行的。也就是說,一般情況是程式起始建立TCP連線,第二步就是建立這個Channel。可以多路複用,1~65535為可用的channel編號,channel的索引不為0時(0是全域性連結),rabbit認為這些資料從屬於某個 channel。如果該channel程式不 存在,則會建立一個channel程式,並由此程式負責該channel上 的所有資料。根據AMQP協議,經過channel.open -> channel.open_ok後,客戶端就可以開始在該channel上傳送資料了。

那麼,建立和關閉TCP連線是有代價的,頻繁的建立關閉TCP連線對於系統的效能有很大的影響,而且TCP的連線數也有限制,這也限制了系統處理 高併發的能力。但是,在TCP連線中建立 Channel是沒有上述代價的。對於Producer或者Consumer來說,可以併發的使用多個 Channel進行 Publish或者Receive。

然而我研究了RabbitMQ.Client程式碼之後發現其並未維護一個連線池:

                if (AutomaticRecoveryEnabled)
                {
                    var autorecoveringConnection = new AutorecoveringConnection(this, clientProvidedName);
                    autorecoveringConnection.Init(endpointResolver);
                    conn = autorecoveringConnection;
                }
                else
                {
                    IProtocol protocol = Protocols.DefaultProtocol;
                    conn = protocol.CreateConnection(this, false, endpointResolver.SelectOne(this.CreateFrameHandler), clientProvidedName);
                }

只是有一個自動恢復功能,需要設定為true建立長TCP連結,然後根據在請求的時候再建立Channel。