一文讀懂訊息佇列一些設計

春哥大魔王發表於2019-06-04

高可用

常用的訊息佇列的高可用是怎麼設計的呢?

訊息佇列一般都有一個nameserver服務,用來檢測broker是否存活,或者處理能力上是否存在延遲。這樣在傳送訊息時就可以規避將訊息傳送到當機的broker上,也避免因為網路等原因訊息處理失敗。

那麼針對於以上兩種情況,訊息佇列如何保證高可用方案的呢?

多副本

每個topic可以設定幾個partition,每個partition負責儲存一部分資料。kafka的broker叢集中,每臺機器儲存一些partition,存放一部分topic資料,這就實現了topic資料分佈在一個broker叢集上。

任何一個分散式系統,內部都有一套多副本冗餘機制,多副本冗餘是任何一個分散式系統具備的基本能力。

kafka中每個partition都有多個副本,其中一個副本是leader,其他副本為follower,leader和follower分佈在不同機器上。leader對外統一提供寫服務,leader接收到訊息後follower副本會不停的和leader通訊,嘗試拉去最新資料,並持久化到本地磁碟。

說到這裡不得不說下ISR,也就是保持同步的副本,表示了和leader始終保持同步的follower有哪些。

比如follower由於fullgc造成自己卡頓,使得無法及時從leader拉取資料,會導致這個follower資料比leader落後很多。
只要follower一直和leader保持同步關係,他們就處於同步關係。

每個partition都有一個ISR,這個ISR一定有leader,因為leader的資料永遠是最新的,然後就是和leader保持同步的follower,也會在ISR裡面。

kafka中有個acks引數。是在producer裡面設定的,也就是客戶端設定的。

在向fafka叢集寫資料時,可以設定這個acks引數,這個引數值有:0,1,all。

0:
意思是proucer在客戶端只要把訊息傳送出去,不管訊息有沒有在partition leader上落盤就不管了。就認為訊息傳送成功了。

1:
意思是producer生產的訊息要確保partition leader寫入本地磁碟,就認為成功了,而不管follower有沒有同步這條訊息。
當然這個是kafka的預設設定。

all:
意思是partition leader接收到訊息後,持久化到本地,還要求ISR列表中跟leader保持同步的那些follower要把訊息持久了,才算寫入成功。一般要求acks=all時,必須isr列表裡面有兩個以上的副本配合使用,起碼每個leader有一個follower才行。

當broker回覆客戶端訊息沒有寫入成功時,需要客戶端進行訊息重發。

重試

訊息傳送時,一般存在這樣的方法:

for(; times < timesTotal; times++){
// send message
}

這裡是client傳送訊息時決定的重試次數,預設值為3。重試可以提高訊息傳送的成功率。

訊息傳送

預設的訊息傳送採用對訊息佇列進行取模,確定佇列。
其他的方式比如輪訓方式等。

Kafka 有兩個預設的分配策略:

  • Range:該策略會把主題的若干個連續的分割槽分配給消費者。
  • RoundRobin:該策略把主題的所有分割槽逐個分配給消費者。

消費者

消費者向kafka訂閱topic,並從topic上接收訊息。

消費者屬於消費者組,一個消費組的消費組訂閱的是同一個topic,每個消費者接收topic一個partition的訊息。

kafka預設的規則中,每個分割槽只能被同一個消費組裡面的一個消費者消費。

1個消費者接收4個分割槽的訊息:
一文讀懂訊息佇列一些設計

2個消費者接收4個分割槽的訊息:
一文讀懂訊息佇列一些設計

4個消費者接收4個分割槽的訊息:
一文讀懂訊息佇列一些設計

5個消費者接收4個分割槽的訊息:
一文讀懂訊息佇列一些設計

如果消費者群組的消費者超過主題的分割槽數量,那麼有一部分消費者就會被閒置,不會接收到任何訊息。

兩個消費者群組對應一個主題:
一文讀懂訊息佇列一些設計

