首頁

目前文章總數:182 篇

  

最後更新:2025年 05月 31日

0093. Kafka Kraft 模式部署與高可用性模擬測試(附完整 docker-compose)

日期:2025年 05月 10日

標籤: Docker Docker-Compose Container Ubuntu Linux Kafka Kafka UI

摘要:C# 學習筆記


應用所需:1. Linux Ubuntu (本篇 22.04)
     2. 已安裝 Docker
     3. Kafka UI 管理介面已安裝
解決問題:1. 介紹 Kafka 3.8.0 版本以後的 Kraft模式與 Zookper 管理集群的差異
     2. 如何在開發環境中切出 3個 Broker 副本,模擬生產架構
相關參考:0089. Linux Ubuntu 上使用 Docker Compose 快速部署 Kafka
相關參考:0090. Kafka 開發指南:.NET Core 上 Kafka 事件流(消息隊列)推送
範例專案:範例
基本介紹:本篇分為 3 部分。
第一部分:安裝 Kafka 3.8.0 與 KRaft 模式介紹
第二部分:開發環境 - 單點多 Broker & 部署配置
第三部分:DEMO 單機多副本因子高可用






安裝 Kafka 3.8.0 與 KRaft 模式 - 介紹

Step 1:Kafka 的 KRaft 模式介紹

Kafka 從 3.8.0 開始,可以獨立使用 KRaft 並且不再使用 Zookeeper 做集群管理 Kraft 模式官網
官方有以下概述:

Apache Kafka Raft (KRaft) is the consensus protocol that was introduced in KIP-500 to remove Apache Kafka’s dependency on ZooKeeper for metadata management. This greatly simplifies Kafka’s architecture by consolidating responsibility for metadata into Kafka itself, rather than splitting it between two different systems: ZooKeeper and Kafka. KRaft mode makes use of a new quorum controller service in Kafka which replaces the previous controller and makes use of an event-based variant of the Raft consensus protocol.


簡要的說:簡化了 Kafka 的架構。KRaft 模式利用 Kafka 中的新仲裁控制器服務來取代先前的控制器。
徹底改變了 Kafka 的架構基礎,使其成為更自包含、更有效率的分散式系統


Step 2:Kafka 的 KRaft 模式 - 特性

關於 Kraft 整理以下特性:


1. KRaft 的本質

1. 基於 Raft 的共識演算法 採用了 Raft 共識演算法來確保元資料在多個節點間的一致性。 Raft 以其簡單易懂的特性而聞名,相比其他共識演算法更容易實現和維護。
2. 元資料主題 KRaft 使用特殊的內部主題(__cluster_metadata)來儲存所有叢集元數據,就像普通的 Kafka 主題一樣,但具有特殊的處理方式



2. KRaft 的核心組件

1. 控制器(Controller) 負責處理元資料操作
  維護集群狀態
  處理領導者選舉
  可獨立運作或與 Broker 角色共存
2. Quorum 控制器 多個控制器節點組成一個 Quorum(法定人數集合)
  透過 Raft 協定在控制器之間複製元數據
  確保元資料的高可用性和一致性
3. Active 控制器 在任何時刻,只有一個控制器是活躍的(Active Controller)
  活躍控制器負責處理所有元資料請求
  如果活躍控制器失效,Quorum 會自動選舉新的活躍控制器



3. KRaft 提供 Kafka 新的部署模式

1. Combined 模式 節點同時擔任控制器和 Broker 角色
  適用於小型到中型集群
  資源利用率更高
2. Separated 模式 控制器和 Broker 角色分離到不同節點
  適用於大型生產環境
  提供更好的隔離性和可維護性



4. KRaft 讓 Kafka 變得更高效能

1. 高效能元資料操作 減少元資料操作的網路跳躍
  更新資料延遲更低
2. 簡化的架構 單一系統,不依賴外部組件(不依賴 Zookeeper)
  統一的配置、監控和日誌處理
3. 可擴展性 支援更大規模的主題和分區數量
  更有效率的元資料管理演算法



Step 3:KRaft 與 Zookeeper 差異對比

特性 ZooKeeper 架構 KRaft 架構
架構組成 雙組件:Kafka Broker + 外部 ZooKeeper 集群 單組件:只需 Kafka 伺服器(可分為控制器和 Broker 角色)
元數據存儲 存儲在 ZooKeeper 中,非同步複製到 Broker 直接存儲在 Kafka 內部的元數據主題中
一致性協議 ZAB (ZooKeeper Atomic Broadcast) Raft 共識協議
擴展性 支援約幾千個分區,大規模集群下性能受限 可支援數萬至十萬級分區數量
性能 元數據操作需跨系統通信,有額外延遲 元數據變更更快,直接在 Kafka 內處理
運維複雜度 需維護兩套系統,配置和監控更複雜 單一系統,簡化部署和維護
故障排查 需檢查兩個系統的日誌和狀態 只需檢查 Kafka 系統
資源消耗 需要額外的伺服器資源運行 ZooKeeper 可以複用現有 Kafka 資源,降低總體消耗
啟動流程 Kafka 依賴 ZooKeeper 先啟動 直接啟動 Kafka 即可
配置管理 需要管理兩套配置 只需管理 Kafka 配置


