消息隊列MQ帶來的一些問題、及解決方案

消息隊列MQ帶來的一些問題、及解決方案

RocketMQ的架構

RocketMQ由NameServer、Broker、Consumer、Producer組成,NameServer之間互不通信,Broker會向所有的nameServer註冊,通過心跳判斷broker是否存活,producer和consumer 通過nameserver就知道broker上有哪些topic。

Rocket:火箭。阿里巴巴雙十一官方指定消息產品,支撐阿里巴巴集團所有的消息服務,歷經十餘年高可用與高可靠的嚴苛考驗,是阿里巴巴交易鏈路的核心產品。

  • Broker :消息中轉角色,負責 存儲消息,轉發消息。 Broker 是具體提供業務的服務器,單個Broker節點與所有的NameServer節點保持長連接及心跳,並會定時將 Topic 信息註冊到NameServer,順帶一提底層的通信和連接都是 基於Netty實現 的。Broker 負責消息存儲,以Topic爲緯度支持輕量級的隊列,單機可以支撐上萬隊列規模,支持消息推拉模型。官網上有數據顯示:具有 上億級消息堆積能力 ,同時可 嚴格保證消息的有序性 。
  • Topic :主題!它是消息的第一級類型。比如一個電商系統可以分爲:交易消息、物流消息等,一條消息必須有一個 Topic 。 Topic 與生產者和消費者的關係非常鬆散,一個 Topic 可以有0個、1個、多個生產者向其發送消息,一個生產者也可以同時向不同的 Topic 發送消息。一個 Topic 也可以被 0個、1個、多個消費者訂閱。
  • Tag :標籤!可以看作子主題,它是消息的第二級類型,用於爲用戶提供額外的靈活性。使用標籤,同一業務模塊不同目的的消息就可以用相同Topic而不同的 Tag 來標識。比如交易消息又可以分爲:交易創建消息、交易完成消息等,一條消息可以沒有 Tag 。標籤有助於保持您的代碼乾淨和連貫,並且還可以爲 RocketMQ 提供的查詢系統提供幫助。
  • MessageQueue :一個Topic下可以設置多個消息隊列,發送消息時執行該消息的Topic,RocketMQ會輪詢該Topic下的所有隊列將消息發出去。消息的物理管理單位。一個Topic下可以有多個Queue,Queue的引入使得消息的存儲可以分佈式集羣化,具有了水平擴展能力。
  • NameServer :類似Kafka中的ZooKeeper,但NameServer集羣之間是 沒有通信 的,相對ZK來說更加 輕量 。它主要負責對於源數據的管理,包括了對於 Topic 和路由信息的管理。每個Broker在啓動的時候會到NameServer註冊,Producer在發送消息前會根據Topic去NameServer 獲取對應Broker的路由信息 ,Consumer也會定時獲取 Topic 的路由信息。
  • Producer : 生產者,支持三種方式發送消息: 同步、異步和單向 單向發送 :消息發出去後,可以繼續發送下一條消息或執行業務代碼,不等待服務器迴應,且 沒有回調函數 。異步發送 :消息發出去後,可以繼續發送下一條消息或執行業務代碼,不等待服務器迴應, 有回調函數 。同步發送 :消息發出去後,等待服務器響應成功或失敗,才能繼續後面的操作。
  • Consumer :消費者,支持 PUSH 和 PULL 兩種消費模式,支持 集羣消費 和 廣播消費 集羣消費 :該模式下一個消費者集羣共同消費一個主題的多個隊列,一個隊列只會被一個消費者消費,如果某個消費者掛掉,分組內其它消費者會接替掛掉的消費者繼續消費。廣播消費 :會發給消費者組中的每一個消費者進行消費。相當於 RabbitMQ 的發佈訂閱模式。
  • Group :分組,一個組可以訂閱多個Topic。分爲ProducerGroup,ConsumerGroup,代表某一類的生產者和消費者,一般來說同一個服務可以作爲Group,同一個Group一般來說發送和消費的消息都是一樣的
  • Offset :在RocketMQ中,所有消息隊列都是持久化,長度無限的數據結構,所謂長度無限是指隊列中的每個存儲單元都是定長,訪問其中的存儲單元使用Offset來訪問,Offset爲Java Long類型,64位,理論上在 100年內不會溢出,所以認爲是長度無限。也可以認爲Message Queue是一個長度無限的數組, Offset 就是下標。

