Kafka爲什麼這麼快?

點擊上方“碼農突圍”,馬上關注

這裏是碼農充電第一站,回覆“666”,獲取一份專屬大禮包
真愛,請設置“星標”或點個“在看

在過去的幾年裏,軟件架構領域發生了巨大的變化。人們不再認爲所有的系統都應該共享一個數據庫。微服務、事件驅動架構和CQRS(命令查詢的責任分離 Command Query Responsibility Segregation)是構建當代業務應用程序的主要工具。除此以外,物聯網、移動設備和可穿戴設備的普及,進一步對系統的近實時能力提出了挑戰。

首先讓我們對“快”這個詞達成共識,這個詞是多方面的、複雜的、高度模糊的。一種解釋是把”延遲、吞吐量和抖動“作爲對“快”的衡量指標。還有,比如工業應用領域,行業本身設置了對於“快”的規範和期望。所以,“快”在很大程度上取決於你的參照體系是什麼。

Apache Kafka以犧牲延遲和抖動爲代價優化了吞吐量,但並沒有犧牲,比如持久性、嚴格的記錄有序性和至少一次的分發語義。當有人說“Kafka速度很快”,並假設他們至少有一定的能力時,你可以認爲他們指的是Kafka在短時間內分發大量記錄的能力。

Kafka誕生於LinkedIn,當時LinkedIn需要高效地傳遞大量信息,相當於每小時傳輸數TB的數據量。在當時,消息傳播的延遲被認爲是可以接受的。畢竟,LinkedIn不是一家從事高頻交易的金融機構,也不是一個在確定期限內運行的工業控制系統。Kafka可用於近實時系統。

注意:“實時”並不意味着“快”,它的意思是“可預測的”。具體來說,實時意味着完成一個動作具有時間限制,也就是最後期限。如果一個系統不能滿足這個要求,它就不能被歸類爲”實時系統“。能夠容忍一定範圍內延遲的系統被稱爲“近實時”系統。從吞吐量的角度來說,實時系統通常比近實時或非實時系統要慢。

Kafka在速度上有兩個重要的方面,需要單獨討論。第一個是與客戶端與服務端之間的低效率實現有關。第二個源自於流處理的並行性。

服務端優化

日誌的存儲

Kafka利用分段、追加日誌的方式,在很大程度上將讀寫限制爲順序I/O(sequential I/O),這在大多數的存儲介質上都很快。人們普遍錯誤地認爲硬盤很慢。然而,存儲介質的性能,很大程度上依賴於數據被訪問的模式。同樣在一塊普通的7200 RPM SATA硬盤上,隨機I/O(random I/O)與順序I/O相比,隨機I/O的性能要比順序I/O慢3到4個數量級。此外,現代的操作系統提供了預先讀和延遲寫的技術,這些技術可以以塊爲單位,預先讀取大量數據,並將較小的邏輯寫操作合併成較大的物理寫操作。因此,順序I/O和隨機I/O之間的性能差異在閃存和其他固態非易失性介質中仍然很明顯,不過它們在旋轉存儲,比如固態硬盤中的性能差異就沒有那麼明顯。

記錄的批處理

順序I/O在大多數存儲介質上都非常快,可以與網絡I/O的最高性能相媲美。在實踐中,這意味着一個設計良好的日誌持久化層能跟上網絡的讀寫速度。事實上,Kafka的性能瓶頸通常並不在硬盤上,而是網絡。因此,除了操作系統提供的批處理外,Kafka的客戶端和服務端會在一個批處理中積累多個記錄——包括讀寫記錄,然後在通過網絡發送出去。記錄的批處理可以緩解網絡往返的開銷,使用更大的數據包,提高帶寬的效率。

批量壓縮

當啓用壓縮時,對批處理的影響特別明顯,因爲隨着數據大小的增加,壓縮通常會變得更有效。特別是在使用基於文本的格式時,比如JSON,壓縮的效果會非常明顯,壓縮比通常在5x到7x之間。此外,記錄的批處理主要作爲一個客戶端操作,負載在傳遞的過程中,不僅對網絡帶寬有積極影響,而且對服務端的磁盤I/O利用率也有積極影響。

便宜的消費者

不同於傳統的消息隊列模型,當消息被消費時會刪除消息(會導致隨機I/O),Kafka不會在消息被消費後刪除它們——相反,它會獨立地跟蹤每個消費者組的偏移量。可以參考Kafka的內部主題__consumer_offsets瞭解更多。同樣,由於只是追加操作,所以速度很快。消息的大小在後臺被進一步減少(使用Kafka的壓縮特性),只保留任何給定消費者組的最後已知偏移量。

將此模型與傳統的消息模型進行對比,後者通常提供幾種不同的消息分發拓撲。一種是消息隊列——用於點對點消息傳遞的持久化傳輸,沒有點對多點功能。另一種是發佈訂閱主題允許點對多點消息通信,但這樣做的代價是持久性。在傳統消息隊列模型中實現持久化的點對多點消息通信模型需要爲每個有狀態的使用者維護專用消息隊列。這將放大讀寫的消耗。消息生產者被迫將消息寫入多個消息隊列中。另外一種選擇是使用扇出中繼,扇出中繼可以消費來自一個隊列中的記錄,並將記錄寫入其他多個隊列中,但這隻會將延遲放大點。並且,一些消費者正在服務端上生成負載——讀和寫I/O的混合,既有順序的,也有隨機的。

