天天看點

RabbitMQ客戶連接配接池的實作

目前RabbitMQ官方給的出的用戶端發送消息的Demo的都是基于短連接配接來做的,例如:

我們剛開始也是采用這種方式來實作的,但做壓力測試時,發現這種每次建立Connection和建立Channel是非常耗時的,在大并發下,一般都要8毫秒左右,慢的話,好多都是幾十毫秒,為此,我專門查了資料,得出如下結論:

1、Rabbit Client提供的連接配接方式介紹:

RabbitMQ官方提供了:

Connection對象,就是一個TCP連接配接對象。

Channels對象,虛拟連接配接。虛拟連接配接建立在上面Connection對象的TCP連接配接中。資料流動都是在Channel中進行的。每個Connection對象的虛拟連接配接也是有限的,如果單個Connnection的Channel對象超出指定範圍了,也會有性能問題,另外一個TCP連接配接上的多個虛拟連接配接,實際在傳輸資料時,傳輸資料的虛拟連接配接還是獨占了TCP連接配接,其它虛拟連接配接在排隊等待。

2、RabbitMQ官方推薦用戶端連接配接使用方式:

在一個Connection對象上建立多個Channel,然後程式發送資料時,分别共享使用建立好的Channel,但使用具體的單個Channel時,需要保障單個Chanel的線程獨占性使用,不要讓多個線程同時在使用某一個Channel,這樣會導緻并發錯誤。使用單個Channel時,加一個鎖即可解決,具體官方文檔說明和代碼示例如下:

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 for all users of an IModel to lock the instance itself:

IModel ch = RetrieveSomeSharedIModelInstance();

lock (ch) {

ch.BasicPublish(…);

}

RabbitMQ客戶連接配接池的實作

3、RabbitMQ Client用戶端目前存在的問題。

3.1、Connection對象建立、銷毀的耗時問題,管理問題:

Connection對象建立的是TCP連接配接,TCP連接配接的建立和銷毀本身就是很耗時,解決的辦法就是建立一個Connection對象之後,一直不關閉,在傳輸資料時,共享這一個Connection對象就行了,MQ官方也是推薦一個Connection對象上建立多個Chnanel來實作快速資料傳輸,具體實作方式,建立一個靜态的Connection對象不就搞定了,但這種方式問題是,如果我需要連接配接多個MQ伺服器呢,如果我這邊根據業務進行了劃分,不同的業務資料,需要分别傳輸到不同的MQ伺服器上,如訂單資料和聊天資料,是分别發到不同的MQ伺服器上面,如果隻是有一個靜态的Connection對象,怎麼可能同時連接配接2台伺服器呢,另外的問題,如果現在用戶端發送資料量較大,一個Connection實在是傳輸不過來,而且Connection雖說是可以給大家共享傳輸,但具體傳輸時,還是某一個具體傳輸Channel會獨占整個Connection中的TCP連接配接,這樣傳輸量一大,Connection忙不過來,還是會導緻擁塞的發生。

解決辦法:建立Connection池,如果不同的伺服器,會分别有不同的Connection對象,如果一個Connection對象傳輸不過來,會有多個Connection對象同時在傳輸資料。連接配接池在建立多個連接配接之後,如果某個連接配接閑置時間超過指定的時間,則連接配接池會進行單個連接配接的dispose和remove動作,将連接配接先銷毀,再從連接配接池中移除,確定不會長期占用無效連接配接。

3.2、Channel對象的建立、銷毀的耗時問題,管理問題:

Channel對象,即Connection對象上的TCP連接配接上的軟連接配接,我們程式具體使用的就是Channel對象進行資料傳輸,我自己記錄了Channel對象建立和銷毀的耗時,也是非常長的,為什麼Channel對象的建立和銷毀會非常耗時,我仔細查了MQ Clinet的源碼,發現建立和銷毀Channel時,MQ Client分别向MQ伺服器發送了2次資料,用于分别通知MQ伺服器,目前這個Channel接下來資料傳輸時用的資料協定,封包格式,以及其它通信相關的資訊。銷毀時,又重新向MQ伺服器發送了資料,通知MQ伺服器,斷開這個Channel,并且釋放MQ伺服器上面關于這個Channel所占用的資源。在平常情況下2次TCP資料傳輸,一般要耗時1毫秒左右,還可以接受,但在高并發下,2次TCP資料傳輸,則會很耗時,而且如果MQ伺服器壓力比較大,遲遲不響應用戶端請求,則用戶端會等待以及整個耗時會更長。而且MQ官方也是推薦共享使用Channel,而不是每次都建立和銷毀Channel。

現在問題來了,我怎麼才能實作共享Channel,我查了MQ Client源碼,Connection對象中确實有集合在存放所有的Channel,但居然沒有提供方法讓我來使用和管理這裡面的Chanel,

具體的原因不詳,有興趣的朋友可以自己在查一下MQ Client的源碼。另外如果我建立多個Channel之後,如果不再使用的Channel在閑置時間超過指定的時間之後,如何銷毀呢,另外我查了源碼,如果我們不設定Connection對象Channel池的長度,則一個Connection對象的Channel數量可以無限增加,因為Channel在傳輸時,實際上是獨占TCP連接配接,如果Channel無限增加的話,會導緻這個TCP擁塞,如果我設定了Channel池的長度,則我建立Channel的數量超過Channel池的長度,則MQ Client直接抛出異常,提示Channel池長度越界(這是我從MQ Client源碼中查到的),這樣的話,我建立Channel時,需要判斷Channel池的長度,防止越界,基于這些問題,我再在外面開發了一個Channel池,用于建立和管理單個Connection對象的Channel對象。如果Chnanel的池數量達到指定數量時,則會建立Connection對象,在新的Connection對象中建立Channel,如果Channel的閑置時間到達指定時間,則會在背景銷毀這個Channel對象,如果一個Connection對象的的Channel全部被銷毀了并且Connection對象的閑置時間也到達了,則Connection對象也會被銷毀。

RabbitMQ客戶連接配接池的實作

性能測試結果如下:我這邊自己用200線程,發送了20萬個和60萬個消息,做了壓力測試,另外還有心跳功能和閑置超過指定時間,主動dispose的功能。

Connection池數量:4,Channel池數量:15 總耗時:218.731秒,平均每秒發送數量:913

RabbitMQ客戶連接配接池的實作

繼續閱讀