image

rabbitmq 的架構

  • Broker :一個RabbitMQ實例就是一個Broker
  • Virtual Host :虛擬主機。 相當於MySQL的DataBase ,一個Broker上可以存在多個vhost,vhost之間相互隔離。每個vhost都擁有自己的隊列、交換機、綁定和權限機制。vhost必須在連接時指定,默認的vhost是/。
  • Exchange :交換機,用來接收生產者發送的消息並將這些消息路由給服務器中的隊列。
  • Queue :消息隊列,用來保存消息直到發送給消費者。它是消息的容器。一個消息可投入一個或多個隊列。
  • Banding :綁定關係,用於 消息隊列和交換機之間的關聯 。通過路由鍵( Routing Key )將交換機和消息隊列關聯起來。
  • Channel :管道,一條雙向數據流通道。不管是發佈消息、訂閱隊列還是接收消息,這些動作都是通過管道完成。因爲對於操作系統來說,建立和銷燬TCP都是非常昂貴的開銷,所以引入了管道的概念,以複用一條TCP連接。
  • Connection :生產者/消費者 與broker之間的TCP連接。
  • Publisher :消息的生產者。
  • Consumer :消息的消費者。
  • Message :消息,它是由消息頭和消息體組成。消息頭則包括 Routing-Key 、 Priority (優先級)等。

image

Kafka的架構

Kafka的元數據信息都是保存在Zookeeper,新版本部分已經存放到了Kafka內部了,由Broker、Zookeeper、Producer、Consumer組成。
Kafka是一個分佈式、支持分區的、多副本的, 基於ZooKeeper 協調的分佈式消息系統。

它最大的特性就是可以實時的處理大量數據以滿足各種需求場景:比如基於Hadoop的批處理系統、低延遲的實時系統、Storm/Spark流式處理引擎,Web/Nginx日誌、訪問日誌,消息服務等等,用 Scala語言編寫 。屬於Apache基金會的頂級開源項目。

  • Broker :消息中間件處理節點,一個Kafka節點就是一個Broker,一個或者多個Broker可以組成一個Kafka集羣

  • Topic :Kafka根據topic對消息進行歸類,發佈到Kafka集羣的每條消息都需要指定一個topic

  • Producer :消息生產者,向Broker發送消息的客戶端

  • Consumer :消息消費者,從Broker讀取消息的客戶端

  • ConsumerGroup :每個Consumer屬於一個特定的ConsumerGroup,一條消息可以被多個不同的ConsumerGroup消費,但是一個ConsumerGroup中只能有一個Consumer能夠消費該消息

  • Partition :物理上的概念,一個topic可以分爲多個partition,每個partition內部消息是有序的

  • Leader :每個Partition有多個副本,其中有且僅有一個作爲Leader,Leader是負責數據讀寫的Partition。

  • Follower :Follower跟隨Leader,所有寫請求都通過Leader路由,數據變更會廣播給所有Follower,Follower與Leader保持數據同步。如果Leader失效,則從Follower中選舉出一個新的Leader。當Follower與Leader掛掉、卡住或者同步太慢,Leader會把這個Follower從 ISR列表 中刪除,重新創建一個Follower。

  • Offset :偏移量。Kafka的存儲文件都是按照offset.kafka來命名,用Offset做名字的好處是方便查找。例如你想找位於2049的位置,只要找到2048.kafka的文件即可。

  • 可以這麼來理解Topic,Partition和Broker:

一個Topic,代表邏輯上的一個業務數據集,比如訂單相關操作消息放入訂單Topic,用戶相關操作消息放入用戶Topic,對於大型網站來說,後端數據都是海量的,訂單消息很可能是非常巨量的,比如有幾百個G甚至達到TB級別,如果把這麼多數據都放在一臺機器上可定會有容量限制問題,那麼就可以在Topic內部劃分多個Partition來分片存儲數據,不同的Partition可以位於不同的機器上,相當於 分佈式存儲 。每臺機器上都運行一個Kafka的進程Broker。