Kafka中的消費者是“便宜的”,只要他們不改變日誌文件(只有生產者或Kafka的內部進程被允許這樣做)。這意味着大量消費者可以併發地從同一主題讀取數據,而不會使集羣崩潰。添加一個消費者仍然有一些成本,但主要是順序讀取夾雜很少的順序寫入。因此,在一個多樣化的消費者系統中,看到一個主題被共享是相當正常的。

未刷新的緩衝寫操作

Kafka性能的另一個基本原因是,一個值得進一步研究的原因:Kafka在確認寫操作之前並沒有調用fsync。ACK的唯一要求是記錄已經寫入I/O緩衝區。這是一個鮮爲人知的事實,但卻是一個至關重要的事實。實際上,這就是Kafka的執行方式,就好像它是一個內存隊列一樣——Kafka實際上是一個由磁盤支持的內存隊列(受緩衝區/頁面緩存大小的限制)。

但是,這種形式的寫入是不安全的,因爲副本的出錯可能導致數據丟失,即使記錄似乎已經被ACK。換句話說,與關係型數據庫不同,僅寫入緩衝區並不意味着持久性。保證Kafka持久性的是運行幾個同步的副本。即使其中一個出錯了,其他的(假設不止一個)將繼續運行——假設出錯的原因不會導致其他的副本也出錯。因此,無fsync的非阻塞I/O方法和冗餘的同步副本組合爲Kafka提供了高吞吐、持久性和可用性。

關注微信公衆號:朱小廝的博客,在菜單欄“推薦閱讀”-“往期精彩”中有更多kafka知識等着你。或者回復:kafka,獲取急速學習通道^-^。

客戶端優化

大多數數據庫、隊列和其他形式的持久性中間件都是圍繞全能服務器(或服務器集羣)和瘦客戶端的概念設計的。客戶端的實現通常被認爲比服務器端簡單得多。服務器會處理大部分的負載,而客戶端僅充當服務端的門面。

Kafka採用了不同的客戶端設計方法。在記錄到達服務器之前,會在客戶端上執行大量的工作。這包括對累加器(RecordAccumulator)中的記錄進行分段、對記錄鍵進行散列以得到正確的分區索引、對記錄進行校驗以及對記錄批處理進行壓縮。客戶端知道集羣元數據,並定期刷新元數據以跟上服務端拓撲的更改。這讓客戶端更準確的做出轉發決策。不同於盲目地將記錄發送到集羣並依靠後者將其轉發到適當的節點,生產者客戶端可以直接將寫請求轉發到分區主機。類似地,消費者客戶端能夠在獲取記錄時做出更明智的決定,比如在發出讀查詢時,可以使用在地理上更接近消費者客戶端的副本。(該特性是從Kafka的2.4.0版本開始提供。)

零拷貝

Kafka使用了Zero Copy技術提升了消費的效率。前面所說的Kafka將消息先寫入頁緩存,如果消費者在讀取消息的時候如果在頁緩存中可以命中,那麼可以直接從頁緩存中讀取,這樣又節省了一次從磁盤到頁緩存的copy開銷。另外對於讀寫的概念可以進一步瞭解一下什麼是寫放大和讀放大。

避免垃圾回收

大量使用通道、緩衝區和頁面緩存還有一個額外的好處——減少垃圾收集器的工作負載。例如,在32 GB RAM的機器上運行Kafka將產生28-30 GB的頁面緩存可用空間,完全超出了垃圾收集器的範圍。吞吐量的差異非常小(大約幾個百分點),但是經過正確調優的垃圾收集器的吞吐量可能非常高,特別是在處理短生存期對象時。真正的收益在於減少抖動。通過避免垃圾回收,服務端不太可能遇到因垃圾回收引起的程序暫停,從而影響客戶端,加大記錄的通信延遲。

與初期的Kafka相比,現在避免垃圾回收已經不是什麼問題了。像Shenandoah和ZGC這樣的現代垃圾收集器可以擴展到巨大的、多TB級的堆,在最壞的情況下,並且可以自動調整垃圾收集的暫停時間,降到幾毫秒。現在,可以看見大量的基於Java虛擬機的應用程序使用堆緩存,而不是堆外緩存。

流處理的並行性

日誌的I/O效率是性能的一個重要方面,主要的性能影響在於寫。Kafka對主題結構和消費生態系統中的並行性處理是其讀性能的基礎。這種組合產生了整體非常高的端到端消息吞吐量。將併發性深入到分區方案和使用者組的操作中,這實際上是Kafka中的一種負載均衡機制——將分區平均地分配到各個消費者中。將此與傳統的消息隊列進行比較:在RabbitMQ的設置中,多個併發的消費者可以以輪詢的方式從隊列中讀取數據,但這樣做會喪失消息的有序性。

