消息隊列(Message Queue,簡稱MQ),其主要用於在複雜的微服務系統中進行消息通訊,它的優勢能夠大體整理成如下幾點:html
微服務系統業務之間相互依賴,各類調用錯綜複雜,若是不能良好對服務進行解耦那一個服務的可用性、併發都會受到其餘服務的影響。數據庫
在沒有引用MQ的以前服務調用大概是這些步驟:架構
圖上的A服務是直接調用的,這是沒啥問題的,可是服務上線後要迭代更新的麻,這個時候要是服務C的開發人員有點代碼小潔癖說:我這個C服務接口命名不太好,我須要從新更新下,當A服務的小哥哥還戴着小耳機聽着小歌曲,忽然就得改代碼了~~。併發
後來負責服務C的那小哥哥也很差意思了,提出你們一塊兒使用MQ吧,因而A、C的調用就變成下面這個樣子了:機器學習
服務A不直接調用C而是向消息隊列中發送消息(生產者),另外一邊的C取出隊列中的消息(消費者)進行處理,這樣A、C就完成了解耦。微服務
舉個例子,在沒引入MQ以前服務調用多個服務都是同步調用,好比像這樣:性能
服務A要順序的調用B、C服務來完成業務邏輯若是A->B
須要200ms,A->C
須要200ms,再加上自身業務邏輯處理可能須要花費500ms,其中有400ms是調用A和B的花費,明明自身100ms就能處理完還白白浪費400ms,不能忍啊因而能夠引入MQ作一下改造:學習
這下有了MQ,A服務只須要發一條消息好比花費50ms,再加上自身業務邏輯的100ms,那整個調用過程只須要花費150ms了,這樣對併發和性能都有必定的改善。大數據
突發流量就是互聯網很常見的狀況,有時候有熱點、突發事件,那日常QPS爲100的接口,忽然提高10-20倍這個時候沒有MQ全部流量直接進入服務,這對服務和數據庫都是很大的挑戰:日誌
再次引入MQ就狀況就不同了,服務A先將請求丟給MQ,而後能夠慢慢消費掉:
使用MQ還有不少好處,可是他也會帶一些麻煩事。首先就是會下降系統的可用性,好比MQ掛了怎麼辦呢?因此在引入MQ以前就須要考慮以後帶來的哪些問題,不能只看它的好處也須要考慮它很差的地方。好比下面列出的這些問題要若是解決:
下面咱們來分析下這些問題。
若是是單機消息隊列,一臺機器掛了消息隊列都就不用了,這是不能接受的,若是是一個消息隊列羣集,一臺機器掛了還有其餘機器能正常提供服務,因此要保證消息隊列的高可用,咱們就須要作消息隊列集羣。
以RabbitMQ爲例它有兩種集羣模式:
普通模式,RabbitMQ會同步各個節點的數據/狀態,但不包括消息隊列
,默認狀況下,消息隊列駐留在一個節點上,儘管它們在全部節點上都是可見且可訪問的。
在這種模式下,每一個節點都有會全部節點的元數據信息,因此當發送消息到隊列時,不管鏈接的是哪個節點都能正確的發送,可是節點只會同步其餘節點的元數據,消息隊列的數據仍是在一個節點上,若是這個節點掛了那就意味着發消息就會失敗,沒法保證消息隊列的高可用。
默認狀況下,RabbitMQ中Queue與Binding、Exchange不同,它只會存於聲明隊列的節點中,可是能夠選擇使Queue跨多個節點進行鏡像。
每個鏡像隊列由一個Master和一個或多個鏡像組成,任何隊列的的操做,都會先應用到Master節點上而後傳播到多個鏡像節點。若是Master節點掛了,最老的鏡像節點將會成爲新的Master節點。
RabbitMQ有兩種集羣方法:普通模式
、鏡像模式
,要實現消息隊列的高可用能夠選一種合適的集羣方式來達到,關於RabbitMQ的集羣搭建方式,因爲篇幅有限這裏就很少說,可自行查看 Distributed RabbitMQ文章。
想象下消費者收到重複的消息會發生什麼狀況,好比訂單支付消息,若是支付服務收到兩條重複的消息讓用戶去支付兩次,那用戶確定是不肯意的,明明已經支付過了還要支付。
如上圖中第四步消費消息B的時候失敗了,若是支付服務在作完業務以後,發送ACK以前服務掛了,MQ沒有收到ACK,因爲消息還存在隊列中,服務恢復正常後會再次收到消息,若是支付不作檢查那用戶就會發生兩次支付。
要避免這個重複消費的問題,能夠在消費端引入內存、Redis、數據庫來保存消息消費記錄,根據消息Id來判斷消息是否已經被消費過。
假設有訂單服務和支付服務,正常流程是用戶下單成功,而後向支付服務發送支付消息,這裏面就涉及訂單服務、支付服務、MQ的交互了,消息丟失能夠分爲三種狀況:
生產者消息丟失,可使用本地消息表解決、消息確認/重發等方式來解決。以RabbitMQ爲例,它有confirm
機制,發出去的消息是否入隊列,會使用回調的形式告知生產者,生產者收到消息後判斷是Ack
仍是Nak
,若是是Nak
則重發消息。
此時還會有問題,若是極端狀況下訂單服務掛了,再次重啓後消息就真丟失了,因此最好仍是在生產中對消息作持久化,待訂單服務恢復後使用Job從新發送消息。
MQ消息丟失通常爲未開啓持久化,MQ掛了再次重啓後消息丟失,因此應當將消息持久化到磁盤中。若是MQ收到消息後在同步到磁盤以前MQ掛了,那磁盤中也沒有消息,這樣仍是會致使消息丟失消息,不過這只是小几率事件。
消費者消息丟失,大都爲開啓了autoAck
選項,消費者收到消息後還未完成處理,此時服務掛了,因爲開啓了autoAck
, MQ會覺得此消息已經被成功消費,將消息從隊列中移除,而服務恢復事後也不會收到原來的消息了。
有些場景下要保持消息的順序消費怎麼辦?好比寫Log都是一條條打印出來,若是發到消息隊列後出現消費順序不一致那消息的那日誌就會亂掉,給看日誌的人帶來沒必要要的麻煩。好比爲了加快日誌的處理速度使用三個消費都處理日誌:
按圖上的流程,消費者A、B、C可能分別消費日誌一、二、3,這時候就沒法保證消息的處理順序。要保證消息的消費順序,首先讓消息都發送到同一個隊列,而後使用一個消費者去處理消息:
這樣消息的處理速度就大大下降,要保持消息的順序,則又想讓消息的處理速度不至於太慢,能夠引用本地隊列:
《架構文摘》天天一篇架構領域重磅好文,涉及一線互聯網公司應用架構(高可用、高性 能、高穩定)、大數據、機器學習等各個熱門領域。