詳解消息隊列的常見功能場景與使用精髓

消息隊列(MQ)是一種不一樣應用程序之間(跨進程)的通訊方法。安全

消息隊列主要有哪些功能?服務器

  • 異步處理 - 增長吞吐量;微信

  • 削峯填谷 - 提升系統穩定性;網絡

  • 系統解耦 - 業務邊界隔離;session

  • 數據同步 - 最終一致性保證。多線程

  •  

 

應用程序經過寫入和檢索出入列隊的數據(消息)來通訊,而無需經過專用鏈接來連接它們。框架

 

消息傳遞指的是程序之間經過在消息中發送數據進行通訊,而不是經過直接調用彼此來通訊,直接調用一般是用於諸如遠程過程調用(Remote Procedure Call. RPC)的技術。異步

 

排隊指的是應用程序經過隊列來通訊。隊列的使用除去了接收和發送應用程序同時執行的要求,這樣就自然地實現了異步的目標。分佈式

 

那麼MQ還有哪些功能場景呢?下面逐一介紹。性能

 

到底有多少種隊列?其實主要看處理業務的範圍大小:

  • 應用內部 - 採用線程池,好比 Java ThreadPool 中 BlockingQueue 來作任務級別的緩衝與處理;

  • 應用外部 - 好比 RabbitMQ、ActiveMQ 就是作應用級別的隊列,方便進行業務邊界隔離與提升吞吐量。

同時,技術上來說,消息隊列通常分爲兩種模型:Pull VS Push

  • Pull 模型:消費者主動請求消息隊列,獲取隊列中的消息;

  • Push 模型:消息隊列主動推送消息到消費者。

 

其中 Pull 模式能夠控制消費速度,沒必要擔憂本身處理不了消息,只須要維護隊列中偏移量 Offset。因此對於消費量有限而且推送到隊列的生產者不均勻的狀況下,採用 Pull 模式比較合適。

 

Push 比較適合實時性要求比較高的狀況,只要生產者消息發送到消息隊列中,隊列就會主動 Push 消息到消費者。不過這種模式對消費者的能力要求就提升不少,若是出現隊列給消費者推送一些不能處理的消息,消費者出現 Exception 狀況下,就會再次入隊列,形成消費堵塞的狀況。

 

不過互聯網業界比較成熟的隊列主要以採用 Pull 模式爲主,像 Kafka、RabbitMQ(兩種方式都支持)、RocketMQ 等。

 

冪等

什麼是冪等設計?

 

其實很簡單,就是一次請求和多個請求的做用是同樣的。用數學上的術語,便是 f(x) = f(f(x))。

 

那咱們爲何要作冪等性的設計呢?主要是由於如今的系統可能是採用分佈式的方式來設計,在分佈式系統中調用通常分爲 3 個狀態:成功、失敗、超時。

 

若是調用是成功或者失敗都沒關係,由於狀態是明確和清晰的,但若是出現超時的狀況,就不知道請求是成功仍是失敗了。

 

 

若是出現這種狀況,咱們該怎麼辦?通常採起重試的操做,從新請求對應接口。若是請求接口是 Get 操做的話,那倒還好,由於請求屢次的效果是同樣的。但若是是 Post、Put 操做的話,就會形成數據不一致,甚至數據覆蓋等問題。

 

舉個例子:在支付收銀臺頁面進行支付的時候,由於網絡超時的問題致使支付失敗,這時咱們都會再進行一次支付操做,可是當支付成功後,若是發現你的帳戶餘額被減了 2 次,內心確定會很不爽。

 

形成這個問題的關鍵是:網絡超時後,不知道支付是什麼狀態,是成功仍是失敗呢?因此說冪等性設計是必須的,尤爲在電商、金融、銀行等對數據要求比較高的行業中。

 

通常在這種場景下,咱們該怎麼解決?

  1. 請求方通常會生產一個惟一性 ID 標識,這個標識能夠具備業務同樣,好比訂單號或者支付流水號,在發起請求時候帶上惟一性 ID;

  2. 接收者在收到請求後,第一步經過獲取惟一性 ID 來查詢接收端是否有對應的記錄。若是有的話,就直接將上次請求的結果返回;若是沒有的話,就進行操做,並在操做完成後記錄到對應的表裏。

 

 

解耦

 

 

服務降級

 

服務降級主要解決資源不足和訪問量過大的問題,好比電商平臺在 61八、雙十一等高峯時期採用部分服務不提供訪問,減小對系統的影響。

 

