萬字長文帶你深入理解Kafka!為春招面試做好準備!

Hi丶ImViper發表於2020-12-06

Table of Contents generated with DocToc

Kafka介紹

Kafka是最初由Linkedin公司開發,是一個分散式、支援分割槽的(partition)、多副本的(replica),基於zookeeper協調的分散式訊息系統,它的最大的特性就是可以實時的處理大量資料以滿足各種需求場景:比如基於hadoop的批處理系統、低延遲的實時系統、storm/Spark流式處理引擎,web/nginx日誌、訪問日誌,訊息服務等等,用scala語言編寫,Linkedin於2010年貢獻給了Apache基金會併成為頂級開源專案。

Kafka特性

  • 高吞吐量、低延遲:kafka每秒可以處理幾十萬條訊息,它的延遲最低只有幾毫秒,每個topic可以分多個partition, consumer group 對partition進行consume操作。
  • 可擴充套件性:kafka叢集支援熱擴充套件
  • 永續性、可靠性:訊息被持久化到本地磁碟,並且支援資料備份防止資料丟失
  • 容錯性:允許叢集中節點失敗(若副本數量為n,則允許n-1個節點失敗)
  • 高併發:支援數千個客戶端同時讀寫

Kafka使用場景

  • 日誌收集:一個公司可以用Kafka可以收集各種服務的log,通過kafka以統一介面服務的方式開放給各種consumer,例如hadoop、Hbase、Solr等。
  • 訊息系統:解耦和生產者和消費者、快取訊息等。
  • 使用者活動跟蹤:Kafka經常被用來記錄web使用者或者app使用者的各種活動,如瀏覽網頁、搜尋、點選等活動,這些活動資訊被各個伺服器釋出到kafka的topic中,然後訂閱者通過訂閱這些topic來做實時的監控分析,或者裝載到hadoop、資料倉儲中做離線分析和挖掘。
  • 運營指標:Kafka也經常用來記錄運營監控資料。包括收集各種分散式應用的資料,生產各種操作的集中反饋,比如報警和報告。
  • 流式處理:比如spark streaming和storm
  • 事件源

Kafka術語

  • 訊息:Record。Kafka 是訊息引擎嘛,這裡的訊息就是指 Kafka 處理的主要物件。
  • 主題:Topic。主題是承載訊息的邏輯容器,在實際使用中多用來區分具體的業務。
  • 分割槽:Partition。一個有序不變的訊息序列。每個主題下可以有多個分割槽。
  • 訊息位移:Offset。表示分割槽中每條訊息的位置資訊,是一個單調遞增且不變的值。
  • 副本:Replica。Kafka 中同一條訊息能夠被拷貝到多個地方以提供資料冗餘,這些地方就是所謂的副本。副本還分為領導者副本和追隨者副本,各自有不同的角色劃分。副本是在分割槽層級下的,即每個分割槽可配置多個副本實現高可用。
  • 生產者:Producer。向主題釋出新訊息的應用程式。
  • 消費者:Consumer。從主題訂閱新訊息的應用程式。
  • 消費者位移:Consumer Offset。表徵消費者消費進度,每個消費者都有自己的消費者位移。
  • 消費者組:Consumer Group。多個消費者例項共同組成的一個組,同時消費多個分割槽以實現高吞吐。
  • 重平衡:Rebalance。消費者組內某個消費者例項掛掉後,其他消費者例項自動重新分配訂閱主題分割槽的過程。Rebalance 是 Kafka 消費者端實現高可用的重要手段。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-JyaP9VPu-1607192767310)(E:\學習\寫作\別人的文章\最近可抄\Java-Summarize-master\image/Kafka-1.png)]

Kafka叢集

線上叢集部署方案

從作業系統、磁碟、磁碟容量和頻寬等方面來討論一下

作業系統

目前常見的作業系統有 3 種:Linux、Windows 和 macOS。應該說部署在 Linux 上的生產環境是最多的。
如果考慮作業系統與 Kafka 的適配性,Linux 系統顯然要比其他兩個特別是 Windows 系統更加適合部署 Kafka。

