不管是官方還是能搜到的文章,使用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的連結客戶端和服務端連結過程(這個過程較為複雜,寫了幾遍都刪了)得出如下結論:
- 先建立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之間就認為已經建立 了連線。
- 再建立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。