降級的方式有哪些?

  • 延遲服務:好比春晚,微信發紅包就出現搶到紅包,可是帳號餘額並無增長,要過幾天才能加上去。其實這是微信內部採用延遲服務的方式來保證服務的穩定,經過隊列實現記錄流水帳單;

  • 功能降級:中止不重要的功能是很是有用的方式,把相對不重要的功能暫停掉,讓系統釋放更多的資源。好比關閉相關文章的推薦、用戶的評論功能等等,等高峯過去以後,在把服務恢復回來;

  • 下降數據一致性:在大促的時候,咱們發現頁面上不顯示真實庫存的數據,只顯示到底有仍是沒有庫存這兩種狀態。

 

 

剛剛說了降級的方式,那操做降級時有哪些注意要點呢?

  1. 清晰定義降級級別: 好比出現吞吐量超過 X,單位時間內響應時間超過 Y 秒、失敗次數超過 Z 次等,這些閾值須要在準備的時候,經過壓測的方式來肯定;

  2. 梳理業務級別:降級以前,首先須要肯定哪些業務是必須有,哪些業務是能夠有的,哪些業務是無關緊要的;

  3. 降級開關:能夠經過接入配置中心(好比攜程 Apollo、百度 Disconf )的方式直接後臺降級。可是若是公司沒有配置中心的話,能夠封裝一個 API 接口來切分,不過該 API 接口要作成冪等的方式,同時須要作一些簡單的簽名,來保證其必定的安全性。

 

 

解耦

MQ最直接的使用場景就是能夠將兩個系統進行解耦,好比咱們的貨款抵扣業務場景,用戶生成訂單發送MQ後當即返回,結算系統去消費該MQ進行用戶帳戶金額的扣款。這樣訂單系統只須要關注把訂單建立成功,最大可能的提升訂單量,而且生成訂單後當即返回用戶。

 

而結算系統重點關心的是帳戶金額的扣減,保證帳戶金額最終一致。這個場景裏面還會涉及到重試冪等性問題,後面有介紹。

 

 

削峯填谷

 

仍是以訂單系統和結算系統場景爲例,若是訂單系統經過RPC框架來調用結算系統,在有高峯促銷的狀況下生成訂單的量會很是大,並且因爲生成訂單的速度也很是快,這樣勢必會給結算系統形成系統壓力,服務器利用率則會偏高,但在不是高峯的時間點訂單量比較小,結算系統的服務器利用率則會偏低。

 

對於結算系統來講就會出現下面這樣的高峯波谷現象圖。

 

削峯填谷

 

那麼若是經過MQ的方式,將訂單存儲到MQ隊列中,消費端經過拉取的方式,而且拉去速度有消費端來控制,則就能夠控制流量趨於平穩。這樣對於結算系統來說,就達到了削峯填谷的目的,或者提及到了流控的目標。接下來,咱們介紹一下拉取方式。

 

拉取模式指用戶在代碼裏主動調用pull方法,不須要在配置文件裏面再配置<mq:listener />,拉取的速度由用戶控制,調用一次拉取一次消息進行消費,這裏要重視消費的速度若是消費性能降低必定會形成積壓,所以用戶本身啓用多線程控制並行度以提升消費速度。


代碼樣例:

messageConsumer.start();
for (;;){
   //手動拉取消息
   messageConsumer.pull(topic,messageListener);
}

 

method: pull(String topic,MessageListener listener)
topic:指消費的主題名
listener:是一個回調對象,當pull拉取到消息後會主動調用listener.onMessage(),
 

與監聽模式的區別是:監聽模式由MQ客戶端守護線程去不停地拉取消息進行消費,拉取模式由用戶控制拉取的頻率,不主動調用就不會消費消息。

 

可是都不須要主動對消息進行確認。這種方式更適合寫場景,保證最終結果落地便可,由於讀是須要當即返回以避免讓用戶長時間等待從而影響用戶體驗。

 

 

最終一致性

 

                                                                     最終一致性

 

一致性問題分爲強一致性、弱一致性、最終一致性。大多數互聯網業務要求實現最終一致性。

 

仍是以訂單系統和結算系統業務場景舉例,訂單系統建立成功一個訂單後給用戶返回的結果,即成功並明確告訴用戶會從帳戶中扣除相應的金額。那麼結算系統須要保持跟訂單系統相同的狀態,即從用帳戶中實際扣除一致的金額。

 

訂單系統會涉及兩個動做,一個是建立成功訂單,一個是發送成功通知到MQ,咱們能夠把這兩個動做放入到一個本地事務中,要麼成功要麼失敗。

 

