1. 程式人生 > >高效能可伸縮的分散式訊息中介軟體設計

高效能可伸縮的分散式訊息中介軟體設計

訊息中介軟體基本上是每一個大型網際網路公司的標準基礎技術元件配置,雖然有很多的開源訊息中介軟體,功能也很強大,但是今天我還是想介紹一下怎樣自主架構與設計並實現一套完整的分散式訊息中介軟體。

開源的訊息中介軟體或多或少存在一些所謂“坑”,沒有遇到大家用得都很happy,遇到的同學就只有加班查資料、google搜尋或者直接review開原始碼尋找問題原因了。還有就是基本上開源的訊息中介軟體一般都是大而全的功能,一般比較強調通用嘛。今天為大家介紹的是可以靈活橫向擴充套件並且具有高效能的分散式訊息中介軟體的架構設計,也會介紹一些實現的關鍵技術,也可能很多開源軟體實現也有同樣的功能,所以通過了解這些設計與實現的核心技術,也可以更好的理解和使用開源的訊息中介軟體。

一、設計目標

先簡單此設計需要達到的一些目標吧:

(1)高效能:應該能夠充分利用cpu、記憶體和網絡卡等資源,通常情況下是把網絡卡(1000M網絡卡)撐滿;

(2)訊息轉發實時性:延遲必須在一個設定目標內(可配置);

(3)訊息不丟失:正常情況下不丟失(如果由於某一個訊息接收到自己網路出現問題導致連線斷開丟失的不算,這個需要訊息中介軟體的客戶端解決的問題);

(4)可以橫向無縫擴充套件:當一個服務節點服務能力不足時,可以無縫的擴充套件服務節點,對其他服務節點和所有客戶端透明;

(5)安全性:客戶端需要通過使用者名稱和密碼才能和伺服器建立連線、傳送訊息和訂閱訊息等;

(6)可以無縫升級系統:程式由於bug或者新增功能等上線是在所難免的,但是不應該影響正常的訊息通訊;

(7)可以無縫縮減服務節點:如果訊息轉發量不是很大使用過多的節點是資源的浪費(伺服器、電費等);

(8)分散式,防單點;

(9)支援分組訂閱:這個分組訂閱的功能太好使用了,尤其在做任務的負載均衡排程的時候,後面我會用一篇單獨的文章介紹基於分散式訊息中介軟體的負載均衡排程系統設計;

(10)其他。。。。

二、基本架構圖

先上圖,在解釋,如下圖:


這個架構圖夠簡單了吧,主要由三大模組組成,訊息中介軟體的客戶端、zk叢集和訊息中介軟體的伺服器叢集。具體他們三者之間是怎麼互動和聯絡的,請看後面的的詳細設計與講解,當然重點還是伺服器節點之間的互動。圖中只畫了一個客戶端,其實是代表成千上萬的客戶端。

三、詳細設計與關鍵技術分析

在具體講解之前需要說明一些簡單有容易理解前提,訊息中介軟體需要傳遞訊息那麼肯定需要和訂閱了訊息的客戶端進行長連線的保持,至於只需要傳送訊息的客戶端那就無所謂了,但是如果需要頻繁傳送訊息建議還是長連線吧。不過實際的應用中,基本上一個客戶端既需要傳送也需要接受訊息(先訂閱),那麼建立一個長連線即可了。

(一)、訊息中介軟體的伺服器叢集設計

(1)存活檢查設計:首先伺服器節點要能夠保證對外提供服務的節點都是正常的(說簡單一點就是存活的),所以伺服器通過zk叢集來保證(不懂zookeeper,google吧,分散式系統一般離不開它的)。所有伺服器節點在啟動的時候都必須註冊在zookeeper叢集上註冊一個臨時節點,這個臨時節點就是來保證伺服器節點的存活的。如果某一個伺服器節點由於某種原因(程式bug、網路斷掉、伺服器宕機等)掛掉了,那麼zookeeper會自動把這個臨時節點刪掉,那麼其他伺服器或者客戶端就會看不到這個節點的地址了,保證了對外公佈的伺服器節點都是存活可用的。