少了 ZooKeeper 可以提升整體效能,並且簡化架構

Step 4:Kafak 支持模式的版本迭代說明

建議使用 Kafka 3.8.0 版本以上,因為 Kraft 穩定(Release)、成熟、並且從這版本計畫移除 ZooKeeper

Kafka 版本 ZooKeeper 支援 KRaft 支援 備註
< 2.8.0 必需 不支援 只能使用 ZooKeeper 模式
2.8.0 必需 實驗性支援 KRaft 首次引入,僅用於測試
3.0.0 支援 預覽版 KRaft 可用於有限生產使用
3.1.0 - 3.2.x 支援 改進版預覽 KRaft 穩定性顯著提升
3.3.0 - 3.4.x 支援 生產就緒 KRaft 開始推薦用於生產環境
3.5.0+ 支援但不推薦 完全支援 KRaft 成為推薦部署方式
3.8.0 (您的版本) 支援但不推薦 完全成熟 KRaft 是首選模式,ZooKeeper 模式計劃逐步淘汰
4.0.0 (預計) 可能移除 標準模式 可能完全移除 ZooKeeper 依賴




第二部分:開發環境 - 單點多 Broker & 部署配置

Step 1:Docker-Comopse 檔案說明

使用的 docker-compose.yml 有以下腳本,分別實現以下 2 個項目的容器化安裝:

項目 項目
kafka 安裝 3 個容器 Kafka 0 ~ 2
  PLAINTEXT://0.0.0.0:9092 container 內部互連用 (9092, 9094, 9096)
  PLAINTEXT://host.docker.internal:9093(或外部 IP) 給宿主機或外部服務用(9093, 9095, 9097)
kafka-ui 提供 kafka 介面化檢視、操作;對外的 Web 操作是開放 8380 Port
version: '3'
services:
  kafka-0:
    image: bitnami/kafka:3.8
    container_name: kafka-0
    ports:
      - "9092:9092"
      - "9093:9093"  
    environment:
      - KAFKA_CFG_NODE_ID=0
      - KAFKA_CFG_PROCESS_ROLES=broker,controller
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka-0:9093,1@kafka-1:9093,2@kafka-2:9093
            
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.51.28:9092
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
            
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      - KAFKA_CFG_NUM_PARTITIONS=3
      - KAFKA_CFG_DEFAULT_REPLICATION_FACTOR=3
      - KAFKA_CFG_MIN_INSYNC_REPLICAS=2
            
      - KAFKA_KRAFT_CLUSTER_ID=abcdefghijklmnopqrstuv
    volumes:
      - kafka_data_0:/bitnami/kafka
    restart: unless-stopped

  kafka-1:
    image: bitnami/kafka:3.8
    container_name: kafka-1
    ports:
      - "9094:9092"
      - "9095:9093"  
    environment:
      - KAFKA_CFG_NODE_ID=1
      - KAFKA_CFG_PROCESS_ROLES=broker,controller
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka-0:9093,1@kafka-1:9093,2@kafka-2:9093
      
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.51.28:9094
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
      
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      - KAFKA_CFG_NUM_PARTITIONS=3
      - KAFKA_CFG_DEFAULT_REPLICATION_FACTOR=3
      - KAFKA_CFG_MIN_INSYNC_REPLICAS=2
      
      - KAFKA_KRAFT_CLUSTER_ID=abcdefghijklmnopqrstuv
    volumes:
      - kafka_data_1:/bitnami/kafka
    restart: unless-stopped

  kafka-2:
    image: bitnami/kafka:3.8
    container_name: kafka-2
    ports:
      - "9096:9092"
      - "9097:9093"  
    environment:
      - KAFKA_CFG_NODE_ID=2
      - KAFKA_CFG_PROCESS_ROLES=broker,controller
      - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@kafka-0:9093,1@kafka-1:9093,2@kafka-2:9093
      
      - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
      - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.51.28:9096
      - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT
      - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER
      - KAFKA_CFG_INTER_BROKER_LISTENER_NAME=PLAINTEXT
      
      - KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=true
      - KAFKA_CFG_NUM_PARTITIONS=3
      - KAFKA_CFG_DEFAULT_REPLICATION_FACTOR=3
      - KAFKA_CFG_MIN_INSYNC_REPLICAS=2
      
      - KAFKA_KRAFT_CLUSTER_ID=abcdefghijklmnopqrstuv
    volumes:
      - kafka_data_2:/bitnami/kafka
    restart: unless-stopped

  kafka-ui:
    image: provectuslabs/kafka-ui:latest
    container_name: kafka-ui
    ports:
      - "8380:8080"
    environment:
      - KAFKA_CLUSTERS_0_NAME=local-kraft
      - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS=kafka-0:9092,kafka-1:9092,kafka-2:9092      
    depends_on:
      - kafka-0
      - kafka-1
      - kafka-2
    restart: unless-stopped

