消息隊列常見問題分析

1、簡介

好久之前也寫過一篇關於消息隊列的文章,這裏的文章,這篇文章是對消息隊列使用場景,以及一些模型作過一點介紹。html

這篇文章將分析消息隊列常見問題。前端

消息隊列:利用高效可靠的消息傳遞機制進行與平臺無關的數據交流,並基於數據通訊來進行分佈式系統集成。java

從定義看:它是一種數據交流平臺,也是數據通訊平臺。
然而,數據通訊咱們能夠用http,RPC來進行通訊,這些與消息隊列有什麼區別呢?
最大的區別就是同步和異步。http和RPC通常都是同步,而消息隊列是異步。git

2、爲何要用消息隊列

1.解耦
雙方不在基於對方直接通訊了,而是基於消息隊列來通訊,經過MQ解耦了客戶端和服務端通訊。處理數據的雙方關注的點不一樣了,好比說一個事務,咱們只關心核心流程,而須要依賴其餘系統但不是那麼重要的事情,有通知便可,不須要等待結果。這種消息模型,關心的是通知,而不在乎處理過程。也能夠用消息隊列。
上下游開發人員也能夠基於消息隊列發送消息,而不須要同步的處理消息了。github

2.異步處理
傳統的業務邏輯都是基於同步的方式進行處理的。而有了消息隊列,就能夠把消息存放在MQ裏,消息隊列的消費者就能夠從消息隊列中獲取數據並進行處理。它不必定要實時處理,能夠隔幾分鐘處理消息隊列裏的數據。web

3.削峯和流控
這裏有點像計算機中的硬件,好比CPU和內存,CPU運算速度比內存高N個數量級,那怎麼才能緩解二者之間的差別?中間加一個緩存來緩解二者速度的差別。
同理,MQ也能夠起到這種做用。對於上下游軟件不一樣的處理速度的差別進行調節。數據庫

好比,咱們常見的秒殺應用,前端瞬間涌入成千上萬的請求,前端能夠承受這麼大的請求壓力,可是複雜的後端系統,確定會被壓垮,從而致使秒殺服務不能夠用的狀況。爲了解決這種先後端處理速度不平衡的差別,致使的服務問題,能夠引入消息隊列來調節,用消息隊列來緩存用戶的請求,等待後端系統來消費。apache

上面就是消息隊列的主要功能,固然還有其餘一些功能,好比消息廣播,最終一致性等。後端

使用MQ後的問題

固然使用了消息隊列,會增長系統的複雜性,一致性延遲,可用性下降等問題。
可用性下降是指系統可用性下降,若是MQ掛了,那麼確定會影響到整個系統了。
由於上下游系統可能都會與MQ交互。緩存

3、何時引入MQ?

這個要看業務系統功能需求,一個是系統處理是否到達了瓶頸,須要消息隊列來緩解;
還有,業務系統一致性要求是否是特別高。一般業務系統不會要求那麼高的一致性要求。固然一些高頻交易系統,一致性要求特別高,就不適合用了。

引入任何一個新的軟件必然會增長原有系統的複雜性,仍是要根據業務特性進行合理的選擇。

4、消息隊列常見問題

1.如何保證消息不被重複消費(怎麼保證冪等)

爲何會重複消費

  • 生產者:也就是客戶端,可能會重複推送一條數據到MQ中。有多是客戶端超時重複推送,也有多是網絡比較慢客戶端重複推送了數據到MQ中。
  • MQ:消費者消費完了一條數據,發送ACK信息表示消費成功時,這時候,MQ忽然掛了,致使MQ覺得消費者還未消費該條消息,MQ恢復後再次推送了該條消息,致使重複消費。
  • 消費者:與上面MQ掛掉狀況相似,消費者已經消費完了一條消息,正準備給MQ發送ACK消息但還未發送時,這時候消費者掛了,服務重啓後MQ覺得消費者尚未消費該條消息,再次推送該條消息。

怎麼處理重複消費

每一個消息都帶一個惟一的消息id。消費端保證不重複消費就能夠了,即便生產端產生了重複的數據,固然生產端也最好控制下重複數據。

消費端保證不重複消費:
一般方法都是存儲消費了的消息,而後判斷消息是否存在。

1.先保存在查詢
每次保存數據前,先查詢下,不存在就插入。這種是併發不高的狀況下可使用。

2.數據庫添加惟一約束條件
好比惟一索引

3.增長一個消息表
已經消費的消息,把消息id插入到消息表裏面。
爲了保證高併發,消息表能夠用Redis來存。

2.如何處理消息丟失的問題