當一個消費者被關閉或發生崩潰時,它就離開群組,原本由它讀取的分割槽將由群組裡的其他消費者來讀取。分割槽的所有權從一個消費者轉移到另一個消費者,這樣的行為被稱為再均衡。在再均衡期間,消費者無法讀取訊息,造成整個群組一小段時間的不可用。

通過上面消費者例項數量變化思考一個問題。在消費者機器重啟過程中,存在partition和消費者重新建立聯絡的情況,比如最開始有4個消費者,由於並行重啟消費者,可能存在一段時間消費者數量變為2個,當重啟完成後消費者數量有變成了4個。

這個過程存在訊息可能重複傳送到同一個消費者消費的情況,造成重複消費,如果是對訊息重複敏感的應用場景,我司自研的訊息佇列元件會提供一個選項,訊息在分割槽進行主動積壓,預設積壓30s等待消費者重啟完成,達到穩定的消費者數量。

消費者通過向被指派為群組協調器的 broker 傳送心跳來維持它們和群組的從屬關係以及它們對分割槽的所有權關係。消費者會在輪訓訊息或提交偏移量時傳送心跳。如果消費者停止傳送心跳的時間足夠長,會話就會過期,群組協調器認為它已經死亡,就會觸發一次再均衡。

如果一個消費者發生崩潰,並停止讀取訊息,群組協調器會等待幾秒鐘,確認它死亡了才會觸發再均衡。所以上面的延遲是由於再平衡期間不可用造成的。

當消費者要加入群組時,它會向群組協調器傳送一個 JoinGroup 請求。

第一個加入群組的消費者將成為"群主"。群主從協調器那裡獲得群組的成員列表,並負責給每一個消費者分配分割槽。
分配完畢之後,群主把分配情況列表傳送給群組協調器,協調器再把這些資訊傳送給所有消費者。
每個消費者只能看到自己的分配情況。這個過程會在每次再均衡時重複發生。

訊息消費

kafka消費者有自己消費偏移量,這個偏移量是從kafka中讀取的量,和kafka提交的偏移量不一樣。消費者一般需要第一次和rebalance的時候需要根據提交的偏移量來獲取資料,剩下的時候根據自己本地的偏移量來獲取。

當消費者使用了自動提交模式,當還沒有提交的時候,有消費者加入或者移除,傳送rebalance,再次消費時,消費者根據提交偏移量進行,可能產生重複消費資料。

選舉設計

先說分割槽leader的選舉,就是當ISR中的leader副本掛了,再重新選舉一個過程。

kafka中的選舉大致可以分為三大類:

  • 控制器選舉
  • 分割槽leader選舉
  • 消費組相關選舉

控制器選舉:
kafka叢集中有一個或多個broker,其中一個broker會被選舉為kafka controller,負責管理整個叢集中所有分割槽和副本狀態。當檢測到某個分割槽的leader副本出現故障,controller負責為該分割槽選舉新的leader副本。
如果檢測到某個分割槽ISR集合發生變化時,控制器負責通知所有的broker更新後設資料資訊。
kafka controller的實現是依賴於zk實現的,哪個broker成功在zk的/controller臨時節點建立成功,就成為kafka controller。

分割槽leader選舉:
在topic下增加分割槽或者分割槽下線時,都需要執行leader選舉。
基本思路是按照AR集合中副本順序查詢第一個存活的副本,並且這個副本在ISR集合中。

消費者相關選舉:
消費組協調器需要為消費組內的消費者選擇一個消費組leader,這個選舉演算法比較簡單。
如果消費組內沒有leader,那麼第一個加入消費組的消費者成為組leader。
如果由於某種原因leader消費者退出消費組,需要重新選舉leader,消費者協調器維護一個map結構,key為消費組id,value為消費者元資訊,預設選擇第一個key作為leader。

更多內容:
一文讀懂訊息佇列一些設計

相關文章