當一次發送MQ失敗以後,能夠結合定時任務進行補償,這樣能夠保證生成訂單的結果能夠落地到mq的存儲中。一樣結算系統消費端依靠MQ重試機制一直髮送消息,直到消費端最終確認扣款業務成功處理完成。

 

這樣咱們經過消息落地加補償,消費端從業務上面考慮重複消費的保障,也就是作好冪等性操做,利用MQ實現了最終一致性。

 

 

廣播消費

 

 

MQ有兩種消息模式一種是點對點模式,一種是發佈/訂閱模式(最經常使用的模式)。同時發佈/訂閱模式按照消費類型又能夠分爲集羣消費和廣播消費。大部分狀況下咱們使用的是集羣消費。

 

集羣消費:MQ發送任何一條消息,集羣中只有一臺服務器可隨機消費到這條消息。以下圖:

 

集羣消費

 

廣播消費:MQ發送每一條消息,集羣中的每一臺服務器至少消費到一次。以下圖:

 

                                                                   廣播消費

 

廣播消費舉例:消息推送系統。首先某一個客戶端與消息中心應用集羣中的一臺服務器創建長鏈接並將鏈接session信息保存到當前服務器內存中,集羣在消費業務消息的時候,是不知道該客戶端創建的長鏈接在哪一臺服務器上面。

 

這個時候經過廣播消費,集羣中的每一臺服務器均可以消費到業務消息。在決定向用戶推送通知以前會判斷當前服務器內存中是否有該客戶端的鏈接session信息,若是有則推送,進而客戶端經過http協議拉取用戶的消息實體。若是session信息不在當前服務器上面,則丟棄。以下圖:

 

廣播消費舉例

 

廣播消費注意事項


一、消費進度在消費端管理,好比默認會在主目錄下建立offset文件夾,偏移量文件存儲在offset目錄下,出現重複的機率要大於集羣消費。


二、MQ能夠確保每條消息至少被每臺消費方服務器消費一次,可是若是消費方消費失敗,不會進入重試,所以業務方須要關注消費失敗的狀況。


三、因爲廣播消費消息不會進行確認,因此管理端上顯示的積壓數會一直不變,須要以出對數爲準。

 

 

使用集羣消費模擬廣播

 

 

在發佈/訂閱模式中,若是是集羣消費,那麼一條消息只能被集羣中的隨機一臺服務器消費到,若是咱們有須要集羣中的每臺服務器消費到如上面的消息推送的例子,咱們使用廣播消費來實現。

 

可是廣播消費有一些弊端好比不支持順序消息,消費進度在客戶端維護出現重複的概率要大於集羣模式,廣播模式下不能維護消費進度,因此管理端上面的積壓數一直保持不變,咱們就必須以出隊數爲準,也就是不可以支持消息堆積的查詢。

 

若是要規避這些弊端,那麼咱們能夠利用集羣消費來模擬廣播,在集羣消費中,咱們的每臺服務器上的消費APPID是相同的,若是要達到廣播的效果,那麼每臺服務器上面的消費APPID保持不一樣就能夠了。

 

                                                     模擬廣播

 

重試之坑

 

 

 

重試之坑

 

MQ的重試功能能夠保證數據結果最終獲得處理,但同時也正由於有重試,在業務處理的時候就須要格外注意冪等性的問題。

 

好比貨款抵扣業務,訂單系統生成訂單以後調用結算平臺去扣除用戶的帳戶金額。結算平臺要根據流水號去計算,若是訂單系統在調用結算平臺的時候發生了網絡異常,形成告終算平臺實際上已經獲得請求而且已處理。

 

訂單系統一側認爲發生異常須要重試,後續再發送到結算平臺的訂單就會形成重複扣款問題。因此流水號尤爲要注意須要保證重試過程當中每次發送的流水號是一致的,結算平臺會根據流水號去作業務校驗,若是已經處理,則丟棄,最終確保冪等性。

 

 

總結

 

本文介紹了MQ常見的使用場景,以及每種場景下的使用注意事項。尤爲是在重試功能中,重試原本是MQ提供的一種保持數據最終能夠獲得確認的方法,可是若是業務使用上不注意冪等性,則會帶來業務數據的不一致甚至像重複扣款這樣比較嚴重的後果。

 

本文還介紹了發佈/訂閱模式下的廣播消費的使用舉例,也介紹了它的缺點以及可使用集羣消費來模擬廣播。以上每種場景都提供了很好的說明,相信你們在之後使用MQ的過程當中能夠更好地發揮MQ的強大做用。

相關文章
相關標籤/搜索