RabbitMQ VS Apache Kafka (八)—— Kafka消息路由原語與路由保證

Kafka的路由保證主要基於以下實現:

  • 消息持久化:一旦消息存儲到主題中,則確認不會出現丟失消息的問題

  • 消息確認:Kafka(或者是Zookeeper)與發佈者、訂閱者之間的消息通信

消息批量

Kafka與RabbitMQ一個最大的不同之處在於Kafka支持在消息發送和處理時批量操作。當然,RabbitMQ也可以實現類似批量的操作:

  • 每多少條消息暫停一次直至收到所有的消息確認。

  • 消費者設置預取閾值,通過multiple標識打包多個發送消息確認。

但消息並不是以批量的形式進行發送,而是通過消息流的持續發送並配合multiple標識打包多個發送消息確認,這其實是TCP所做的。

而Kafka則明確支持消息批量,使用批量的原因在於性能但這之間也是有一個權衡。RabbitMQ也會面對同樣的權衡,比如,在途未確認消息越多,當失敗發生時,也就意味着越多的消息重複或者處理重複。

在消費端,Kafka可以更有效的執行批量處理,因爲Kafka是通過分區實現併發而不是通過競爭消費者的形式進行的。每一個分區對應一個消費者,所以大批量的使用並不影響工作的分佈。但對於RabbitMQ來說,如果想實現類似效果,我們需要通過已經廢棄的“拉”API來讀取,但導致的結果很就是各競爭消費者之間的負載不均衡,RabbitMQ的設計初衷並適用於消息的批量處理。

持久化原語

日誌複製

Kakfa提供了日誌分區級別的主從(領導者-追隨者)架構以實現容錯。每一個分區領導者都可以擁有多個追隨者,當領導者所在的服務節點宕機,Kafka會從剩餘的追求者選擇一個提升爲領導者。當服務短暫中斷後,Kafka允許生產者和管理者來同通過一定的配置來確保已存儲的消息不會丟失。當然,消息持久化時間跨度越久,Kafka的寫延遲就會越長。

此外Kafka還提供了同步複製的概念(ISR),每一個副本都可以與領導者保持異步或同步狀態,保持同步的含義是數據與領導者保持一致,誤差時間不超過一定的時間週期(默認10秒)。當副本落後於領導者,那麼副本就處於異步狀態,導致異步狀態的主要原因可能因爲網絡延遲,或者因爲副本所在宿主主機發生故障。只有當領導者出現宕機掉線,且沒有其他同步副本在線的場景纔會導致信息丟失。關於這一點,我們後續章節中討論。

消息確認與偏移量跟蹤

生產者消息確認

當生產者發送消息給Kafka,它會通過Acks屬性告訴Kafka代理期待哪種類型的消息確認。

  • 無需確認,Acks=0

  • 領導者需持久化當前消息,Acks=1

  • 領導者和所有副本都需持久化當前消息,Acks=All

和RabbitMQ一樣,採用消息確認可能會出現消息重複的問題。比如,Kafka代理收到生產者發送的消息,並對其進行了持久化操作,當Kafka準備發送確認消息給生產者,發送過程中出現了代理宕機或者網絡中斷,導致生產者無法收到消息確認,進而導致生產者必須再次重新發送消息。

但Kafka採用了一種的消息去重方式來避免了消息重複的問題,儘管這一方式有一定的侷限性:

  • enable.idempotence設置爲true
  • max.in.flight.requests.per.connection限定爲5或者更少
  • retries標識設置爲1或者更大
  • acks設置爲all

因此,如果批量消息個數超過5,或者acks=0或者1,那麼你是無法使用Kafka的消息去重特性的。

消費者偏移量跟蹤

消費者需要保存各自在Kafka日誌中的偏移量,這樣,即便消費者出現掉線或者錯誤時,新的消費者可以從失敗的地方繼續執行。偏移量通常被存儲在Zookeeper中或者其他Kafka主題中。

