返回文章列表

Kafka 核心機制與儲存最佳化策略

本文探討 Kafka 的長官者選舉機制、同步副本與非同步副本的概念,以及客戶端請求處理流程,包含後設資料請求、生產請求和取得請求的詳細步驟。此外,文章還解析了 Kafka 的儲存機制、分層儲存架構、分割槽分配策略和檔案系統最佳化,包括日誌分段、保留策略和零複製技術的應用,提供程式碼範例和圖表說明,幫助讀者深入理解

分散式系統 訊息佇列

Kafka 的長官者選舉機制確保了分割槽的高用性,偏好長官者的概念有助於維持叢集的穩定性。同步副本列表(ISR)的維護是 Kafka 保證資料一致性的關鍵,replica.lag.time.max.ms 引數的組態會影響副本的同步狀態。客戶端與 Broker 的互動透過後設資料請求、生產請求和取得請求完成,理解這些請求的處理流程對於最佳化 Kafka 應用至關重要。Kafka 的儲存機制根據分割槽副本,分層儲存架構的引入解決了儲存容量限制和成本問題。分割槽分配策略確保了資料在 Broker 間的均衡分佈,檔案系統的最佳化策略,例如日誌分段、保留策略和零複製技術,則提升了 Kafka 的讀寫效能。

Kafka Broker 的偏好副本與長官者選舉

每個分割槽都有一個偏好長官者,即建立主題時最初的長官者。Kafka 預設組態為 auto.leader.rebalance.enable=true,這意味著當偏好長官者副本不同於當前長官者但保持同步時,Kafka 將觸發長官者選舉,使偏好長官者成為當前長官者。

如何找到偏好長官者?

檢視分割槽的副本列表,第一個列出的副本即為偏好長官者。這一規則適用於任何當前長官者和即使用副本重新分配工具重新分配了副本的情況。

同步副本與不同步副本

  1. 同步副本(In-Sync Replicas, ISR):持續請求最新訊息的副本被視為同步副本。只有同步副本才有資格在現有長官者失敗時被選為分割槽長官者。
  2. 不同步副本:如果副本在一段時間內沒有請求訊息或沒有趕上最新訊息,則被視為不同步。不同步的副本不能成為新的長官者,因為它們不包含所有訊息。

組態引數:replica.lag.time.max.ms

此引數控制著一個後續副本被視為不同步之前可以處於不活躍或落後狀態的時間長度。它對客戶端行為和長官者選舉期間的資料保留有影響。

Kafka客戶端請求處理機制詳解

Kafka的客戶端請求處理是其高效能和可擴充套件性的關鍵部分。當客戶端與Kafka叢集互動時,它們會傳送不同型別的請求,包括後設資料請求、生產請求和取得請求。

後設資料請求

客戶端需要知道哪些Broker負責哪些分割槽的讀寫操作。後設資料請求用於取得這些資訊。客戶端可以向任何一個Broker傳送後設資料請求,因為所有的Broker都維護了一個後設資料快取。

此圖示說明瞭客戶端如何透過後設資料請求取得資訊並正確路由後續請求。

內容解密:

  1. 客戶端傳送後設資料請求到任意Broker。
  2. Broker傳回包含分割槽和Broker對映的後設資料。
  3. 客戶端根據後設資料將後續請求路由到正確的Broker。

生產請求

當客戶端需要向Kafka寫入訊息時,它會傳送生產請求。生產請求的處理涉及多個步驟,包括驗證、寫入本地磁碟和根據acks組態等待確認。

生產請求處理流程

  1. 驗證:Broker驗證客戶端的寫入許可權、acks組態的有效性以及是否有足夠的同步副本。
  2. 寫入本地磁碟:訊息被寫入Leader副本所在的Broker的本地磁碟。
  3. 根據acks組態處理:根據acks的值(0、1或all),決定何時向客戶端傳回成功回應。

此圖示展示了生產請求的處理流程,包括驗證、寫入和根據acks組態傳回回應。

內容解密:

  1. 客戶端傳送生產請求到Leader Broker。
  2. Leader Broker進行驗證,包括檢查寫入許可權和acks組態。
  3. 驗證透過後,訊息被寫入本地磁碟。
  4. 根據acks組態,決定何時傳回回應給客戶端。

取得請求

客戶端透過傳送取得請求來從Kafka讀取訊息。取得請求的處理涉及從指定分割槽和偏移量讀取訊息,並傳回給客戶端。