volumes:
  kafka_data_0:
  kafka_data_1:
  kafka_data_2:




一、每個容器都有以下這段,目的是告訴 Kafka,哪些節點是 Controller(KRaft mode 下的 Raft quorum) 的投票成員。
等於為 3 個容器(Id 0~2),設定了 3 個 KRaft Controller 的集群

KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.51.28:9096



二、每個容器都有以下這段,目的是設定該節點要開啟哪些通訊協議與端口

KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093
1. PLAINTEXT://:9092:開給 producer / consumer / kafka-ui 等客戶端用的通訊 port
2. CONTROLLER://:9093:給 Controller 之間互相溝通(Raft 投票)的內部 port
備註:9092 是 client-facing port,9093 是 Raft 通訊 port,不能搞混。



三、3 個容器都各自有自己的這段,目的是設定該節點要開啟哪些通訊協議與端口
告知外部服務如果要連線,請走這個 listener


Kafka-0

KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.51.28:9092


Kafka-1

KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.51.28:9094


Kafka-2

KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://192.168.51.28:9096
1. PLAINTEXT://:9092:開給 producer / consumer / kafka-ui 等客戶端用的通訊 port
2. CONTROLLER://:9093:給 Controller 之間互相溝通(Raft 投票)的內部 port
備註:9092 是 client-facing port,9093 是 Raft 通訊 port,不能搞混。


至此會看到架構下會直接連線到 Kafka 對應容器,C# 程式中還需要配置 bootstrap server


Step 2:部署 Kafka - 建立檔案

將 docker-compose.yml 放到 Ubuntu 上


Step 3:部署 Kafka - 安裝

在對應根目錄下輸入 Shell 指令,進行安裝

docker-compose up -d




Step 4:部署 Kafka - 安裝完成

安裝完成後會出現 1 個 Kafka-UI 與 3 個 Kafka 0~2 容器。


Step 5:進入 Kafka-UI Web 畫面

DashBoard -> Brokers 同時運行的會有 3 個,當前為 Active 的為 Broker ID 0


Step 6:進入 Kafka-UI 檢查副本因子數

輸入 Replicas 得到 2 的數量 (min.insync.replicas)


Step 7:進入 Kafka-UI 副本因子 - 說明

有 3 個 Kafka Brokers,而 Topic 的 副本因子(Replication Factor)設為 2
表示 即使 某 1 個 broker 掛掉,仍然有另一個副本能接手(高可用) :

1. Kafka 會確保每個 partition 有 1 個 leader + 1 個 follower
2. 這 2 個副本會存在 不同的 broker 中
3. 占用空間量 3 個都活著的情況下,會變為 3 倍,因為副本


舉例來說,會看到以下的結構,在後續生產者產生資料到 Kafka 中:
※因此,單機模式下,有 2 個 Kafka(Broker) 容器 關閉,生產者就 100% 不能將資料送往 Kafka
※min.insync.replicas 設為 1 會失去高可用 & 資料一致性的問題,通常生產環境不會如此設定

Partition Leader (Broker) Follower (Broker)
0 kafka-0 kafka-1
1 kafka-1 kafka-2
2 kafka-2 kafka-0



Step 8:嘗試發送生產者訊息 - 發送

啟動範例專案,進入 Debug 模式,嘗試發送


Step 9:嘗試發送生產者訊息 - 收到消息

回到 DashBoard 可以發現有 Leader(Broker) 與 機器相關的資訊,成功發送


Step 10:嘗試發送生產者訊息 - 副本因子狀態

當前容器都是正常的狀況下,所有訊息都可以正常收到


第三部分:DEMO 單機多副本因子高可用

Step 1:當前架構 - 未添加集群

當前架構下,如果沒有在 bootstrap 添加所有 Broker ,當發生 9092 關閉時,就會直接造成錯誤
這失去了副本因子的高可用


當前範例專案,設定檔只有配置 9092,單一容器

Step 2:關閉 1 台容器 - Kafka-0

將 kafka-0 (Broker) Port:9092 關閉


Step 3:嘗試發送 - 異常

可以發現產生生產者訊息時,會出現大量訪問異常,無法訪問,但實際上 Kafka-1 與 Kafka-2 是正常的


Step 4:添加所有 Broker 集群 - 設定

在 Confing 檔按下添加 Kafka-1 與 Kafka-2 容器位置


Step 5:添加所有 Broker 集群 - 再次發送 & 成功

再次發送,並且成功發送,完成了高可用


Step 6:關閉 2 台容器

這次再關閉一台容器,因為 min.insync.replicas = 2 ,預期不可發送


Step 7:關閉 2 台容器 - 不可發送

再次進行發送,雖然 Kafka-2 (Broker)活著,但沒有達到備份副本因子的最低要求 2 ,因此視為異常


Step 8:Kafka 高可用測試

即使 部分節點故障,仍能保證:

資料可寫入(只要副本同步夠)

資料可讀取(有 leader 存在)

故障節點恢復時,自動追上 missed 的資料(replica sync)