(2)訊息訂閱及同步設計:任何需要收到指定主題訊息的客戶端都必須先和伺服器叢集建立長連線並且訂閱訊息,如果是單伺服器節點那麼很容易實現,只需要簡單在伺服器節點記憶體中儲存一個訂閱佇列即可。但是在分散式的訊息中介軟體中,由於伺服器節點是多臺,所以任何一個客戶端的訂閱資訊都需要在每一個伺服器節點上保持同步。所以增加了分散式的特性需要處理的內容和複雜度大大增加了,這也是分散式系統最大的難點(需要考慮每一個節點此時此刻的執行狀態,然後根據這些狀態做合適的處理)。訊息訂閱的同步絕對是需要強一致性的,所以如果一個客戶端的訂閱訊息在同步到任何一個伺服器節點失敗那麼此次訊息訂閱就是失敗的。雖然所有伺服器節點之間基本上都是通過內網進行通訊的,但是網路異常依然不可避免,所以必須需要考慮。還有如果某一個伺服器節點掛掉,那麼其他伺服器節點需要及時的知道,因為掛掉的節點不應該在訊息訂閱同步的範圍內。

上面介紹了正常情況下訊息訂閱同步的處理,但是伺服器節點由於某種原因的增加(例如擴容)或者掛掉的某臺伺服器節點重啟,這些新增或者新啟動的伺服器節點也需要把以後的訊息訂閱同步過來。所以需要在伺服器節點啟動的時候先把以後的訂閱資訊從其他伺服器節點成功同步過來以後才能對外提供服務。這裡的設計依然依賴zookeeper提供的存活保證來實現,伺服器節點啟動的時候首先檢查通過zookeeper叢集獲取已經註冊的伺服器節點,如果沒有那麼就不做任何處理,如果有那麼就依次獲取這些伺服器節點的資訊,然後通過伺服器之間相互通訊的埠進行通訊,新啟動的伺服器節點會主動去連線所有已經成功註冊並且對外提供服務的伺服器節點,其他被連線的伺服器節點在接受到一個伺服器節點連線的時候把自己本地訂閱通過建立的連線全部發送過去(也就是把自己當做客戶端去訂閱這些訊息,訊息在伺服器之間同步的手段就是採用伺服器之間進行二次訂閱)。這樣就保證了任何一個新啟動(不論是新增擴容的還是掛掉重啟的)伺服器節點都能夠有完整的訂閱資訊,那麼有訊息傳送到新節點以後就可以進行正常的訊息轉發了。

重點說明:程式實現的時候要防止進行伺服器直接的迴圈訂閱(會造成死迴圈),也就是對訂閱訊息需要進行區分,如果是伺服器之間的相互訂閱就不需要在相互同步了,只有客戶端的訂閱訊息才需要在所有伺服器節點之間進行訊息同步(同步的方法就是通過伺服器自身二次訂閱,獲取其他伺服器的地址資訊都是通過zookeeper叢集,因為通過zookeeper叢集獲取到的伺服器節點資訊都是正常工作的)。

(3)訊息釋出及轉發設計:訊息都已經成功的訂閱了,那麼就應該有訊息進行釋出了。訊息釋出功能其實比較簡單,就是根據訂閱列表裡面的主題進行訊息主題的匹配進行訊息進行轉發即可。這裡唯一需要控制的就是需要根據訊息釋出者來進行區別進行轉發,如果是客戶端釋出的訊息,那麼需要把這些訊息同樣釋出給其他訂閱此訊息主題的伺服器節點,如果是伺服器二次轉發的訊息就不需要進行轉發到其他伺服器節點了,不然也會形成訊息的死迴圈。分組訂閱的訊息傳送需要有一點點的不同,他會從所有訂閱次分組訂閱資訊的節點中隨機選擇一個進行轉發,而不是像普通訊息訂閱進行所有的訂閱轉發。

(4)網路異常處理設計:所有分散式系統都不可避免的需要考慮網路異常的情況,針對伺服器節點的網路異常處理設計需要考慮兩點,一點是和zookeeper保持的心跳長連線情況;二是伺服器節點之間相互建立的長連線(主要用於訊息訂閱同步和訊息轉發)。針對第一種情況,需要專門的一個執行緒來定期檢查和zookeeper的連線情況,如果發現連線已經斷開那麼重新進行註冊即可;第二種情況也需要定期檢查,不過要複雜一點,就是每一個節點都需要考慮此時與自己保持連線的伺服器節點是否和zookeeper上的一致,如果不一致需要處理成一致(以zookeeper上註冊資訊為準)。