取得請求處理流程

  1. 驗證:Broker驗證請求的有效性,包括檢查偏移量是否存在。
  2. 讀取訊息:從指定分割槽和偏移量開始讀取訊息,直到達到客戶端設定的資料量限制。
  3. 傳回訊息:將讀取到的訊息傳回給客戶端。

Kafka使用零複製技術直接將訊息從檔案系統快取傳送到網路通道,提高了效能。

此圖示展示了取得請求的處理流程,包括驗證、讀取訊息和傳回訊息。

內容解密:

  1. 客戶端傳送取得請求到Leader Broker。
  2. Leader Broker驗證請求的有效性。
  3. 驗證透過後,從指定偏移量開始讀取訊息。
  4. 將讀取到的訊息傳回給客戶端。

Kafka 客戶端請求處理機制深入解析

Kafka 客戶端與 Broker 之間的通訊是透過一系列的請求(Request)與回應(Response)來完成的。瞭解這些請求的處理機制對於最佳化 Kafka 應用程式的效能至關重要。

請求型別與處理流程

Kafka 目前支援多達 61 種不同的請求型別,這些請求大致可分為幾大類別:

  1. 後設資料請求(Metadata Request):客戶端用於取得 Kafka 叢集的後設資料,如主題、分割槽、Leader 等資訊。
  2. 生產請求(Produce Request):生產者用於向 Kafka 主題傳送訊息。
  3. 取得請求(Fetch Request):消費者用於從 Kafka 主題讀取訊息。

取得請求的最佳化

在取得請求中,消費者可以指定最小資料量和逾時時間,以平衡延遲和吞吐量。如果 Broker 在指定時間內無法滿足最小資料量要求,可以直接傳回當前已有的資料。

讀取已複製訊息的重要性

消費者預設只能讀取已經被複製到所有同步副本(In-Sync Replicas)的訊息。這是因為尚未複製完成的訊息被視為「不安全」,如果 Leader 發生故障,這些訊息可能會丟失,從而導致消費者之間的資料不一致。

為什麼要等待訊息複製完成?

  1. 保證資料一致性:只有當訊息被複製到所有同步副本後,消費者才能讀取,確保了資料的一致性。
  2. 避免資料丟失:如果允許消費者讀取 Leader 上的獨佔訊息,一旦 Leader 故障,這些訊息可能無法被其他消費者讀取。

Fetch Session 快取最佳化

當消費者需要讀取大量分割槽的資料時,頻繁地傳送分割槽清單和後設資料會造成效能瓶頸。為此,Kafka 引入了 Fetch Session 快取機制,允許消費者建立快取會話,以減少不必要的後設資料傳輸。

程式碼範例:Fetch 請求處理

// 建立 Fetch 請求
FetchRequest fetchRequest = FetchRequest.Builder.forConsumer(maxWaitMs, minBytes, entryMap)
        .isolationLevel(IsolationLevel.READ_COMMITTED)
        .build();

// 處理 Fetch 回應
FetchResponse fetchResponse = broker.fetch(fetchRequest);
for (Map.Entry<TopicPartition, FetchResponseData.PartitionData> entry : fetchResponse.responseData().entrySet()) {
    TopicPartition partition = entry.getKey();
    FetchResponseData.PartitionData partitionData = entry.getValue();
    // 處理每個分割槽的資料
}

內容解密:

  1. FetchRequest.Builder.forConsumer:建立一個針對消費者的 Fetch 請求建構器,需要指定最大等待時間 maxWaitMs、最小讀取位元組數 minBytes 和分割槽資料對映 entryMap
  2. isolationLevel(IsolationLevel.READ_COMMITTED):設定隔離級別為 READ_COMMITTED,確保只讀取已經提交的訊息。
  3. broker.fetch(fetchRequest):向指定的 Broker 傳送 Fetch 請求,並取得回應資料。
  4. fetchResponse.responseData():遍歷 Fetch 回應中的分割槽資料,並對每個分割槽進行處理。

其他請求型別與協定演進

除了上述常見的請求型別外,Kafka 還支援多種其他請求,如 OffsetCommitRequest、OffsetFetchRequest 等,用於管理消費者的偏移量和其他後設資料操作。

協定演進的重要性