Kafka 客戶端底層使用了 Java 的 selector,selector 在 Linux 上的實現機制是 epoll,而在 Windows 平臺上的實現機制是 select。因此在這一點上將 Kafka 部署在 Linux 上是有優勢的,因為能夠獲得更高效的 I/O 效能。

Linux 平臺實現了零拷貝機制,就是當資料在磁碟和網路進行傳輸時避免昂貴的核心態資料拷貝從而實現快速地資料傳輸。在 Linux 部署 Kafka 能夠享受到零拷貝技術所帶來的快速資料傳輸特性。

磁碟

應該選擇普通的機械磁碟還是固態硬碟?前者成本低且容量大,但易損壞;後者效能優勢大,不過單價高。

Kafka 大量使用磁碟,可它使用的方式多是順序讀寫操作,一定程度上規避了機械磁碟最大的劣勢,即隨機讀寫操作慢。

就 Kafka 而言,一方面 Kafka 自己實現了冗餘機制來提供高可靠性;另一方面通過分割槽的概念,所以使用機械磁碟完全能夠勝任 Kafka 線上環境。

磁碟容量

我們來計算一下:每天 1 億條 1KB 大小的訊息,儲存兩份且留存兩週的時間,那麼總的空間大小就等於 1 億 * 1KB * 2 / 1000 / 1000 = 200GB。一般情況下 Kafka 叢集除了訊息資料還有其他型別的資料,比如索引資料等,故我們再為這些資料預留出 10% 的磁碟空間,因此總的儲存容量就是 220GB。既然要儲存兩週,那麼整體容量即為 220GB * 14,大約 3TB 左右。Kafka 支援資料的壓縮,假設壓縮比是 0.75,那麼最後你需要規劃的儲存空間就是 0.75 * 3 = 2.25TB。

總之在規劃磁碟容量時你需要考慮下面這幾個元素:

  • 新增訊息數
  • 訊息留存時間
  • 平均訊息大小
  • 備份數
  • 是否啟用壓縮

頻寬

在 1 小時內處理 1TB 的業務資料。需要多少臺 Kafka 伺服器來完成這個業務呢?

通常情況下你只能假設 Kafka 會用到 70% 的頻寬資源,因為總要為其他應用或程式留一些資源。

根據這個目標,我們每秒需要處理 2336Mb 的資料,除以 240,約等於 10 臺伺服器。如果訊息還需要額外複製兩份,那麼總的伺服器臺數還要乘以 3,即 30 臺。

叢集引數配置

Broker 端引數

  • log.dirs:指定了 Broker 需要使用的若干個檔案目錄路徑。要知道這個引數是沒有預設值的,它必須由你親自指定。
  • log.dir:注意這是 dir,結尾沒有 s,說明它只能表示單個路徑,它是補充上一個引數用的。

線上上生產環境中一定要為log.dirs配置多個路徑。這樣做有兩個好處:

  • 提升讀寫效能:比起單塊磁碟,多塊物理磁碟同時讀寫資料有更高的吞吐量。
  • 能夠實現故障轉移:即 Failover。這是 Kafka 1.1 版本新引入的強大功能。壞掉的磁碟上的資料會自動地轉移到其他正常的磁碟上,而且 Broker 還能正常工作。

ZooKeeper 相關引數

  • zookeeper.connect

Broker連線相關

  • listeners:監聽器,告訴外部連線者要通過什麼協議訪問指定主機名和埠開放的 Kafka 服務。
  • advertised.listeners:和 listeners 相比多了個 advertised。Advertised 的含義表示宣稱的、公佈的,就是說這組監聽器是 Broker 用於對外發布的。

Topic管理

  • auto.create.topics.enable:是否允許自動建立 Topic。
  • unclean.leader.election.enable:是否允許 Unclean Leader 選舉。
  • auto.leader.rebalance.enable:是否允許定期進行 Leader 選舉。

