您當前的位置:首頁 > 體育

訊息佇列專欄之RocketMQ和Kafka

作者:由 Winston 發表于 體育時間:2022-03-15

本文為極客時間《訊息佇列高手》學習筆記,為了按照課程的安排進行學習,這裡只是對RabbitMQ簡單說明,後續會專門再出一下相關的文章進行詳細學習。

不論是挑選訊息佇列,還是挑選其他的開源技術棧,我們實際上都需要考慮系統的高可靠、高可用以及高效能三個點,對應到訊息佇列就是:

高可靠:訊息的傳遞不能丟失;

高可用:支援叢集,不會出現單點問題;

高效能:能夠滿足大多數場景效能要求,具備一定的通用性;

1。RocketMQ

RocketMQ對線上業務的響應時延做了很多最佳化,大多數情況下可以做到毫秒級的響應,如果應用場景很在意響應時延可以選用RocketMQ,且每秒鐘的處理請求可以在幾十萬量級。

1。1 釋出訂閱模型

釋出訂閱模型(Publish-Subscribe Pattern)中,訊息的傳送方成為釋出者Publisher,訊息的接收方成為訂閱者Subscriber,服務端存放訊息的容器稱為主題Topic。釋出者將訊息傳送到主題中,訂閱者在接收訊息之前需要先訂閱主題。訂閱既是一個動作,也可以認為是主題在消費時的一個邏輯副本,每份訂閱中訂閱者都可以接收到主題的所有訊息。

1。2 RocketMQ的訊息模型

RocketMQ使用的就是釋出訂閱模型,但是也由佇列QUEUE這個概念。

幾乎所有的訊息佇列產品都使用“請求-確認”機制確保訊息不會在傳遞過程中由於網路或者伺服器故障導致丟失。生產端先將訊息傳送到服務端Broker,服務端在收到訊息並將訊息寫入主題或者佇列中後,會給生產者傳送確認的響應。

如果生產者沒有收到服務端返回的確認或者失敗的訊息,則會重新發送訊息;在消費端,消費者在收到訊息並且完成自己的消費業務邏輯後,也會給服務端傳送訊息消費成功的確認,服務端只有收到消費確認後,才認為一條訊息被成功消費,否則,它會給消費者重新發送訊息,直到收到對應的消費成功確認。

但是為了保證訊息的可靠性,就必須先保證訊息的有序性,及前面的訊息被成功消費之前,下一條訊息是不能被消費的,否則就會出現訊息空洞,違背了有序性原則。

也就是說,每個主題在任意時刻,至多隻能有一個消費者例項在進行消費,那就沒法透過水平擴充套件消費者的數量來提升消費端總體的消費效能。為了解決這個問題,RocketMQ 在主題下面增加了佇列的概念。

每個主題包含多個佇列,透過多個佇列來實現多例項並行生產和消費。需要注意的是,RocketMQ 只在佇列上保證訊息的有序性,主題層面是無法保證訊息的嚴格順序的。

RocketMQ 中,訂閱者的概念是透過消費組(Consumer Group)來體現的。每個消費組都消費主題中一份完整的訊息,不同消費組之間消費進度彼此不受影響,也就是說,一條訊息被 Consumer Group1 消費過,也會再給 Consumer Group2 消費。

消費組中包含多個消費者,同一個組內的消費者是競爭消費的關係,每個消費者負責消費組內的一部分訊息。如果一條訊息被消費者 Consumer1 消費了,那同組的其他消費者就不會再收到這條訊息。

在 Topic 的消費過程中,由於訊息需要被不同的組進行多次消費,所以消費完的訊息並不會立即被刪除,這就需要 RocketMQ 為每個消費組在每個佇列上維護一個消費位置(Consumer Offset),這個位置之前的訊息都被消費過,之後的訊息都沒有被消費過,每成功消費一條訊息,消費位置就加一。這個消費位置是非常重要的概念,我們在使用訊息佇列的時候,丟訊息的原因大多是由於消費位置處理不當導致的。

訊息佇列專欄之RocketMQ和Kafka

1。2。1 消費者組與佇列的關係

消費者組和佇列數並沒有關係,佇列數量可以根據資料量和消費速度來合理配置,RocketMQ和Kafka都可以支援水平擴容佇列數量,但是都需要手動操作。

從主題中的所有佇列中取出訊息給所有消費者進行消費,訊息只能被消費組中的一個執行緒進行消費,有點類似執行緒池的形式,工作執行緒消費來自不同佇列的訊息,感覺這也是RocketMQ低延時的原因,不同佇列中的訊息可以同時被消費,並且消費組的執行緒也可以併發的消費不同的訊息。