Kafka 的協定是動態演進的,新版本的 Kafka 可能會新增或修改現有的請求型別。為了保持向前相容性,Kafka 支援多版本請求共存,新版本的客戶端可以與舊版本的 Broker 通訊,反之亦然。

Kafka 儲存機制與分層儲存架構解析

Kafka 的基本儲存單位是分割槽副本(partition replica)。分割槽無法跨多個 broker,也不能跨同一個 broker 上的多個磁碟。因此,分割槽的大小受限於單一掛載點(mount point)上的可用空間。掛載點可以是單一磁碟(JBOD 組態),也可以是多個磁碟(RAID 組態)。

Kafka 組態與儲存管理

在組態 Kafka 時,管理員需要定義一個目錄列表,用於儲存分割槽,這由 log.dirs 引數控制。典型的組態包括為 Kafka 將使用的每個掛載點指定一個目錄。Kafka 使用這些目錄來儲存資料,首先將資料分配給叢集中的 broker,然後在 broker 上管理檔案,特別是如何處理保留保證。

儲存分配與管理

  1. 資料分配:Kafka 將資料分配給叢集中的 broker,並進一步分配到 broker 上的目錄。
  2. 檔案管理:broker 管理檔案,確保保留保證得以實作,包括如何處理日誌段和索引檔案。
  3. 日誌壓縮:Kafka 提供日誌壓縮功能,允許將 Kafka 用作長期資料儲存,並描述其工作原理。

分層儲存架構

從 2018 年底開始,Apache Kafka 社群開始合作開發一個雄心勃勃的專案,為 Kafka 新增分層儲存(tiered storage)功能。該專案仍在進行中,計劃在 3.0 版本發布。

分層儲存的動機

  1. 儲存限制:目前 Kafka 的分割槽儲存容量受限於物理磁碟大小。
  2. 成本與彈性:叢集大小受儲存需求影響,導致成本增加和彈性降低。
  3. 分割槽遷移時間:在擴充套件或縮減叢集時,分割槽遷移時間受分割槽大小影響。

分層儲存架構設計

分層儲存架構引入了兩層儲存:本地(local)和遠端(remote)。本地層使用 Kafka broker 上的本地磁碟儲存日誌段,而遠端層則使用專用的儲存系統,如 HDFS 或 S3,來儲存完成的日誌段。

  • 本地層:通常具有較短的保留期限(幾小時),提供低延遲存取。
  • 遠端層:可以具有較長的保留期限(數天或數月),適合長期儲存。

分層儲存的優勢

  1. 獨立擴充套件儲存:允許 Kafka 成為長期儲存解決方案,降低成本。
  2. 提高彈性:減少在還原和重新平衡期間需要複製的資料量。
  3. 讀取隔離:歷史讀取和實時讀取之間具有隔離效果,提高系統整體效能。

分割槽分配

建立主題時,Kafka 首先決定如何在 broker 之間分配分割槽。例如,若有 6 個 broker,並決定建立一個具有 10 個分割槽和 3 個副本的主題,則 Kafka 需要將 30 個分割槽副本分配給 6 個 broker。分配的目標是均勻地將副本分散到各個 broker 上。

graph LR
    A[Kafka Cluster] -->|包含|> B[Broker 1]
    A -->|包含|> C[Broker 2]
    A -->|包含|> D[Broker 3]
    A -->|包含|> E[Broker 4]
    A -->|包含|> F[Broker 5]
    A -->|包含|> G[Broker 6]
    B -->|儲存|> H[Partition 1]
    C -->|儲存|> I[Partition 2]
    D -->|儲存|> J[Partition 3]
    E -->|儲存|> K[Partition 4]
    F -->|儲存|> L[Partition 5]
    G -->|儲存|> M[Partition 6]

此圖示說明瞭 Kafka 叢集如何由多個 Broker 組成,並且每個 Broker 都儲存不同的分割槽。

內容解密:
  1. 圖表採用 Plantuml 語法中的 graph LR 定義了一個由左至右的流程圖,用於展示 Kafka Cluster 與多個 Broker 之間的關係,以及各個 Broker 如何分別儲存不同的 Partition。
  2. 每個節點代表一個實體,例如 A 代表 Kafka Cluster,而 BG 代表不同的 Broker,HM 代表不同的 Partition。
  3. 連線符號 -->| 包含了特定的標籤,用以描述節點之間的關係,例如「包含」和「儲存」,從而清晰地展示了 Kafka Cluster 如何包含多個 Broker,以及每個 Broker 如何負責儲存特定的 Partition。