重點說明:在處理網路異常的時候,如果兩個伺服器節點同時發現和對方的連線斷開了,那麼肯定會同時去連線對方,那麼這個時候就可能建立兩條連線,所有還需要有一種機制檢查重複的連線建立情況,檢查到以後關閉掉多餘的重複連線。

(5)檢查客戶端連線設計:伺服器節點的連線資源是非常寶貴的,因為需要支援更多的客戶端,就需要更多的連線(需要佔用socket套接字,對應linux的一個檔案描述符等)。但是客戶端很多情況下並不會主動的釋放這些連線和資源,那麼伺服器節點自身就應該有一套能夠檢測客戶端是否還存活或者還需要這個連線的情況,一旦檢測到某一個客戶端連線已經斷掉,那麼伺服器段就需要主動關閉連線,是否資源。

重點說明:這裡不僅僅需要考慮連線資源釋放,還需要考慮此客戶端的訂閱資訊,需要在關閉連線的時候同時取消這個客戶端以前的訂閱訊息,如果不這樣伺服器會存在越來越多的垃圾訂閱訊息,佔用記憶體不說,在查詢和匹配訊息的也會降低效能。這一點非常重要,因為分散式的訊息中介軟體是需要7*24小時執行的基礎服務系統。

(6)慢消費客戶端檢查與處理設計:為了保證伺服器節點轉發訊息的及時性,需要保證客戶端接收和處理訊息的速度,因為一個公共的訊息主題,可能被成千上萬個客戶端訂閱,如果由於某一個客戶端處理很慢,導致其他客戶端也不能及時接收訊息,伺服器節點不能及時轉發訊息,那麼就很得不償失了。所以需要有機制檢查出那些消費很慢的客戶端,然後直接清除掉,通常的手段就是進行超時設定,如果發現哪一個客戶端接收訊息超時,那麼就直接把這個客戶端的連線關閉掉。從而保證以後的訊息轉發都是很暢通無阻的。

(7)分組訂閱設計:分組訂閱主要用於多個客戶端處理同一個訊息但是每一次只需要一個客戶端處理的情況,那麼就可以對這個訊息設定一個分組,伺服器節點在轉發訊息的時候發現時分組訂閱那麼就只需要隨機選擇一個客戶端進行訊息轉發即可。分組訂閱是一個很有用的功能,基於這個功能來做任務排程和負載均衡有很大的好處和便捷性。

(二)、客戶端設計

客戶端設計主要需要考慮到對某一個伺服器節點的容錯設計,因為客戶端如果需要訂閱訊息那麼就必須選擇一個伺服器節點建立長連線,但是選擇的這一個伺服器節點隨時可能不可用,那麼這個時候客戶端需要有自動切換到其他正常伺服器節點的能力。這個切換過程中需要處理重新訂閱以前已經訂閱過的訊息,這個是需要客戶端自動完成而不是需要具體某一個業務系統來完成,因為這是一個通用容錯設計,讓使用訊息中介軟體的業務系統不必關心這些容錯設計。

首先客戶端選擇一個伺服器節點建立連線,因為訊息中介軟體的所有伺服器節點都註冊到zookeeper系統上了,所以客戶端可以通過訪問zookeeper獲取所有伺服器節點的地址資訊並且快取起來(快取起來的作用主要是做另外一個容錯設計,就是當訊息中介軟體客戶端需要切換訊息中介軟體伺服器節點的時候,而這個時候zookeeper叢集也恰好不可用,那麼這個時候就可以也需要使用快取的伺服器節點地址,當然需要排除目前正在使用的這個伺服器節點)。然後根據隨機演算法選擇一個伺服器節點建立長連線,以後客戶端就可以訂閱訊息和釋出訊息了。

重點說明:客戶端在切換伺服器節點的時候那麼以前所有的訂閱資訊都丟失了,需要重新把所有以前訂閱過的訊息重新在新切換的伺服器節點中進行訂閱,並且這一切對使用訊息中介軟體的業務系統是透明的。

(三)、其他核心技術解析

(1)安全性:未完待續。。。

(2)支援應答的訊息模型:未完待續。。。

(3)線上伸縮:未完待續。。。

四、待完善設計

(1)訊息丟失:未完待續。。。