1。2。2 producer和佇列

producer會往所有佇列傳送訊息,但不是“同一條訊息每個佇列都發一次”,每條訊息只會往某個佇列裡面傳送一次,而不是發往所有的佇列。至於說訊息最終寫入哪個佇列,主要是決定於當前使用的演算法,一般來說預設是輪詢,也可以是取模之類的演算法。

對於一個消費組,每個佇列上只能序列消費,因此說,主題並不能保證訊息的有序,只有佇列中的訊息才是有序的。多個佇列加一起就是並行消費了,並行度就是佇列數量,佇列數量越多並行度越大,所以水平擴充套件可以提升消費效能。

1。2。3 消費位置

每個佇列都會維護一個消費位置offset,記錄這個消費組在這個佇列上消費訊息的進度。因此,消費位置並非全域性唯一。

由於每個主題中的一個佇列都會被多個消費組進行消費,為此需要為每個消費組的消費的不同佇列設定一個下標(每個消費組可以一直消費佇列中的訊息,無需等待其他消費組的確認),主題中的佇列訊息是有序的,為此需要等到所有消費組對此條訊息進行確認,才能從佇列中移除,感覺每個消費組的佇列下標,可以一個佇列維護一個CurrentHashMap來為此每個消費組的下標,這樣的話可以防止鎖的競爭。

伺服器端可以在該消費組中對每個佇列實時維護消費位點,並且持久化到磁碟來應對機器宕機後重啟的恢復,同時針對磁碟故障也需要提供該消費位點的副本機制來保證。每個queue上的資料被每個消費組消費完應該是不會被立刻刪除的,會有一個過期時間,過期後刪除,沒有刪除的資料還可以被新接進來的消費組消費。offset相當於陣列下標,broker記錄每個消費組在queue上的offset,每個queue上的不同的group的消費者之間不會有影響。consumer A消費失敗了offset不會遞增,等待訊息重發,而consumer B繼續向後消費。

總的來說,一個主題Topic下面有多個queue,目的是為了負載均衡,假設我這邊一個主題對應8個queue,那麼消費端如果有兩個例項,那就每個例項消費4個佇列的資料。當生產者傳送訊息的時候,會落在8個queue上的某一個,消費位置由另外一個元件管理,來一個新的消費者組,就在記憶體和檔案中記錄消費的位置,如果新增,就在建立新的記錄。

2。Kafka

Kafka是與周邊生態系統相容性最好的沒有之一,尤其是在大資料和流計算領域,幾乎所有的相關軟體都會優先支援Kafka。

Kafka使用Scala和Java語言開發,設計上大量使用批次和非同步思想,這種設計使得kafka能夠做到超高的效能,尤其是非同步收發的效能,是三者中最好的,每秒鐘的處理訊息也在幾十萬條左右。

Kafka這種非同步批次的設計帶來的問題是:它的同步收發訊息的響應時延比較高,因為當客戶端傳送一條訊息的時候,Kafka並不會立即傳送出去,而是要一會攢一批再發送,在它的Broker中,很多地方都會使用這種“先攢一波再一起處理”的設計。當你的業務場景中,每秒鐘訊息數量沒有那麼多的時候,Kafka的時延反而會比較高。所以,Kafka不太適合線上業務場景。

Kafka認為在某些情況下,瓶頸實際上不是CPU或者磁碟,而是網路寬頻。而其認為的高效壓縮,即把多個訊息壓縮在一起,而不是分別壓縮每個訊息,然後以這種形式傳送到服務端。這批訊息將以壓縮形式寫入,並保持壓縮在日誌中,並且僅由使用者解壓縮。官網相關描述連結為:http://kafka。apache。org/documentation/#majorde。

Kafka的訊息模型與RocketMQ是完全一樣的,但是在Kafka中,不再成為佇列,成為分割槽partition。

3 思考題:不做嚴格順序要求,是否可以實現單個佇列的並行消費

問題:剛剛我在介紹RocketMQ的訊息模型時講過,在消費的時候,為了保證訊息的不丟失和嚴格順序,每個佇列只能序列消費,無法做到併發,否則會出現消費空洞的問題。那如果放寬一下限制,不要求嚴格順序,能否做到單個佇列的並行消費呢?如果可以,該如何實現?歡迎在留言區與我分享討論。

感覺佇列可以維護一個全域性的下標,消費佇列時,使用CAS進行下標的獲取,由於不保證你哼訊息消費的有序,這樣的話可以併發的消費訊息,由於有全域性下標,不會出現獲取佇列的空洞訊息。

標簽: 佇列  消費  訊息  RocketMQ  每個