如何設計乙個簡單的訊息中介軟體

2022-01-10 18:16:22 字數 3409 閱讀 3040

我們日常開發當中需要用到訊息中介軟體的場合很多,我們或許也用到了形形色色的訊息中介軟體產品,有老牌的activemq、rabbitmq,炙手可熱的kafaka,還有阿里研發的notify、metaq、rocketmq等等,但反過來思考一下,如果讓我們自己來設計乙個訊息中介軟體,需要考慮哪些方面的問題,需要有什麼樣的特性來滿足實際業務生產的需要呢?下面就這個問題展開討論。

解耦想象這樣一種場景,有個業務系統a,在處理某個核心業務邏輯的時候,跟另外的b、c、d三個業務系統有關聯,當前已有的處理流程是a系統在處理完自己本地的事務後,順次呼叫b、c、d三個系統的介面去同步資料狀態。但是,隨著業務的發展,突然有一天另外乙個業務系統d的資料也同樣需要根據a系統當中資料記錄的更新而同步更新,此時,開發不得不去修改原有**,加上呼叫d系統同步更新資料的**。然後某一天,業務需求又發生了變更,b系統不再維護對應的業務資料,開發又不得不修改原有**,將呼叫b系統介面的**給刪掉。如此周而復始,開發就會很苦惱。其實,站在a系統的角度來看,可能「關心」a系統變更的應用有很多個,但a系統只需要發布變更訊息即可,誰關心誰接入。

非同步另外一種情況就是,還是上面最初的場景,a系統處理業務邏輯時,需要呼叫b、c、d三個系統的介面,但是其中d介面是個耗時任務介面,需要很長的時間才會得到處理結果,那麼一旦這樣的請求多了以後,a系統和d系統都會被拖垮。乙個典型的例子是,電商當中的訂單系統,訂單最終支付成功之後可能需要給使用者傳送簡訊積分什麼的,但其實這已經不是我們系統的核心流程了。如果外部系統速度偏慢(比如簡訊閘道器速度不好),那麼主流程的時間會加長很多,使用者也肯定不希望點選支付過好幾分鐘才看到結果。那麼我們只需要通知簡訊系統「我們支付成功了」,不一定非要等待它處理完成。

削峰還是a系統,a系統裡面有個秒殺業務邏輯,每天上午11點有一波搶購活動,對於一天的其他時間來說,訪問a系統的請求流量平平淡淡,a系統完全可以應付的了,但就是11點時候,有一波「突襲」流量訪問進來,上游入口還好,做了集群部署等一系列應對高併發的措施,但mysql資料庫扛不住,每秒2k個請求已經是極限了。

像上面這種情況,上下游處理能力存在明顯差距,利用訊息佇列來做乙個通用的「漏斗」,當下游有能力處理的時候,再進行分發,就是一種很好的處理方式。

實際上,除了上面三個典型的應用場景以外,訊息佇列還有乙個應用場景,那就是---最終一致性。

最終一致性

以乙個銀行的轉賬過程來理解最終一致性,轉賬的需求很簡單,如果a系統扣錢成功,則b系統加錢一定成功。反之則一起回滾,像什麼都沒發生一樣。 然而,這個過程中存在很多可能的意外:

a扣錢成功,呼叫b加錢介面失敗。

a扣錢成功,呼叫b加錢介面雖然成功,但獲取最終結果時網路異常引起超時。

a扣錢成功,b加錢失敗,a想回滾扣的錢,但a機器down機。

用分布式事務去實現強一致性,但實際上這種實現成本很高。

利用訊息佇列的「記錄」和「補償」的方式去實現最終一致性。

這裡主要講第二種實現方式。回到剛才的例子,系統在a扣錢成功的情況下,把要給b「通知」這件事記錄在庫里(為了保證最高的可靠性可以把通知b系統加錢和扣錢成功這兩件事維護在乙個本地事務裡),通知成功則刪除這條記錄,通知失敗或不確定則依靠定時任務補償性地通知我們,直到我們把狀態更新成正確的為止。

整個這個模型依然可以基於rpc來做,但可以抽象成乙個統一的模型,基於訊息佇列來做乙個「企業匯流排」。 具體來說,本地事務維護業務變化和通知訊息,一起落地(失敗則一起回滾),然後rpc到達broker,在broker成功落地後,rpc返回成功,本地訊息可以刪除。否則本地訊息一直靠定時任務輪詢不斷重發,這樣就保證了訊息可靠落地broker。 broker往consumer傳送訊息的過程類似,一直傳送訊息,直到consumer傳送消費成功確認。 我們先不理會重複訊息的問題,通過兩次訊息落地加補償,下游是一定可以收到訊息的。然後依賴狀態機版本號等方式做判重,更新自己的業務,就實現了最終一致性。