image

總結

  • RocketMQ定位於非日誌的可靠消息傳輸(日誌場景也OK),目前RocketMQ在阿里集團被廣泛應用在訂單,交易,充值,流計算,消息推送,日誌流式處理,binglog分發等場景。
  • RocketMQ的同步刷盤在單機可靠性上比Kafka更高,不會因爲操作系統Crash,導致數據丟失。
  • 同時同步Replication也比Kafka異步Replication更可靠,數據完全無單點。
  • 另外Kafka的Replication以topic爲單位,支持主機宕機,備機自動切換,但是這裏有個問題,由於是異步Replication,那麼切換後會有數據丟失,同時Leader如果重啓後,會與已經存在的Leader產生數據衝突。
  • 例如充值類應用,當前時刻調用運營商網關,充值失敗,可能是對方壓力過多,稍後在調用就會成功,如支付寶到銀行扣款也是類似需求。這裏的重試需要可靠的重試,即失敗重試的消息不因爲Consumer宕機導致丟失。

如何保證順序消費?

  • RabbitMQ :一個Queue對應一個Consumer即可解決。
  • RocketMQ hash(key)%隊列數
  • Kafka: hash(key)%分區數

如何實現延遲消費?

  • RabbitMQ :兩種方案
    死信隊列 + TTL引入RabbitMQ的延遲插件
  • RocketMQ :天生支持延時消息。
  • Kafka :步驟如下
    專門爲要延遲的消息創建一個Topic新建一個消費者去消費這個Topic消息持久化再開一個線程定時去拉取持久化的消息,放入實際要消費的Topic實際消費的消費者從實際要消費的Topic拉取消息。

如何保證消息的可靠性投遞

RabbitMQ:

Broker-->消費者:手動ACK
生產者-->Broker:兩種方案
數據庫持久化:

1.將業務訂單數據和生成的Message進行持久化操作(一般情況下插入數據庫,這裏如果分庫的話可能涉及到分佈式事務)

2.將Message發送到Broker服務器中

3.通過RabbitMQ的Confirm機制,在producer端,監聽服務器是否ACK。

4.如果ACK了,就將Message這條數據狀態更新爲已發送。如果失敗,修改爲失敗狀態。

5.分佈式定時任務查詢數據庫3分鐘(這個具體時間應該根據的時效性來定)之前的發送失敗的消息

6.重新發送消息,記錄發送次數

7.如果發送次數過多仍然失敗,那麼就需要人工排查之類的操作。

image

優點:能夠保證消息百分百不丟失。

缺點:第一步會涉及到分佈式事務問題。

消息的延遲投遞:

流程圖中,顏色不同的代表不同的message

1.將業務訂單持久化

2.發送一條Message到broker(稱之爲主Message),再發送相同的一條到不同的隊列或者交換機(這條稱爲確認Message)中。

3.主Message由實際業務處理端消費後,生成一條響應Message。之前的確認Message由Message Service應用處理入庫。

4~6.實際業務處理端發送的確認Message由Message Service接收後,將原Message狀態修改。

7.如果該條Message沒有被確認,則通過rpc調用重新由producer進行全過程。

image

優點:相對於持久化方案來說響應速度有所提升

缺點:系統複雜性有點高,萬一兩條消息都失敗了,消息存在丟失情況,仍需Confirm機制做補償。

RocketMQ

生產者弄丟數據:

Producer在把Message發送Broker的過程中,因爲網絡問題等發生丟失,或者Message到了Broker,但是出了問題,沒有保存下來。針對這個問題,RocketMQ對Producer發送消息設置了3種方式:

同步發送
異步發送
單向發送

Broker弄丟數據:

Broker接收到Message暫存到內存,Consumer還沒來得及消費,Broker掛掉了。

可以通過 持久化 設置去解決:

創建Queue的時候設置持久化,保證Broker持久化Queue的元數據,但是不會持久化Queue裏面的消息
將Message的deliveryMode設置爲2,可以將消息持久化到磁盤,這樣只有Message支持化到磁盤之後纔會發送通知Producer ack
這兩步過後,即使Broker掛了,Producer肯定收不到ack的,就可以進行重發。

消費者弄丟數據:

Consumer有消費到Message,但是內部出現問題,Message還沒處理,Broker以爲Consumer處理完了,只會把後續的消息發送。這時候,就要 關閉autoack,消息處理過後,進行手動ack , 多次消費失敗的消息,會進入 死信隊列 ,這時候需要人工干預。

Kafka

生產者弄丟數據

設置了 acks=all ,一定不會丟,要求是,你的 leader 接收到消息,所有的 follower 都同步到了消息之後,才認爲本次寫成功了。如果沒滿足這個條件,生產者會自動不斷的重試,重試無限次。

Broker弄丟數據

Kafka 某個 broker 宕機,然後重新選舉 partition 的 leader。大家想想,要是此時其他的 follower 剛好還有些數據沒有同步,結果此時 leader 掛了,然後選舉某個 follower 成 leader 之後,不就少了一些數據?這就丟了一些數據啊。

此時一般是要求起碼設置如下 4 個參數:

replication.factor
min.insync.replicas
acks=all
retries=MAX

我們生產環境就是按照上述要求配置的,這樣配置之後,至少在 Kafka broker 端就可以保證在 leader 所在 broker 發生故障,進行 leader 切換時,數據不會丟失。

消費者弄丟數據

你消費到了這個消息,然後消費者那邊自動提交了 offset,讓 Kafka 以爲你已經消費好了這個消息,但其實你纔剛準備處理這個消息,你還沒處理,你自己就掛了,此時這條消息就丟咯。

這不是跟 RabbitMQ 差不多嗎,大家都知道 Kafka 會自動提交 offset,那麼只要 關閉自動提交 offset,在處理完之後自己手動提交 offset,就可以保證數據不會丟。 但是此時確實還是可能會有重複消費,比如你剛處理完,還沒提交 offset,結果自己掛了,此時肯定會重複消費一次,自己保證冪等性就好了。

如何保證消息的冪等?

以 RocketMQ 爲例,下面列出了消息重複的場景:

發送時消息重複

當一條消息已被成功發送到服務端並完成持久化,此時出現了網絡閃斷或者客戶端宕機,導致服務端對客戶端應答失敗。如果此時生產者意識到消息發送失敗並嘗試再次發送消息,消費者後續會收到兩條內容相同並且Message ID也相同的消息。

投遞時消息重複

消息消費的場景下,消息已投遞到消費者並完成業務處理,當客戶端給服務端反饋應答的時候網絡閃斷。爲了保證消息至少被消費一次,消息隊列RocketMQ版的服務端將在網絡恢復後再次嘗試投遞之前已被處理過的消息,消費者後續會收到兩條內容相同並且Message ID也相同的消息。

負載均衡時消息重複(包括但不限於網絡抖動、Broker重啓以及消費者應用重啓)

當消息隊列RocketMQ版的Broker或客戶端重啓、擴容或縮容時,會觸發Rebalance,此時消費者可能會收到重複消息。

那麼,有什麼解決方案呢? 直接上圖。

image

如何解決消息積壓的問題?

關於這個問題,有幾個點需要考慮:

如何快速讓積壓的消息被消費掉?

臨時寫一個消息分發的消費者,把積壓隊列裏的消息均勻分發到N個隊列中,同時一個隊列對應一個消費者,相當於消費速度提高了N倍。

積壓時間太久,導致部分消息過期,怎麼處理?

批量重導。在業務不繁忙的時候,比如凌晨,提前準備好程序,把丟失的那批消息查出來,重新導入到MQ中。

消息大量積壓,MQ磁盤被寫滿了,導致新消息進不來了,丟掉了大量消息,怎麼處理?

這個沒辦法。誰讓【消息分發的消費者】寫的太慢了,你臨時寫程序,接入數據來消費,消費一個丟棄一個,都不要了,快速消費掉所有的消息。然後走第二個方案,到了晚上再補數據吧。

文:一隻阿木木

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章