auto.create.topics.enable引數我建議最好設定成 false,即不允許自動建立 Topic。線上上環境裡面有很多名字稀奇古怪的 Topic,大概都是因為該引數被設定成了 true 的緣故。

資料留存

  • log.retention.{hour|minutes|ms}:都是控制一條訊息資料被儲存多長時間。從優先順序上來說 ms 設定最高、minutes 次之、hour 最低。
  • log.retention.bytes:這是指定 Broker 為訊息儲存的總磁碟容量大小。
  • message.max.bytes:控制 Broker 能夠接收的最大訊息大小。
  • retention.ms:規定了該 Topic 訊息被儲存的時長。預設是 7 天,即該 Topic 只儲存最近 7 天的訊息。一旦設定了這個值,它會覆蓋掉 Broker 端的全域性引數值。
  • retention.bytes:規定了要為該 Topic 預留多大的磁碟空間。和全域性引數作用相似,這個值通常在多租戶的 Kafka 叢集中會有用武之地。當前預設值是 -1,表示可以無限使用磁碟空間。

Kafka分割槽機制

為什麼分割槽?

分割槽的作用就是提供負載均衡的能力,或者說對資料進行分割槽的主要原因,就是為了實現系統的高伸縮性(Scalability)。不同的分割槽能夠被放置到不同節點的機器上,而資料的讀寫操作也都是針對分割槽這個粒度而進行的,這樣每個節點的機器都能獨立地執行各自分割槽的讀寫請求處理。並且,我們還可以通過新增新的節點機器來增加整體系統的吞吐量。

Kafka 的三層訊息架構:

  • 第一層是主題層,每個主題可以配置 M 個分割槽,而每個分割槽又可以配置 N 個副本。
  • 第二層是分割槽層,每個分割槽的 N 個副本中只能有一個充當領導者角色,對外提供服務;其他 N-1 個副本是追隨者副本,只是提供資料冗餘之用。
  • 第三層是訊息層,分割槽中包含若干條訊息,每條訊息的位移從 0 開始,依次遞增。
  • 最後,客戶端程式只能與分割槽的領導者副本進行互動

分割槽策略

所謂分割槽策略是決定生產者將訊息傳送到哪個分割槽的演算法

輪詢策略

也稱 Round-robin 策略,即順序分配。比如一個主題下有 3 個分割槽,那麼第一條訊息被髮送到分割槽 0,第二條被髮送到分割槽 1,第三條被髮送到分割槽 2,以此類推。當生產第 4 條訊息時又會重新開始,即將其分配到分割槽 0

輪詢策略是 Kafka Java 生產者 API 預設提供的分割槽策略。它總是能保證訊息最大限度地被平均分配到所有分割槽上,故預設情況下它是最合理的分割槽策略,也是我們最常用的分割槽策略之一。

隨機策略

所謂隨機就是我們隨意地將訊息放置到任意一個分割槽上

按訊息鍵保序策略

Kafka 允許為每條訊息定義訊息鍵,簡稱為 Key。這個 Key 的作用非常大,它可以是一個有著明確業務含義的字串,比如客戶程式碼、部門編號或是業務 ID 等;也可以用來表徵訊息後設資料。

Kafka副本機制

所謂的副本機制(Replication),也可以稱之為備份機制,通常是指分散式系統在多臺網路互聯的機器上儲存有相同的資料拷貝。副本機制有什麼好處呢?

  1. 提供資料冗餘。即使系統部分元件失效,系統依然能夠繼續運轉,因而增加了整體可用性以及資料永續性。
  2. 提供高伸縮性。支援橫向擴充套件,能夠通過增加機器的方式來提升讀效能,進而提高讀操作吞吐量。
  3. 改善資料區域性性。允許將資料放入與使用者地理位置相近的地方,從而降低系統延時。

Kafka 是有主題概念的,而每個主題又進一步劃分成若干個分割槽。副本的概念實際上是在分割槽層級下定義的,每個分割槽配置有若干個副本。

所謂副本(Replica),本質就是一個只能追加寫訊息的提交日誌。

如何確保副本中所有的資料都是一致的呢?

