Kafka技術知識總結之五——Kafka的高可用性

接上篇《Kafka技術知識總結之四——Kafka 再均衡》

五. 消息中間件的高可用性

5.1 消息中間件的高可用性

Kafka 實現高可用性的方式是進行 replication。對於 kafka,如果沒有提供高可用性機制,一旦一個或多個 Broker 宕機,則宕機期間其上所有 Partition 都無法繼續提供服務。若該 Broker永遠不能再恢復,那麼所有的數據也就將丟失,這是不可容忍的。所以 kafka 高可用性的設計也是進行 Replication。
Replica 的分佈:爲了儘量做好負載均衡和容錯能力,需要將同一個 Partition 的 Replica 儘量分散到不同的機器。
Replica 的同步:當有很多 Replica 的時候,一般來說,對於這種情況有兩個處理方法:

  • 同步複製:當 producer 向所有的 Replica 寫入成功消息後才返回。一致性得到保障,但是延遲太高,吞吐率降低。
  • 異步複製:所有的 Replica 選取一個一個 leader,producer 向 leader 寫入成功即返回(即生產者參數 acks = 1)。leader 負責將消息同步給其他的所有 Replica。但是消息同步一致性得不到保證,但是保證了快速的響應。

而 kafka 選取了一個折中的方式:ISR (in-sync replicas)。producer 每次發送消息,將消息發送給 leader,leader 將消息同步給他“信任”的“小弟們”就算成功,巧妙的均衡了確保數據不丟失以及吞吐率。具體步驟:

  1. 在所有的 Replica 中,leader 會維護一個與其基本保持同步的 Replica 列表,該列表稱爲ISR (in-sync Replica);每個 Partition 都會有一個 ISR,而且是由 leader 動態維護。
  2. 如果一個 replica 落後 leader 太多,leader 會將其剔除。如果另外的 replica 跟上腳步,leader 會將其加入。
  3. 同步:leader 向 ISR 中的所有 replica 同步消息,當收到所有 ISR 中 replica 的 ack 之後,leader 才 commit。
  4. 異步:收到同步消息的 ISR 中的 replica,異步將消息同步給 ISR 集合外的 replica。

參考地址:
《Kafka消息投遞語義-消息不丟失,不重複,不丟不重》
《消息隊列面試題要點》

  • 問題 1:使用 Kafka 的時候,你們怎麼保證投遞出去的消息一定不會丟失?
  • 問題 2:你們怎麼保證投遞出去的消息只有一條且僅僅一條,不會出現重複的數據?

上述問題換一種問法,可以翻譯爲**如何保證消息隊列的冪等性?**這個問題可以認爲是消息隊列領域的基本問題。這個問題的回答可以根據具體的業務場景來答,沒有固定的答案。

無論是哪種消息隊列,造成重複消費原因其實都是類似的。正常情況下,消費者在消費消息的時候,消費完畢後,會發送一個確認消息給消息隊列,消息隊列就知道該消息被消費了,就會將該消息從消息隊列中刪除。只是不同的消息隊列發出的確認消息形式不同(例如 RabbitMQ 是發送一個 ACK 確認消息,RocketMQ 是返回一個 CONSUME_SUCCESS 成功標誌),kafka 是通過**提交 offset 的方式**讓消息隊列知道自己已經消費過了。

造成重複消費的原因,就是因爲網絡傳輸等等故障,確認信息沒有傳送到消息隊列,導致消息隊列不知道自己已經消費過該消息了,再次將消息分發給其他的消費者。

注:關於重複消費,還有部分與第七章 Kafka 再均衡相關的內容。
參考地址:《記一次線上kafka一直rebalance故障》

如何解決?這個問題針對業務場景來答,分以下三種情況:

  • 實際使用方案:準備一個第三方介質,來做消費記錄
    • 以 redis 爲例,給消息分配一個全局 ID,只要消費過該消息,將 <id,message>以 K-V 形式寫入 redis。消費者開始消費前,先去 redis 中查詢有沒有消費記錄即可。
  • 數據庫主鍵:拿到這個消息做數據庫的 insert 操作,那就容易了,給這個消息做一個唯一的主鍵,那麼就算出現重複消費的情況,就會導致主鍵衝突,避免數據庫出現髒數據。
  • Redis Set 操作:拿到這個消息做 redis 的 set 的操作,那就容易了,不用解決,set 操作無論幾次結果都是一樣的,因爲 set 操作本來就是冪等操作。