本篇文章探討了 Kafka 的基本儲存單位、分割槽副本,以及分層儲存架構的設計與優勢,並透過 Plantuml 圖表具體展示了 Kafka Cluster 的架構。文章全面闡述了 Kafka 的內部工作原理及其在現代資料處理中的關鍵作用。

Kafka 分割槽管理與檔案系統最佳化

Kafka 的分割槽管理是其可擴充套件性和高用性的關鍵。正確的分割槽策略可以確保資料均勻分佈在多個 broker 上,並且在發生故障時能夠快速還原。

分割槽分配策略

Kafka 的分割槽分配策略旨在將每個分割槽的副本分散到不同的 broker 上,以確保在單一 broker 故障時,其他副本可以繼續提供服務。分配策略如下:

  1. 初始分配:從一個隨機的 broker 開始,按照輪詢的方式將分割槽分配給每個 broker,以確定 leader 的位置。
  2. 副本分配:對於每個分割槽,將副本放置在與 leader 不同 offset 的 broker 上。如果考慮機架感知(rack awareness),則會優先將副本分配到不同的機架上,以提高用性。

程式碼範例:分割槽分配邏輯

// 簡化的分割槽分配邏輯範例
public void assignPartitions(List<Broker> brokers, List<Partition> partitions) {
    // 輪詢分配分割槽給 brokers
    for (int i = 0; i < partitions.size(); i++) {
        Partition partition = partitions.get(i);
        Broker leader = brokers.get(i % brokers.size());
        partition.setLeader(leader);
        // 分配副本到不同的 broker
        List<Broker> replicas = new ArrayList<>();
        replicas.add(leader);
        for (int j = 1; j <= replicationFactor; j++) {
            replicas.add(brokers.get((i + j) % brokers.size()));
        }
        partition.setReplicas(replicas);
    }
}

內容解密:

  1. 輪詢分配:程式碼使用輪詢的方式將分割槽分配給可用的 brokers,確保每個 broker 負責不同分割槽的 leader。
  2. 副本放置:對於每個分割槽,程式碼將副本放置在與 leader 不同的 broker 上,以確保高用性。
  3. 機架感知:如果啟用機架感知,程式碼會優先將副本分配到不同的機架上,以提高容錯能力。

檔案管理與保留策略

Kafka 使用分段(segment)的方式來管理日誌檔案,每個分段預設為 1 GB 或一週的資料。保留策略可以根據時間或資料大小來組態。當達到保留限制時,舊的分段將被刪除。

Kafka 日誌分段與保留策略

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Kafka 核心機制與儲存最佳化策略

package "非同步程式設計" {
    package "事件迴圈" {
        component [Event Loop] as loop
        component [Task 任務] as task
        component [Future 物件] as future
    }

    package "協程模式" {
        component [async/await] as async
        component [Generator] as gen
        component [Callback] as callback
    }

    package "並行處理" {
        component [asyncio] as asyncio
        component [aiohttp] as aiohttp
        component [ThreadPool] as thread
        component [ProcessPool] as process
    }
}

loop --> task : 排程執行
task --> future : 等待結果
async --> asyncio : 協程管理
asyncio --> aiohttp : HTTP 非同步
thread --> process : CPU 密集

note right of loop
  單執行緒併發
  非阻塞 I/O
  高效能處理
end note

@enduml

此圖示說明瞭 Kafka 日誌分段的管理方式以及如何根據保留策略刪除舊資料。

檔案格式與零複製最佳化

Kafka 的檔案格式與網路傳輸格式相同,這使得 Kafka 可以使用零複製(zero-copy)最佳化來提高效能。當消費者讀取資料時,Kafka 可以直接將資料從磁碟傳輸到網路,而無需進行額外的複製或解壓縮操作。

程式碼範例:零複製最佳化

// 使用 FileChannel 進行零複製傳輸
FileChannel fileChannel = new RandomAccessFile(file, "r").getChannel();
SocketChannel socketChannel = SocketChannel.open(socketAddress);
fileChannel.transferTo(position, count, socketChannel);

內容解密:

  1. 零複製技術:程式碼使用 FileChannelSocketChannel 實作零複製,直接將檔案內容傳輸到網路。
  2. 效能最佳化:這種方法避免了不必要的資料複製和上下文切換,從而提高了效能。