最常見的解決方案就是採用基於領導者(Leader-based)的副本機制。Apache Kafka 就是這樣的設計。

基於領導者的副本機制的工作原理如下圖所示

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-HaqlJuxC-1607192767312)(image/Kafka-4.png)]

  1. 在 Kafka 中,副本分成兩類:領導者副本(Leader Replica)和追隨者副本(Follower Replica)。每個分割槽在建立時都要選舉一個副本,稱為領導者副本,其餘的副本自動稱為追隨者副本。
  2. Kafka 的副本機制比其他分散式系統要更嚴格一些。在 Kafka 中,追隨者副本是不對外提供服務的。這就是說,任何一個追隨者副本都不能響應消費者和生產者的讀寫請求。所有的請求都必須由領導者副本來處理,或者說,所有的讀寫請求都必須發往領導者副本所在的 Broker,由該 Broker 負責處理。追隨者副本不處理客戶端請求,它唯一的任務就是從領導者副本非同步拉取訊息,並寫入到自己的提交日誌中,從而實現與領導者副本的同步。
  3. 當領導者副本掛掉了,或者說領導者副本所在的 Broker 當機時,Kafka 依託於 ZooKeeper 提供的監控功能能夠實時感知到,並立即開啟新一輪的領導者選舉,從追隨者副本中選一個作為新的領導者。老 Leader 副本重啟回來後,只能作為追隨者副本加入到叢集中。

為什麼追隨者副本是不對外提供服務的呢?

1. 方便實現“Read-your-writes”

顧名思義就是,當你使用生產者 API 向 Kafka 成功寫入訊息後,馬上使用消費者 API 去讀取剛才生產的訊息。

2. 方便實現單調讀(Monotonic Reads)

就是對於一個消費者使用者而言,在多次消費訊息時,它不會看到某條訊息一會兒存在一會兒不存在。

無訊息丟失配置怎麼實現?

Kafka 只對“已提交”的訊息(committed message)做有限度的持久化保證。

生產者程式丟失資料

Producer 永遠要使用帶有回撥通知的傳送 API,也就是說不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。

它能準確地告訴你訊息是否真的提交成功了。一旦出現訊息提交失敗的情況,你就可以有針對性地進行處理。

消費者程式丟失資料

Consumer 端丟失資料主要體現在 Consumer 端要消費的訊息不見了。Consumer 程式有個“位移”的概念,表示的是這個 Consumer 當前消費到的 Topic 分割槽的位置。下面這張圖來自於官網,它清晰地展示了 Consumer 端的位移資料。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-fxQVfN5N-1607192767313)(E:\學習\寫作\別人的文章\最近可抄\Java-Summarize-master\image/Kafka-2.png)]

比如對於 Consumer A 而言,它當前的位移值就是 9;Consumer B 的位移值是 11。

要對抗這種訊息丟失,辦法很簡單:維持先消費訊息,再更新位移的順序即可。

如果是多執行緒非同步處理消費訊息,Consumer 程式不要開啟自動提交位移,而是要應用程式手動提交位移。