消息丟失的緣由

  • 生產者:生產者推送消息到MQ中,可是網絡出現了故障,好比網絡超時,網絡抖動,致使消息沒有推送到MQ中,在網絡中丟失了。又或者推送到MQ中了,可是這時候MQ內部出錯致使消息丟失。

  • MQ:MQ本身內部發生了錯誤,致使消息丟失。

  • 消費者:有時處理消息的消費者處理不當,還沒等消息處理完,就給MQ發送確認信息,可是這時候消費者自身出問題,掛了,確認消息已經發送給MQ告訴MQ本身已經消費完了,致使消息丟失。

如何保證消息不丟失呢? 下面談談這方面的作法。

3.如何保證消息可靠性傳輸

整個消息從生產到消費通常分爲三個階段:生產者-生產階段,MQ-存儲階段,消費者-消費階段

3.1 生產者-生產階段
在這個階段,通常經過請求確認機制,來保證消息可靠性傳輸。 與TCP/IP協議裏ACK機制有點像。
客戶端發送消息到消息隊列,消息隊列給客戶端一個確認響應,表示消息已經收到,客戶端收到響應,表示一次正常消息發送完畢。

3.2 MQ-存儲階段
消息隊列給客戶端發送確認消息。存儲完成後,才發送確認消息。

3.3 消費者-消費階段
跟生產階段相同,消費完了,給消息隊列發送確認消息。

4.如何保證消息的順序性

咱們平常說的順序性是什麼呢?

好比說小孩早上上學過程,他先起牀,而後洗漱,吃早餐,最後上學。咱們認爲他作的事情是有前後順序的,及是時間的前後順序,咱們用時間來標記他的順序。
更抽象的理解,這些發生的事件有一個相同的參考系,即他們的時間是對應同一個物理時鐘的時間。

若是沒有絕對的時間做爲參考系,那他們之間還能肯定順序嗎?
若是事件之間有因果關係,好比A、B兩個事件是因果關係,那麼A必定發生在B以前(前應後果)。相反,在沒有一個絕對的時間的參考的狀況下,若A、B之間沒有因果關係,那麼A、B之間就沒有順序關係。跟java裏的happen before很像。

總結一下,咱們說順序時,其實說的是

  • 在有絕對時間做爲參考系的狀況下,事件發生的時間前後關係;
  • 在沒有絕對時間做爲參考系的狀況下,一種由因果關係推斷出來的happening before的關係;

在分佈式系統領域,有一篇關於時間,時鐘和事件的順序的頗有名的一篇論文
Time, Clocks, and the Ordering of Events in a Distributed System
,能夠看一看,上面舉例狀況都是參考這篇論文。

參考上面的結論,在消息隊列中,咱們也是以時間做爲參考系,讓消息有序。

可是,在消息隊列中,消息有序會遇到一些問題,下面讓咱們來討論這些問題。

消息的順序性的一些問題

在計算機系統中,有一個比較棘手的問題是,它能夠是多線程執行的,並且哪一個線程先運行,哪一個線程後運行,徹底是由操做系統決定的,徹底沒有規律,是亂序執行。顯然與消息隊列中的消息有序相悖。

還有,在消息隊列中,涉及到生產者,MQ,消費者,還有網絡,這4者之間的關係。而後他們又涉及到消息的順序性,就有不少種狀況須要考慮。能夠參考這篇文章
分佈式開放消息系統(RocketMQ)的原理與實踐
(做者:CHUAN.CHEN),各類狀況討論的很全面。

最後的結論就是:消息的順序性,不只僅是MQ自己存儲消息要保證順序性,還須要生產者和消費者一同來保證順序性。

順序性保證

在消息隊列中,消息的順序性須要3方面來保證:
一、生產者發送消息時要保證順序
二、消息被消息隊列存儲時要保持和發送的順序一致
三、消息被消費時保持和存儲的順序一致

生產者:發送時要求用戶在同一個線程中採用同步的方式發送。
消息隊列:存儲保持和發送的順序一致。通常是在一個分區中保持順序性。
消費者:一個分區的消息由一個線程來處理消費消息。

https://www.hicsc.com/post/2020041566 這個連接中,做者分析了RocketMQ順序消息的代碼實現。

5.消息隊列中消息延遲問題

你說的 消息的延遲 是延遲消息隊列嗎? 啊,並非,是徹底2個不一樣的概念。延遲消息隊列是MQ提供的一個功能。消息的延遲,是指消費端消費的速度跟不上生產端產生消息的速度,可能致使消費端丟失數據,也可能致使消息積壓在MQ中。因此這裏說的消息的延遲,指的是消費端消費消息的延遲。

消息隊列的消費模型pull和push:

一、push模式