5.2 Kafka 消息投遞語義

kafka 有三種消息投遞語義:

  • At most Once:最多一次;消息不會重複,但可能丟失;
  • At least Once:最少一次;消息不會丟失,但可能重複;
  • Exactly Once:最佳情況,只且消費一次;消息不會重複,也不會丟失;

整體的消息投遞語義由生產者、消費者兩端同時保證。

5.3 Producer 生產者端

Producer 端保證消息投遞重複性,是通過 Producer 的 acks 參數Broker 端的 min.insync.replicas 參數決定的。

Producer 端的 acks 參數值信息如下:

  • acks = 0:不等待任何響應的發送消息;
  • acks = 1leader 分片寫消息成功,就返回響應給生產者;
  • acks = -1(all):要求 ISR 集合至少兩個 Replica,而且必須全部 Replica 都寫入成功,才返回響應給 Producer;
    • 無論 ISR 少於兩個 Replica,或者不是全部 Replica 寫入成功,都會拋出異常;

前面 Producer 的 acks = 1 可以保證寫入 Leader 副本,對大部分情況沒有問題。但如果剛剛一條消息寫入 Leader,還沒有把這條消息同步給其他 Replica,Leader 就掛了,那麼這條消息也就丟失了。所以如果保證消息的完全投遞,還是需要令 acks = all;

5.4 Broker 節點端

首先上面說到,爲了配合 Producer acks 參數爲 all,需要令 Broker 端參數 min.insync.replicas = 2;
min.insync.replicas 參數是用來配合 Producer acks 參數的。因爲如果 acks 設置爲 all,但某個 Topic 只有 leader 一個 Replica(或者某個 Kafka 集羣中由於同步很慢,導致所有 follower 全部被剔除 ISR 集合),這樣 acks = -1 就演變成了 acks = 1。
所以需要 Broker 端設置 min.insync.replicas 參數:當參數值爲 2 時,如果副本數小於 2 個,會拋出異常。

注:然而在筆者的使用環境中,訂閱是 Kafka 主要的使用場景之一,方式是對於想要訂閱的某個 Topic,每個用戶創建並獨享一個不會重複的消費組。所以這樣的情況下,環境下的 min.insync.replicas 只能等於 1;

除此之外,broker 端還有一個需要注意的參數 unclean.leader.election.enable。該參數爲 true 的時候,表示在 leader 下線的時候,可以從非 ISR 集合中選舉出新的 Leader。這樣的話可能會造成數據的丟失。所以如果需要在 Broker 端的 unclean.leader.election.enable 設置爲 false。

5.5 Consumer 消費者端

Consumer 端比較麻煩,原因是需要考慮到某個 Consumer 宕機後,同 Consumer Group 會發生負載均衡,同 Group 其他的 Consumer 會重新接管並繼續消費。

假設兩種場景:

第一個場景,Consumer 先提交 offset,再處理消息。代碼如下:

List<String> messages = consumer.poll();
consumer.commitOffset();
processMsg(messages);

這種情形下,提交 offset 成功,但處理消息失敗,同時當前 Consumer 宕機,這時候發生負載均衡,其他 Consumer 從已經提交的 offset 之後繼續消費。這樣的情況保證了 at most once 的消費語義,當然也可能會丟消息。

第二個場景,Consumer 先處理消息,再提交 offset。代碼如下:

List<String> messages = consumer.poll();
processMsg(messages);
consumer.commitOffset();

這種情形下,消息處理成功,提交 offset 失敗,同時當前 Consumer 宕機,這時候發生負載均衡,其他 Consumer 依舊從同樣的 offset 拉取消息消費。這樣的情況保證了 at least once 的消費語義,可能會重複消費消息。

上述機制的保證都不是直接一個配置可以解決的,而是 Consumer 端代碼的處理先後順序問題完成的。

注:關於 Kafka 解耦作用的思考:
註冊中心可以將服務於服務之間解耦,但 Kafka 也可以通過相同的 topic 的消息傳遞實現業務的解耦。這兩種形式都可以實現解耦,但筆者個人理解:

  • 註冊中心通過請求 -> 響應的模式,等待其他服務處理結果完畢之後的響應;
  • Kafka 的將消息從生產者投遞,消費者接收,但消費者的消費結果通常生產者並不需要的,生產者只需要確保將消息投遞到 Kafka Broker 節點即可。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章