Kafka 無訊息丟失的配置

  1. 不要使用 producer.send(msg),而要使用 producer.send(msg, callback)。記住,一定要使用帶有回撥通知的 send 方法。
  2. 設定 acks = all。acks 是 Producer 的一個引數,代表了你對“已提交”訊息的定義。如果設定成 all,則表明所有副本 Broker 都要接收到訊息,該訊息才算是“已提交”。這是最高等級的“已提交”定義。
  3. 設定 retries 為一個較大的值。這裡的 retries 同樣是 Producer 的引數,對應前面提到的 Producer 自動重試。當出現網路的瞬時抖動時,訊息傳送可能會失敗,此時配置了 retries > 0 的 Producer 能夠自動重試訊息傳送,避免訊息丟失。
  4. 設定 unclean.leader.election.enable = false。這是 Broker 端的引數,它控制的是哪些 Broker 有資格競選分割槽的 Leader。如果一個 Broker 落後原先的 Leader 太多,那麼它一旦成為新的 Leader,必然會造成訊息的丟失。故一般都要將該引數設定成 false,即不允許這種情況的發生。
  5. 設定 replication.factor >= 3。這也是 Broker 端的引數。其實這裡想表述的是,最好將訊息多儲存幾份,畢竟目前防止訊息丟失的主要機制就是冗餘。
  6. 設定 min.insync.replicas > 1。這依然是 Broker 端引數,控制的是訊息至少要被寫入到多少個副本才算是“已提交”。設定成大於 1 可以提升訊息永續性。在實際環境中千萬不要使用預設值 1。
  7. 確保 replication.factor > min.insync.replicas。如果兩者相等,那麼只要有一個副本掛機,整個分割槽就無法正常工作了。我們不僅要改善訊息的永續性,防止資料丟失,還要在不降低可用性的基礎上完成。推薦設定成 replication.factor = min.insync.replicas + 1。
  8. 確保訊息消費完成再提交。Consumer 端有個引數 enable.auto.commit,最好把它設定成 false,並採用手動提交位移的方式。就像前面說的,這對於單 Consumer 多執行緒處理的場景而言是至關重要的。

Kafka的通訊

Apache Kafka 的所有通訊都是基於 TCP 的,而不是基於 HTTP 或其他協議。無論是生產者、消費者,還是 Broker 之間的通訊都是如此。

目的是利用 TCP 本身提供的一些高階功能,比如多路複用請求以及同時輪詢多個連線的能力

所謂的多路複用請求,即 multiplexing request,是指將兩個或多個資料流合併到底層單一物理連線中的過程。TCP 的多路複用請求會在一條物理連線上建立若干個虛擬連線,每個虛擬連線負責流轉各自對應的資料流。其實嚴格來說,TCP 並不能多路複用,它只是提供可靠的訊息交付語義保證,比如自動重傳丟失的報文。

更嚴謹地說,作為一個基於報文的協議,TCP 能夠被用於多路複用連線場景的前提是,上層的應用協議(比如 HTTP)允許傳送多條訊息。

消費者組

Consumer Group 是 Kafka 提供的可擴充套件且具有容錯性的消費者機制。既然是一個組,那麼組內必然可以有多個消費者或消費者例項(Consumer Instance),它們共享一個公共的 ID,這個 ID 被稱為 Group ID。

組內的所有消費者協調在一起來消費訂閱主題(Subscribed Topics)的所有分割槽(Partition)。當然,每個分割槽只能由同一個消費者組內的一個 Consumer 例項來消費。

理解 Consumer Group 記住下面這三個特性:

  1. Consumer Group 下可以有一個或多個 Consumer 例項。這裡的例項可以是一個單獨的程式,也可以是同一程式下的執行緒。在實際場景中,使用程式更為常見一些。
  2. Group ID 是一個字串,在一個 Kafka 叢集中,它標識唯一的一個 Consumer Group。
  3. Consumer Group 下所有例項訂閱的主題的單個分割槽,只能分配給組內的某個 Consumer 例項消費。這個分割槽當然也可以被其他的 Group 消費。

Kafka 僅僅使用 Consumer Group 這一種機制,卻同時實現了傳統訊息引擎系統的兩大模型:如果所有例項都屬於同一個 Group,那麼它實現的就是訊息佇列模型;如果所有例項分別屬於不同的 Group,那麼它實現的就是釋出 / 訂閱模型。

理想情況下,Consumer 例項的數量應該等於該 Group 訂閱主題的分割槽總數。

Rebalance重平衡

Rebalance 本質上是一種協議,規定了一個 Consumer Group 下的所有 Consumer 如何達成一致,來分配訂閱 Topic 的每個分割槽。