這種模式是消息隊列主動將消息推送給消費者。

  • 優勢:儘量實時的將消息發送給消費者進行消費。
  • 缺點:若是消費端消費能力弱,消費端的消費速度趕不上生產端,而MQ又不斷的給消費端推送消息,消費端的緩存滿了致使緩存溢出,就會產生錯誤或丟失數據的可能。
二、pull模式

這種模式是由消費端主動向消息隊列拉取消息。

  • 優勢:能夠自主可控的拉取消息。
  • 缺點:拉取消息的頻率很差控制。

a、若是每次pull時間間隔比較久,會增長消息延遲,消息到達消費者時間會加長。這樣時間一長會致使MQ中消息的堆積,而消息長時間堆積就會致使一系列的問題:

  • 一、若是積壓了幾個小時的數據,有幾千萬的數據量,消費端處理的壓力會愈來愈大。
  • 二、若是是帶有過時時間的消息,可能這些消息已經到了過時時間,由於積壓時間太長,但還沒被消費端消費掉,消費端來不及消費。
  • 三、若是持續的積壓,達到了MQ能存儲消息數量的上限,也就是說MQ滿了,存不下了,會致使MQ丟掉數據,致使數據丟失。
    想一下,上面的情形是否是跟TCP/IP協議的流量控制和擁塞控制遇到的一些問題很像,也有不少不一樣。

b、若是每次pull的時間間隔比較短,在一段時間內MQ中沒有可消費的消息,會產生不少無效的pull請求,致使必定的網絡開銷。

因此解決問題的辦法最主要就是優化消費端的消費性能。1.優化消費邏輯 2.水平擴容,增長消費端併發。

延遲問題處理

若是消息堆積已經發生了,致使了上面的3個問題,這時怎麼辦?
一、積壓了幾個小時幾千萬的數據
第一:確定要找到積壓數據的緣由,通常都是消費端的問題。
第二:若是能夠的,擴大消費端的數量,快速消費掉消息。
第三:擴容,增長多機器消費。新建一個topic,partition是原來10倍,創建原先10倍的queue。而後寫一個臨時的消費程序,這個消費程序去轉移積壓的數據,把積壓的數據均勻輪詢寫入創建好的10倍數量的queue。而後在徵用10倍機器的消費端來消費這個queue。這種作法至關於臨時將 queue 資源和 consumer 資源擴大 10 倍,以正常的 10 倍速度來消費數據。消費完了,恢復原來的部署。這是大廠作法。

二、積壓時間過長,帶有過時時間的消息過時失效了
這個沒有好的辦法處理,只能經過程序找出丟失的數據,而後也是經過程序把丟失的數據從新導入到MQ裏,從新消費。

三、長時間積壓卻是MQ寫滿了
這個也沒啥好辦法處理,只能快速消費掉MQ裏的數據,快速消費指消費一個,丟掉一個,不要這些數據了,而後從新導入數據。用戶少的時候在補回數據。

6.消息隊列高可用

6.1 kafka

kafka基本架構:

  • Broker:一個kafka節點就是一個broker,多個broker組成一個kafka集羣。一個broker能夠是一個單機器kafka服務器。
  • Topic:存放消息的主題,至關於一個隊列。能夠理解爲存放消息的分類,好比你能夠有前端日誌的Topic,後端日誌的Topic。能夠理解爲MySQL裏的表。
  • Partition:一個topic能夠劃分爲多個partition,每一個partition都是一個有序隊列。把topic主題中的消息進行分拆,均攤到kafka集羣中不一樣機器上。partition是topic的進一步拆分。
  • Replica:副本消息。kafka能夠以partition爲單位,保存多個副本,分散在不一樣的broker上。副本數是能夠設置的。
  • Segment: 一個Partition被切分爲多個Segment,每一個Segment包含索引文件和數據文件。
  • Message:kafka裏最基本消息單元。

一個kafka集羣能夠由多個broker組成,每一個broker是一個節點,你建立一個topic,這個topic能夠劃分爲多個partition,每一個partition能夠存儲在不一樣的broker上,每一個partition存放一部分數據。

6.2 RocketMQ

在 RocketMQ 4.5 版本以前,RocketMQ 只有 Master/Slave 一種部署方式來實現高可用。
一組 Broker 中有一個 Master,有零到多個 Slave,Slave 經過同步複製或異步複製方式去同步 Master 的數據。Master/Slave 部署模式,提供了必定的高可用性。

上面主從高可用架構有一個缺點:
主節點掛了後須要人爲的進行重啓或者切換。爲了解決這個問題,後續引入了raft,用raft協議來完成自動選主。RocketMQ的DLedger 就是一個基於 raft 協議的 commitlog 存儲庫,也是 RocketMQ 實現新的高可用多副本架構的關鍵。

還能夠多master多slave部署,防止單點故障。

5、參考

相關文章
相關標籤/搜索