一旦消費者需要從一個分區中批量讀取消息,它可以採取如下方式來保存偏移量:

  • 定時自動提交:客戶端隨着消息的處理定時提交偏移量,記錄已處理消息的位置。從開發者角度來看,這種處理方式簡單適用,並且性能良好。但當消息者出現錯誤或者失敗時,增加重複路由的風險,原因在於消費者可能已經完成了批量消息的處理,但對應的消息偏移量卻因消費者出現宕機或者失敗導致提交失敗。

  • 消息收到時主動提交:這種模式與之前我們討論的at-most-once路由語義是一樣,也就是說無論消息者是否會出現失敗的情況,消息都將不會被處理兩次,儘管有可能出現消息不被處理的情況。假設,有10條消息需要處理,但消費者在處理第5條消息時出現失敗,那麼實際情況就是隻有4條消息被處理,剩餘的其他消息將會被跳過執行,下一個消費者將會從本次批量之後的位置開始處理。

  • 結束之後主動提交:在所有的消息都被處理完畢,再執行主動提交,這種場景對應之前我們討論的at-least-once路由語義,不管消費者是否會出現失敗的場景,在這種模式下可以確保沒有消息遺漏,儘管一條消息可能會被處理多次。比如,還是上面的例子,有10條消息需要處理,在第5條消息處理時,消費者失敗,那麼這10條消息會被重新讀取並再次處理,那麼之前已經被處理的4條記錄會被處理兩次。

  • 一次一提交:這種避免了消息重複,但嚴重影響吞吐效率。

僅一次路由保證僅限於Kafka Streams,具體機制由Java庫實現,如果使用Java語言開發,建議可以研究一下。僅一次的主要問題在於單個消息處理的輸出與偏移量的保存需要在一個事務內完成。比如,如果消息處理的輸出是發送郵件,那麼我們就不能使用僅一次的消息處理業務場景。如果我們發送了郵件,但消費者執行偏移量保存的時候出現了錯誤,那麼接下來新的消費者就會再次發送同樣的郵件。

如果當前消息的輸出作爲另一個消息的輸入,Kafka Streams僅一次的消息處理模式就有了實際的用武之地。在這樣的業務場景中,我們可以通過Kafka的事務功能來完成消息的寫入和偏移量的保存,即便消費者失敗,我們也可以確保全部成功或者全部失敗,不會導致重複出現消息輸出。

事務與隔離級別

上面已經討論,Kafka的事務功能主要用於“讀-處理-寫”的場景,事務可以跨多個主題和分區執行,生產者負責打開事務,執行批量消息寫入,然後提交事務。

當消費者使用默認的read uncommited隔離級別時,無論事務是否提交或者中止,消費者都可以看到所有消息。當消費者使用read commited隔離級別時,消費者是無法讀取到未提交或者中止的事務消息,只能處理已提交的事務消息。

或許,這裏你會產生疑問,使用read committed隔離級別會不會影響路由順序保證?答案是不會。消費者仍舊會以正確的順序來讀取消息,並且如果目標消息未提交,消費者會停止處理等待消息提交。因此,事務的使用會阻塞read committed消費者。LSO(Last Stable Offset)是第一打開事務偏移量之前的偏移量,實際運用中,由LSO負責標識read committed消費者可以讀取的消息邊界。

總結

RabbitMQ和Kafka均提供了可靠的、可持久化的消息路由,兩者均提供了相對可靠的路由保證,然而,就我個人而言,考慮其冪等性,Kafka仍具一定的優勢,即便實際運用中可能出現偏移量混亂,但這也不意味着消息永久丟失,事實上,消息仍舊保留着在分區中。

  • 兩者均提供了最多一次和最少一次的路由保證
  • 兩者均提供消息複製功能
  • 在系統吞吐效率及消息重複方面,兩者均實現了一種均衡。
  • 針對未確認在途消息數量,兩者均有一定的控制
  • 兩者均提供了消息路由順序保證
  • Kafka提供事務支持
  • Kafka支持消息回溯,而RabbitMQ則會導致消息丟失
  • Kafka提供了消息批處理,而RabbitMQ使用了競爭消費的推送模型,不適用於消息批量處理。
原文鏈接

https://jack-vanlightly.com/blog/2017/12/15/rabbitmq-vs-kafka-part-4-message-delivery-semantics-and-guarantees

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