Rebalance 的觸發條件:

  1. 組成員數發生變更。比如有新的 Consumer 例項加入組或者離開組,抑或是有 Consumer 例項崩潰被“踢出”組。
  2. 訂閱主題數發生變更。Consumer Group 可以使用正規表示式的方式訂閱主題,比如 consumer.subscribe(Pattern.compile(“t.*c”)) 就表明該 Group 訂閱所有以字母 t 開頭、字母 c 結尾的主題。在 Consumer Group 的執行過程中,你新建立了一個滿足這樣條件的主題,那麼該 Group 就會發生 Rebalance。
  3. 訂閱主題的分割槽數發生變更。Kafka 當前只能允許增加一個主題的分割槽數。當分割槽數增加時,就會觸發訂閱該主題的所有 Group 開啟 Rebalance。

重平衡全流程

消費者組狀態機的各個狀態流轉

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-qNZScClN-1607192767315)(E:\學習\寫作\別人的文章\最近可抄\Java-Summarize-master\image/Kafka-5.png)]

一個消費者組最開始是 Empty 狀態,當重平衡過程開啟後,它會被置於 PreparingRebalance 狀態等待成員加入,之後變更到 CompletingRebalance 狀態等待分配方案,最後流轉到 Stable 狀態完成重平衡。

當有新成員加入或已有成員退出時,消費者組的狀態從 Stable 直接跳到 PreparingRebalance 狀態,此時,所有現存成員就必須重新申請加入組。當所有成員都退出組後,消費者組狀態變更為 Empty。Kafka 定期自動刪除過期位移的條件就是,組要處於 Empty 狀態。因此,如果你的消費者組停掉了很長時間(超過 7 天),那麼 Kafka 很可能就把該組的位移資料刪除了。

在消費者端,重平衡分為兩個步驟:分別是加入組和等待領導者消費者(Leader Consumer)分配方案。這兩個步驟分別對應兩類特定的請求:JoinGroup 請求和 SyncGroup 請求。

  1. 第一個傳送 JoinGroup 請求的成員自動成為領導者,領導者消費者的任務是收集所有成員的訂閱資訊,然後根據這些資訊,制定具體的分割槽消費分配方案。
  2. 領導者向協調者傳送 SyncGroup 請求,將剛剛做出的分配方案發給協調者。

JoinGroup 請求的處理過程

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-67cFNLT3-1607192767316)(E:\學習\寫作\別人的文章\最近可抄\Java-Summarize-master\image/Kafka-6.png)]

SyncGroup 請求的處理流程

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-q5n0CFB3-1607192767317)(E:\學習\寫作\別人的文章\最近可抄\Java-Summarize-master\image/Kafka-7.png)]

位移提交

Consumer 需要向 Kafka 彙報自己的位移資料,這個彙報過程被稱為提交位移。因為 Consumer 能夠同時消費多個分割槽的資料,所以位移的提交實際上是在分割槽粒度上進行的,即Consumer 需要為分配給它的每個分割槽提交各自的位移資料。

位移提交的語義保障是由你來負責的,Kafka 只會“無腦”地接受你提交的位移。你對位移提交的管理直接影響了你的 Consumer 所能提供的訊息語義保障。

從使用者的角度來說,位移提交分為自動提交和手動提交;從 Consumer 端的角度來說,位移提交分為同步提交和非同步提交。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-O2QpHM3P-1607192767319)(E:\學習\寫作\別人的文章\最近可抄\Java-Summarize-master\image/Kafka-3.jpeg)]

我們先來說說自動提交和手動提交。所謂自動提交,就是指 Kafka Consumer 在後臺默默地為你提交位移,作為使用者的你完全不必操心這些事;自動提交位移的一個問題在於,它可能會出現重複消費。而手動提交,則是指你要自己提交位移,Kafka Consumer 壓根不管。

反觀手動提交位移,它的好處就在於更加靈活,你完全能夠把控位移提交的時機和頻率。但是,它也有一個缺陷,就是在呼叫 commitSync() 時,Consumer 程式會處於阻塞狀態,直到遠端的 Broker 返回提交結果,這個狀態才會結束。

消費進度監控

  1. 使用 Kafka 自帶的命令列工具 kafka-consumer-groups 指令碼。
  2. 使用 Kafka Java Consumer API 程式設計。
  3. 使用 Kafka 自帶的 JMX 監控指標。

相關文章