最終一致性不是訊息佇列的必備特性,但某些時候確實可以依賴訊息佇列做一些需要滿足最終一致性的事情。那麼可以再思考一下,理論上只要訊息佇列不能100%保證不丟訊息,那也無法實現最終一致性。

其實總體而言,我們設計乙個訊息佇列,一言以蔽之,可以簡單的理解為設計乙個整體的訊息被消費的資料流

broker(服務端):broker這個概念主要來自於apache的activemq,特指訊息佇列的服務端。

consumer(訊息消費者):從訊息佇列接收訊息,consumer回覆消費確認。

訊息的轉儲:訊息儲存在broker伺服器上,在合適的時間點把訊息投遞出去,或者通過一系列手段輔助訊息最終能送達消費機。

規範一種正規化和通用的模式,以滿足解耦、最終一致性、錯峰等需求。

訊息的傳輸:rpc呼叫

build乙個整體的資料流,例如producer傳送給broker,broker傳送給consumer,consumer回覆消費確認,broker刪除/備份訊息等。 利用rpc將資料流串起來。然後考慮rpc的高可用性,盡量做到無狀態,方便水平擴充套件。 之後考慮如何承載訊息堆積,然後在合適的時機投遞訊息,而處理堆積的最佳方式,就是儲存,儲存的選型需要綜合考慮效能/可靠性和開發維護成本等諸多因素。 為了實現解耦非同步功能,我們必須要維護消費關係,可以利用zk/config server等儲存消費關係。

當然,在基本實現的基礎之上,訊息佇列也會視實際情況封裝一些高階特性,如可靠投遞,事務特性,效能優化等,這些高階特性不是本文**的重點,本文主要關注訊息佇列基本特性的原理和設計,即通訊協議、儲存選擇和消費關係維護這幾方面。

訊息message既是資訊的載體,訊息傳送者需要知道如何構造訊息,訊息接收者需要知道如何解析訊息,它們需要按照一種統一的格式描述訊息,這種統一的格式稱之為訊息協議。

幾種常見訊息通訊協議從速度上記憶體顯然是最快的,對於允許訊息丟失,訊息堆積能力要求不高的場景(例如日誌),記憶體會是比較好的選擇。

db則是最簡單的實現可靠儲存的方案,很適合用在可靠性要求很高,最終一致性的場景(例如交易訊息),對於不需要100%保證資料完整性的場景,要求效能和訊息堆積的場景,hbase也是乙個很好的選擇。

具體的選擇還是要從支援的業務場景出發作出最合理的選擇,如果你們的訊息佇列是用來支援支付/交易等對可靠性要求非常高,但對效能和量的要求沒有這麼高,而且沒有時間精力專門做檔案儲存系統的研究,db是最好的選擇;對於不需要100%保證資料完整性的場景,要求效能和訊息堆積的場景,hbase也是乙個很好的選擇,典型的比如 kafka的訊息落地可以使用hadoop。

經過上面的儲存選型以後,我們的訊息佇列就初步具備了轉儲訊息的能力。下面乙個重要的事情就是解析傳送接收關係,進行正確的訊息投遞了。市面上的訊息佇列定義了一堆讓人暈頭轉向的名詞,如jms 規範中的topic/queue,kafka裡面的topic/partition/consumergroup,rabbitmq裡面的exchange等等。 掰開了揉碎了看,無外乎是單播與廣播的區別。所謂單播,就是點到點;而廣播,是一點對多點。

為了實現廣播功能,我們必須要維護消費關係,通常訊息佇列本身不維護消費訂閱關係,可以利用zookeeper等成熟的系統維護消費關係,在消費關係發生變化時下發通知。

訊息中介軟體(之一) 訊息中介軟體設計

什麼是訊息中介軟體 訊息中介軟體常見協議 訊息持久化 訊息分發 高可用策略 高可靠利用高效,可靠的訊息傳遞機制進行平台無關的資料交流 基於資料通訊來進行分布式系統的整合 通過提供訊息傳遞和訊息排隊模型,在分布式環境下擴充套件程序間的通訊。跨系統資料傳遞,高併發流量削峰,資料非同步處理等。active...

訊息中介軟體設計思路

持久化訊息分發 高可用高可靠 三要素訊息中介軟體常見協議 openwire amqp mqtt kafka openmessage 為什麼訊息中介軟體不用 http 協議 http 太大,並且是短連線 高階訊息佇列協議即 advanced message queuing protocol amqp ...

訊息中介軟體系列一 訊息中介軟體的基本了解

前言 這是中介軟體乙個系列的文章之一,有需要的朋友可以看看這個系列的其他文章 訊息中介軟體系列 一 訊息中介軟體的基本了解 訊息中介軟體系列二 windows下的activemq和rabbitmq的安裝 訊息中介軟體系列三 jms和activemq的簡單使用 訊息中介軟體系列 四 認識amqp和ra...