分區機制有利於Kafka服務端的水平擴展。每個分區都有一個專門的領導者。因此,任何重要的多分區的主題都可以利用整個服務端集羣進行寫操作。這是Kafka和傳統消息隊列的另一個區別。當後者利用集羣來提高可用性時,Kafka通過負載均衡來提高可用性、持久性和吞吐量。

發佈具有多個分區的主題時,生產者指定發佈記錄時的分區。(可能有一個單分區主題,那就不是問題了。)可以通過指定分區索引直接完成,或通過記錄鍵間接完成,記錄鍵通過計算散列值確定分區索引。具有相同散列值的記錄共享相同的分區。假設一個主題有多個分區,那麼具有不同鍵的記錄可能會出現在不同的分區中。然而,由於散列衝突,具有不同散列值的記錄也可能最終出現在同一個分區中。這就是散列的本質。如果你理解了散列表的工作方式,一切都很自然了。

記錄的實際處理由消費者完成,在一個可選的消費者組中完成。Kafka保證一個分區最多隻能分配給消費者組中的一個消費者。(爲什麼用”最多“,當所有消費者都離線時,那就是0個消費者了。)當組中的第一個消費者訂閱主題時,它將接收該主題上的所有分區。當第二個消費者訂閱主題時,它將接收到大約一半的分區,從而減輕第一個消費者的負載。根據需要添加消費者(理想情況下,使用自動伸縮機制),這使你能夠並行地處理事件流,前提是你已經對事件流進行了分區。

以兩種方式控制記錄的吞吐量:

  1. 主題分區方案。應該對主題進行分區,最大化事件流的數量。換句話說,只有在絕對需要時才提供記錄的順序。如果任何兩個記錄不存在關聯,它們就不應該被綁定到同一個分區。這意味着要使用不同的鍵,因爲Kafka使用記錄鍵的散列值作爲分區映射的根據。

  2. 組中消費者的數量。你可以增加消費者的數量來均衡入站記錄的負載,消費者的數量最多可以增加到和分區數量一樣多。(你可以增加更多的消費者,但每個分區最多只能有一個的活動消費者,剩下的消費者將處於閒置狀態。)請注意,你可以提供一個線程池,根據消費者執行工作負載的不同,消費者可以是一個進程或一個線程。

寫在最後

如果你想知道Kafka爲什麼這麼快,它是如何做到的,以及它是否適合你,我想你現在已經有了答案了。

Kafka作爲一個完整的生態系統,它在整體上仍然是無與倫比的。它展示了出色的性能,同時提供了一個豐富和成熟的環境,Kafka仍在以令人羨慕的速度增長。

Kafka的設計者和維護者在設計一個以性能爲核心的解決方案時做了大量的工作。它的設計元素中很少有讓人覺得是事後纔想到的,或者是補全的。從將工作負載轉移到客戶端,到服務端日誌的持久性、批處理、壓縮、零拷貝I/O和並行流處理——Kafka向任何其他消息中間件廠商發起挑戰,無論是商業的還是開源的。最令人印象深刻的是,它做到了這一點,卻沒有犧牲持久性、記錄有序性和至少一次分發的語義。

Kafka不是最簡單的消息中間件平臺,還有許多需要改進的地方。在設計和構建高性能事件驅動系統之前,必須掌握總體和部分的順序、主題、分區、消費者和消費者組的概念。雖然知識曲線很陡峭,但值得你花時間去學習。如果你知道這個諺語“red pill”(red pill,指爲了達到對某種事物的深度探索或追求,選擇去思考,不放棄,繼續走下去,哪怕這條路多難走。),請閱讀“介紹Kafka和Kafdrop中的事件流Introduction to Event Streaming with Kafka and Kafdrop[1]”。

相關鏈接:https://medium.com/swlh/introduction-to-event-streaming-with-kafka-and-kafdrop-22afdb4b380a

關注微信公衆號:朱小廝的博客,在菜單欄“推薦閱讀”-“往期精彩”中有更多kafka知識等着你。或者回復:kafka,獲取急速學習通道^-^。

本文英文原文鏈接:https://medium.com/swlh/why-kafka-is-so-fast-bde0d987cd03

---END---

重磅!碼農突圍-技術交流羣已成立

掃碼可添加碼農突圍助手,可申請加入碼農突圍大羣和細分方向羣,細分方向已涵蓋:Java、Python、機器學習、大數據、人工智能等羣。

一定要備註:開發方向+地點+學校/公司+暱稱(如Java開發+上海+拼夕夕+猴子),根據格式備註,可更快被通過且邀請進羣

▲長按加羣

順說句題外話,有不少人想加魚哥微信,魚哥姑且放出來,但是坑位有限哦


有熱門推薦????

1、一份來自亞馬遜工程師的Google面試指南,GitHub收穫9.8萬星,已翻譯成中文

2、用公司服務器挖礦賺 10 萬,百度運維被判 3 年

3、在瀏覽器輸入 URL 回車之後發生了什麼(流程圖,超詳細版)

如有收穫,點個在看,誠摯感謝

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