前言

在現代分散式系統中,當微服務的數量從個位數增長到數十個,服務之間的通訊方式若仍依賴同步 REST API,系統將面臨嚴峻的「耦合性(Coupling)」與「級聯故障(Cascading Failure)」風險:任何一個下游服務的抖動,都可能沿著呼叫鏈蔓延至整個系統。

Apache Kafka 正是為了解決這個核心問題而生。它是一個以「持久化、高吞吐、分割區(Partitioned)、事件日誌(Log)」為核心設計哲學的分散式串流平台(Distributed Streaming Platform),讓生產者(Producer)與消費者(Consumer)能夠完全解耦,各自以最適合的速率運作。

然而,Kafka 強大能力的背後,伴隨著遠比 REST API 複雜的運維複雜度。本文將以架構師 → 後端工程師 → DevOps/SRE 三個視角,深度解析 Kafka 在 AWS 微服務環境中的實戰場景、常見災難與應對策略。


📖 本文架構導覽

層次 主題
Part 1:部署選型 EC2/ECS/EKS 環境的 Kafka 部署模式選擇
Part 2:實戰場景 事件驅動、訂單流、CDC、跨服務通知
Part 3:架構師視角 Topic 設計、Partition 分配、消費者群組、資料保留策略
Part 4:後端工程師視角 冪等生產者、Exactly-Once、Schema Registry、死信佇列
Part 5:DevOps/SRE 視角 Rebalance 避雷、滾動升級、跨 Region、可觀測性
Part 6:維運雷區清單 生產環境禁忌與常見錯誤設定

Part 1:部署選型 — 自托管 vs AWS MSK vs Redpanda

在 AWS 環境中,部署 Kafka 架構有三條主流路徑:

面向 自行託管 Kafka(EC2/EKS) AWS MSK(Managed Streaming for Kafka) 自建 Redpanda(ECS/EKS)
核心架構 JVM 基礎,依賴 ZooKeeper/KRaft AWS 託管的標準 Kafka C++ 重寫,單一 Binary,無外部依賴
控制度 極高,可調所有 Broker 參數 中,透過 MSK 設定頁控制主要參數 高,架構極簡,容易透過 Docker/ECS 部署
高可用性 需自行設定 Multi-AZ 的 Broker 佈局 開箱即用,Multi-AZ 自動部署 內建 Raft 共識演算法,自動 Leader 選舉與資料同步
維運成本 極高(Kafka 是業界公認最複雜的中介軟體之一) 顯著降低,AWS 負責 Patch 與 HA 中低(遠低於自建 Kafka,適合容器化環境)
費用 較低(EC2 費用) 較高(MSK + 儲存 + 資料傳輸費) 較低(ECS/EC2 費用),且記憶體與 CPU 使用率更高效
適用情境 需要極致調校、有歷史包袱的大型團隊 絕大多數量產環境的首選 想自建但不想碰 JVM/ZooKeeper 泥淖的現代化團隊
新世代選擇 MSK Serverless / Amazon EventBridge Pipes 原生支援 Tiered Storage (直接寫入 S3 降低成本)

【踩坑警告】
許多團隊低估了自行維護原生 Kafka 的成本。Kafka 有 Broker、ZooKeeper/KRaft、Topic 分區、Replication Factor、ISR(In-Sync Replicas)等眾多概念需要同時監控管理。如果團隊的核心價值在於業務,強烈建議直接採用 AWS MSK,省下的維運人力遠大於費用差異。

🌟 新世代的選擇:Redpanda(Kafka API 相容)

若您基於成本或架構考量,決定要在 AWS ECS 或 EKS 上「自行託管」訊息佇列,強烈建議評估 Redpanda 作為 Kafka 的替代方案。它與 Kafka API 100% 相容,但在架構上做出了革命性的簡化:

  1. 極簡的運維體驗:捨棄了 JVM 與 ZooKeeper(或 KRaft),整個服務打包為一個單一的 C++ Binary。只需在 ECS Task Definition 中定義一個 Container 即可啟動節點,徹底消除 JVM 記憶體調優(GC Pause)的噩夢。
  2. 硬體榨取機(Thread-per-core 模型):其底層採用 Seastar 框架,將特定 CPU 核心與記憶體/網路中斷綁定(Bypass Linux Kernel),在相同規格的 EC2 實例上,通常能提供比原生 Kafka 高出數倍的吞吐量與更低/更穩定的 Tail Latency。
  3. 雲原生儲存(Tiered Storage):內建將冷資料自動搬移至 AWS S3 的能力,讓 EBS 磁碟只需保留最近的熱資料,大幅降低長時間保留事件(Event Sourcing)的儲存成本。

⚠️ 原生 Kafka 適合放在 ECS 嗎?

這是許多架構師在考慮「自建(Self-hosted)」時最糾結的問題。雖然紅熊 (Redpanda) 在 ECS 上運作如魚得水,但原生 JVM 版的 Apache Kafka 在 ECS 上其實存在不少隱形的技術坑點

  1. 網路身份識別的矛盾 (Network Identity):Kafka Broker 依賴穩定的 advertised.listeners 供 Client 發現。在 ECS 中,Task 重啟後的內部 IP 往往會變更。除非部署在 EC2 模式並搭配 host 網路模式與固定 ENI,否則若使用 Fargate,必須額外實作 Cloud Map 服務發現,這會顯著增加 Client 端與跨 Broker 通訊配置的複雜度。
  2. 狀態持久化的限制 (Persistent Storage):Kafka 極度依賴磁碟 I/O。在 ECS 中管理 EBS 卷(Volumes)的自動掛載與資料同步,其彈性不如 K8s 的 StatefulSet。在 Broker 故障轉移(Failover)時,磁碟重建與資料同步的速度可能成為系統瓶頸。
  3. JVM 與記憶體吞噬問題:原生 Kafka 需要大量記憶體來支撐 OS Page Cache 以提升 IO 效能。ECS Task 有嚴格的 Memory Limit,若配置不當(Heap 佔比過高),會導致 Page Cache 空間不足,造成劇烈的磁碟 I/O 抖動,這在資源受限的容器環境中非常難以調優。

💡 專業建議

  • 不推薦情境:如果您計畫使用 Fargate 且對效能與穩定性有極高要求。
  • 可考慮情境:使用 ECS on EC2 且團隊具備深度的 EBS 管理經驗與網路調優能力,目標是將硬體資源利用率最大化時。

MSK Serverless vs MSK Provisioned

  • MSK Serverless:無需預估容量,按實際吞吐量計費。適合流量不規律、新專案快速啟動。限制:Partition 數量有上限、部分進階設定不支援。
  • MSK Provisioned:需指定 Broker 數量與 Instance 類型。適合穩定且高吞吐的量產環境,成本可預測且可精細調優。

Part 2:實戰場景

🎯 場景一:事件驅動的訂單系統(Event-Driven Order Processing)

場景描述

電商訂單建立後,需要同時觸發多個下游動作:扣減庫存(Inventory Service)、建立出貨單(Shipping Service)、發送 Email(Notification Service)、更新統計報表(Analytics Service)。

若全部用同步 REST 呼叫串接,每增加一個下游服務就需要修改訂單服務,且任一下游的故障都會拖垮整個建單流程。

Kafka 解法:Order Service 建單成功後,發佈一個 order.created 事件到 Kafka Topic。所有下游服務各自開一個 Consumer Group 訂閱此 Topic,並行消費,互不干擾。Order Service 完全無需知道有哪些下游存在。

🚨 問題:消費者處理失敗後的 Offset 管理災難

Consumer 取得訊息但業務邏輯拋出例外後,Offset 是否應該 Commit?Commit 了,損失這筆資料;不 Commit,訊息會被無限重試,導致消費隊伍卡死(永遠停留在同一筆錯誤訊息)。

  • 現象:ECS 上的 Consumer 服務因一筆格式异常的訊息持續處理失敗,Kafka 的 Consumer Lag 指標急速攀升,後續所有正常訊息全部被堵死。整個訂單建立後的下游流程停擺。
  • 解決方法
    1. 指數退避重試(Exponential Backoff Retry):在 Consumer 內對失敗的訊息先本地重試 3~5 次,每次等待時間翻倍。
    2. 死信主題(Dead Letter Topic, DLT):超過重試次數的訊息,不 Commit、但將其發佈至獨立的 DLT(如 order.created.DLT)並提交 Offset,讓主要消費流程繼續前進。DLT 由獨立的告警與補償機制處理。
    3. Spring Kafka / confluent-kafka 的內建支援:Java 的 Spring Kafka 框架提供了 @RetryableTopic@DltHandler 等標準做法,配合 Backoff 策略開箱即用。

🎯 場景二:變更資料捕獲(CDC — Change Data Capture)

場景描述

當微服務各自擁有獨立資料庫(Database-per-Service 原則),需要將某個服務的 DB 變更即時同步至其他服務(例如:將 User DB 的使用者資料同步到搜尋索引 Elasticsearch,或同步至 Data Warehouse 做 BI 分析)時,Kafka + CDC 是業界標準方案。

做法:使用 Debezium Connector(可部署在 AWS MSK 的 MSK Connect 功能上),監聽 MySQL/PostgreSQL 的 Binlog/WAL,自動將每一筆 INSERT/UPDATE/DELETE 操作轉換成 Kafka 事件(包含 Before / After 欄位值),發佈至對應的 Topic。

🚨 問題:Schema 變更(Schema Evolution)導致 Consumer 崩潰

DB schema 加了一個欄位後,CDC 發出的新格式訊息被尚未更新的 Consumer 讀到,反序列化失敗,Consumer 崩潰。

  • 現象:DBA 為 users 表新增了 phone_number 欄位。因訊息格式新增了 phone_number 屬性,使用舊程式碼的 Consumer(Elasticsearch Sink)在讀取時解析異常,開始報錯並觸發 EKS 的 Pod 熱重啟,Consumer Lag 狂飆。
  • 解決方法:引入 Schema Registry(詳見 Part 4),強制所有 Producer 和 Consumer 使用正式的 Schema 進行序列化,並遵循只允許「向後相容(Backward Compatible)」的 Schema 變更規則(如:新增欄位必須設定預設值)。

🎯 場景三:跨服務通知與 Fan-out(廣播事件)

場景描述

一個使用者等級晉升的事件(user.tier.upgraded),可能需要同時通知:

  • 推播服務(Push Notification)
  • Email 服務
  • 活動積分服務
  • CRM 系統

這是 Kafka Pub/Sub 的最佳使用場景:一個 Topic,多個 Consumer Group 各自獨立消費,可按需增減訂閱者而無需修改發佈方。

🚨 問題:EKS 擴縮容觸發 Consumer Rebalance,導致消費暫停

當 EKS 的 HPA(Horizontal Pod Autoscaler)新增或移除一個 Consumer Pod 時,Kafka 的 Consumer Group 會觸發再平衡(Rebalance)。在 Rebalance 期間,Consumer Group 的所有成員都必須停止消費(Stop-The-World),直到 Partition 重新分配完畢。

  • 現象:系統在白天流量高峰時 HPA 頻繁擴縮容,导致每隔幾分鐘就發生一次 Rebalance,每次卡住 2~10 秒。消費者 Lag 呈現鋸齒狀抖動,下游通知延遲時好時壞。
  • 解決方法:詳見 Part 5 的 Rebalance 最佳化策略。

🎯 場景四:即時資料串流處理(Stream Processing)

場景描述

電商平台需要即時偵測異常的購買行為(如:同一個使用者 10 秒內下了 50 筆訂單),並立即封鎖帳號與退款。這需要在消費 Kafka 事件的同時,對一個時間視窗內的事件計數與聚合。

做法:引入 Kafka Streams(純 Java 函式庫)或 Apache Flink(部署在 EKS 或 AWS EMR)對 Topic 中的即時資料進行有狀態(Stateful)的 Windowing 計算,結果發佈至另一個 Topic 或直接寫入 Redis / DynamoDB 供後續規則引擎判斷。


Part 3:架構師視角

📐 一、Topic 命名設計規範

在多微服務環境中,Topic 命名應清晰反映來源服務、資料領域與事件動作,且易於透過 ACL 權限管理進行隔離。

建議命名格式{environment}.{service}.{domain}.{event-type}

1
2
3
4
5
6
# 正確示範
prod.order-service.order.created
prod.order-service.order.cancelled
prod.user-service.user.tier-upgraded
prod.payment-service.payment.failed
staging.inventory-service.stock.adjusted

命名原則

  • 以**過去式(Past Tense)**命名,強調「事件已發生」而非「指令」(order.created 而非 create-order)。
  • 避免在 Topic 中直接反映消費者(notification-service-queue 是錯誤的),Topic 屬於發佈方的領域,不應與特定消費者耦合。
  • 不同環境(prodstagingdev)應有明確前綴,避免錯讀。

📐 二、Partition 數量設計決策

Partition 是 Kafka 的並行單位:一個 Partition 同一時刻只能被一個 Consumer(同 Consumer Group 內)消費。因此:

1
Consumer Group 的最大有效並行度 = Partition 數量
情境 建議
Partition 數 > Consumer 數 允許;消費者可消費多個 Partition,以後可增加消費者來提升並行
Partition 數 = Consumer 數 最理想的平衡狀態
Partition 數 < Consumer 數 多餘的 Consumer 閒置不消費,浪費資源

Partition 數量設定的黃金法則

  1. 一旦建立就難以減少(可增加,減少代價很高)。建議在評估完期望的最大消費者並行數後,預留 1.5~2 倍空間。
  2. Partition 過多的代價:每個 Partition 在 Broker 上對應獨立的檔案控制代碼,Partition 數過多會增加 Broker 的 File Handle 消耗與 Controller 的 Metadata 負擔。
  3. 建議起點:中型服務的 Topic 通常從 6~12 個 Partition 出發,是兼顧並行度與管理成本的合理選擇。

📐 三、消費者群組(Consumer Group)設計

不同的業務邏輯必須使用獨立的 Consumer Group,各自維護獨立的 Offset 進度,互不干擾:

1
2
3
4
5
Topic: prod.order-service.order.created
├── Consumer Group: notification-svc-group (負責發 Email/推播)
├── Consumer Group: shipping-svc-group (負責建立出貨單)
├── Consumer Group: analytics-svc-group (負責寫入資料倉儲)
└── Consumer Group: fraud-detection-group (負責即時風控)

常見錯誤:多個功能不同的服務使用同一個 Consumer Group,導致 Kafka 將 Partition 分配給其中一個服務,另一個服務就完全看不到該訊息。


📐 四、資料保留與 Compaction 策略

策略 說明 適用場景
Time-based Retention 保留最近 N 天的訊息(retention.ms 事件日誌、審計紀錄、任何有時效性的事件
Size-based Retention 保留最近 N GB 的訊息(retention.bytes 儲存成本敏感的場景,搭配時間限制使用
Log Compaction 只保留每個 Key 的最新一筆訊息(cleanup.policy=compact 最終狀態快照,如商品最新價格、使用者最新資料

Log Compaction 的典型用途:CDC + Log Compaction 結合,讓 Topic 行為近似一個「每個 Key 的最新狀態」資料庫快照(Changelog),新加入的 Consumer 能直接從頭播放,重建出完整的當前狀態,無需依賴外部資料庫的全量導出。


Part 4:後端工程師視角

🛠️ 一、冪等生產者(Idempotent Producer)與 Exactly-Once 語義

網路抖動下,Producer 可能在不確定訊息是否送達的情況下重試,導致重複訊息(Duplicate Messages)。

解法:開啟 Idempotent Producer

在 Producer 的設定中加入 enable.idempotence=true(Kafka 3.0+ 預設值已為 true)。Kafka 會為每條訊息賦予唯一的 Sequence Number,若 Broker 收到重複的序號,會自動去重,讓 Producer 達到 Exactly-Once at the producer level

Exactly-Once Semantics(EOS)— 端到端

若需要從「Consumer 讀取 → 業務處理 → Producer 發佈結果」的整個流程都達到 Exactly-Once,需要:

  1. Producer:開啟 enable.idempotence=truetransactional.id
  2. Consumer:將 isolation.level 設定為 read_committed,只讀取已被 Broker 確認提交的交易訊息。
  3. 業務邏輯:對外部資料庫(RDS/DynamoDB)的寫入必須與 Kafka Offset Commit 在同一個「事務」中(需利用 Outbox Pattern 或資料庫事務 + Kafka 事務組合實現)。

EOS 的代價:吞吐量明顯下降(可達 20%~50%),延遲上升。僅在金流、扣款等絕對不容許重複執行的場景中使用。一般事件通知場景,配合良好設計的冪等消費者(At-Least-Once + Idempotent Consumer)通常已足夠。


🛠️ 二、Schema Registry 與 Schema Evolution

Kafka 的訊息本身是原始 Bytes,沒有內建的 Schema 驗證。若 Producer 和 Consumer 之間的資料格式定義只靠「口頭約定」,隨著微服務迭代,很快就會出現格式不相容的崩潰問題。

Schema Registry 的角色

  • 建立一個中央的 Schema 倉庫(如 Confluent Schema Registry 或 AWS MSK 搭配的 AWS Glue Schema Registry)。
  • Producer 在發佈前,先將訊息依照已登錄的 Avro / Protobuf Schema 序列化,並在訊息中標入 schema_id
  • Consumer 在消費時,依 schema_id 向 Schema Registry 查詢對應的 Schema 進行反序列化。

Schema 相容性規則(Compatibility Rules)

模式 規則 說明
BACKWARD 新 Schema 可讀舊資料 Consumer 先升級,Producer 後升級
FORWARD 舊 Schema 可讀新資料 Producer 先升級,Consumer 後升級
FULL 雙向相容 最安全,要求最嚴格
NONE 不限制 危險,易導致生產事故

建議:生產環境預設採用 BACKWARDFULL 相容模式,並在 CI/CD Pipeline 中加入 Schema 相容性檢查步驟,任何破壞性的 Schema 變更(如:刪除欄位、修改型別)要求人工審核。


🛠️ 三、Outbox Pattern — 解決分散式事務的困境

問題場景(雙寫不一致):

1
2
3
4
服務 A 的業務邏輯:
1. 寫入 RDS 資料庫(成功)
2. 發佈 Kafka 事件(失敗!網路抖動)
→ 資料庫已更新,但 Kafka 事件沒發出去,下游服務永遠不知道這件事

Outbox Pattern 解法

  1. 服務 A 在同一個 DB 事務中,同時將業務資料寫入對應資料表,並且將待發佈的 Kafka 訊息寫入本地的 outbox 資料表。
  2. 獨立的 Relay Process(或 Debezium CDC)持續輪詢 outbox 資料表,發現新紀錄後發佈至 Kafka,發佈成功後標記為「已發送」。
1
2
3
4
5
6
7
8
-- outbox 資料表結構
CREATE TABLE outbox (
id UUID PRIMARY KEY,
topic VARCHAR(255) NOT NULL,
payload JSONB NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
sent_at TIMESTAMPTZ -- NULL 表示尚未發送
);

這樣即可保證:訊息要麼兩邊都成功,要麼兩邊都失敗(DB 事務 Rollback),徹底解決分散式雙寫的一致性問題


🛠️ 四、死信主題(Dead Letter Topic)最佳實務

由 Part 2 的 DLT 概念延伸,以下是完整的 DLT 實作策略:

1
2
3
4
5
6
主題流轉路徑(以重試 3 次為例):
order.created
→ (失敗) order.created.retry-1 (等待 10 秒後重試)
→ (失敗) order.created.retry-2 (等待 30 秒後重試)
→ (失敗) order.created.retry-3 (等待 60 秒後重試)
→ (仍失敗) order.created.DLT (告警 + 人工介入或補償機制)

DLT 中的訊息需包含完整的錯誤上下文(Error Context),以便排查:

  • 原始訊息內容(Payload)
  • 失敗的例外訊息(Exception Message + Stack Trace)
  • 失敗的服務名稱與版本
  • 重試次數與每次失敗的時間戳

Part 5:DevOps / SRE 視角

🔧 一、Consumer Rebalance 最佳化(解決 Stop-The-World 問題)

Kafka 的 Consumer Group 在成員加入或退出時,會觸發全員暫停消費的再平衡(Rebalance)期間,消費者 Lag 必然上升,影響服務的即時性。

問題根源:EKS HPA 擴縮容,或 Rolling Update 滾動重啟,都會頻繁觸發 Group 成員的離開/加入事件。

解決方法

  1. 靜態群組成員(Static Group Membership)

    • 為每個 Consumer Pod 設定一個固定不變的 group.instance.id(如 Pod Name)。
    • 當 Pod 因滾動更新重啟時,Kafka 判定這是「同一個成員回歸」而非「新成員加入」,不觸發 Rebalance。
    • 搭配 session.timeout.ms 設定(如 5 分鐘),允許 Pod 在此時間內重啟回來而無需 Rebalance。
  2. 協作式再平衡(Cooperative Rebalancing,Kafka 2.4+)

    • 傳統再平衡(Eager Rebalance)會讓所有成員先撤銷所有 Partition,再重新分配(全員 Stop-The-World)。
    • 協作式再平衡(partition.assignment.strategy=CooperativeStickyAssignor)只遷移真正需要移動的 Partition,其餘成員繼續消費不中斷,大幅縮短影響範圍。
  3. 優化 Consumer 的 heartbeat.interval.mssession.timeout.ms

    • 縮短 heartbeat.interval.ms 可讓 Broker 更快偵測到真正宕機的 Consumer。
    • 適當延長 max.poll.interval.ms(業務處理時間的 2~3 倍),避免「Consumer 還活著但業務太慢」被 Broker 誤判為死亡並踢出 Group。

🔧 二、Broker Rolling Update 與滾動升級

自行管理 Kafka Cluster(EC2 或 K8s StatefulSet)時,Broker 升級不當可能導致資料遺失或服務中斷。

前置條件檢查清單

  • min.insync.replicas(通常設為 2):確保寫入被至少 2 個 Replica 確認後才 ACK,升級期間 Broker 下線不影響可用性。
  • replication.factor(通常設為 3):確保每個 Partition 有 3 份副本。
  • 確認 Under-Replicated Partitions = 0:使用 kafka-topics.sh --describe --under-replicated-partitions 確保目前所有 Partition 都處於完全同步狀態,才能安全下線一個 Broker。

滾動升級步驟

  1. 確認 under.replicated.partitions 為 0。
  2. 對最先升級的 Broker 執行 Graceful Shutdown(觸發 Leader Election 將 Leadership 轉移給其他 Broker)。
  3. 等待 Broker 完成版本升級並重新加入 Cluster,確認 under.replicated.partitions 回歸 0。
  4. 對下一個 Broker 重複步驟 2~3。

AWS MSK 的好處:MSK 的 Broker 升級支援滾動升級(Rolling Update),由 AWS 負責協調上述流程,顯著降低升級風險。


🔧 三、跨 Region 架構(MirrorMaker 2 / MSK Replication)

當需要跨 Region 的 Kafka 資料複製時(如:主 Region 在 ap-northeast-1,DR 在 us-east-1),業界常見方案有兩種:

方案 說明 適用情境
MirrorMaker 2(MM2) Apache Kafka 官方的跨 Cluster 複製工具,部署在 Kafka Connect 框架內 自行管理 Kafka / MSK 的跨 Region DR
MSK Replication(AWS 原生) AWS 在 2023 年推出,無需自行部署 MM2,由 AWS 全託管 MSK 環境的首選,配置簡單、自動維護

注意事項

  • Kafka 的跨 Region 複製是單向的非同步複製(Active-Passive);在 DR 場景下,若主 Region 完全失效,Consumer Offset 可能無法精確同步至 DR Region(存在少量訊息重複消費的可能)。
  • MM2 / MSK Replication 在複製 Topic 時,目標端的 Topic 名稱預設會加上來源 Cluster 的名稱作為前綴(如 source-cluster.prod.order-service.order.created),Consumer 在 DR 切換時需相應調整配置。

🔧 四、可觀測性(Observability)三支柱

支柱 工具 / 方法 關鍵指標或設定
Metrics(指標) AWS CloudWatch (MSK) KafkaDataLogsDiskUsedActiveControllerCount(必須為 1)、UnderReplicatedPartitions(必須為 0)、OfflinePartitionsCount(必須為 0)
Consumer Lag(消費延遲) AWS CloudWatch + Burrow / Kafka UI / MSK 內建 EstimatedTimeLagConsumerLag:這是最直接反映消費者跟不跟得上的核心指標,建議設定告警閾值
Metrics(指標) Prometheus + kafka_exporter / JMX Exporter(EKS Sidecar) Broker 的 JVM GC Pause 時間、Request Queue Size、Network 吞吐量
Logging(日誌) MSK + CloudWatch Logs 開啟 Broker Log 輸出至 CloudWatch,設定 log4j.logger.kafka.controller 以查看 Leader Election 日誌

建議必設的 MSK CloudWatch 告警

1
2
3
4
5
- UnderReplicatedPartitions > 0     → Replica 同步異常,資料可靠性降低
- OfflinePartitionsCount > 0 → 有 Partition 無 Leader,對應到 Topic 完全不可用!最高優先級
- ActiveControllerCount != 1 → Kafka 控制器異常,叢集可能陷入混亂
- ConsumerLag > (業務可接受的延遲閾值) → 消費者追不上生產者,下游處理嚴重延遲
- KafkaDataLogsDiskUsed > 85% → 磁碟快滿,Broker 將開始強制截斷日誌

Part 6:維運雷區清單

☠️ 禁忌一:replication.factor=1(資料零容錯)

這是最致命的設定錯誤。當一個 Partition 只有一個副本時,只要該 Broker 宕機(AWS EC2 Instance 故障、EBS 磁碟毀損),這個 Partition 的所有資料將永久遺失,且該 Topic 對這段時間的所有訊息將完全不可用。

解法:生產環境所有 Topic 的 replication.factor 必須 ≥ 3,且與 min.insync.replicas=2 搭配,確保寫入資料的持久性。


☠️ 禁忌二:acks=0acks=1(Producer 不等確認)

  • acks=0:Producer 發出去就算成功,完全不等 Broker 確認,最快但訊息可能在網路中丟失。
  • acks=1:只等 Leader Broker 確認,若 Leader 在將訊息複製給 Follower 前崩潰,訊息會遺失。
  • acks=all(或 -1):等所有 ISR(In-Sync Replicas)都確認後才返回,結合 min.insync.replicas=2,是生產環境的唯一正確設定

☠️ 禁忌三:Partition Key 設計不當導致資料傾斜(Data Skew)

Kafka 的 Partition 分配機制:Partition = hash(key) % partitionCount。若使用了基數(Cardinality)極小的欄位作為 Partition Key(如:is_vip=true/false,只有 2 個值),所有訊息必然只落在 2 個 Partition。

  • 現象:部分 Partition 的訊息量是其他 Partition 的數十倍,對應的 Consumer 過載,其他 Consumer 閒置,Consumer Lag 嚴重的熱點集中在少數幾個 Consumer 上。
  • 解法:選擇高基數(High Cardinality)的欄位作為 Partition Key(如 userIdorderId),確保訊息均勻分散。若業務需要相同 userId 的事件必須有序,則必須承受部分不均的代價,並適當規劃 Partition 數量。

☠️ 禁忌四:消費者 auto.offset.reset=earliest + 程式碼上線前忘記重設 Offset

在新的 Consumer Group 第一次啟動時,auto.offset.reset 決定從哪裡開始消費:

  • earliest:從最早的訊息(依保留策略內)開始,可能消費大量歷史訊息。

  • latest:只消費此後新產生的訊息。

  • 現象:新服務上線,auto.offset.reset=earliest,結果開始從 7 天前的歷史訊息消費,向 7 天前的所有使用者重複發送了一遍推播通知或 Email,造成嚴重的業務事故。

  • 解法:明確管理 Consumer Group 的初始 Offset(用 kafka-consumer-groups.sh --reset-offsets 命令),並在 CI/CD 中加入 Consumer Group Offset 初始化步驟,絕不依賴 auto.offset.reset 的預設行為在生產環境中自動決定起點。


結語

Kafka 不是一個「裝上去就能跑」的工具,它是一套需要深刻理解其分散式日誌哲學才能正確使用的基礎設施。從 Topic 設計、Partition 分配、Consumer Group 隔離,到 Schema Evolution、Exactly-Once 的代價,再到 DevOps 層面的 Rebalance 優化與可觀測性建設,每一層都藏著讓系統瞬間崩盤的地雷。

然而,一旦掌握了這些知識並建立正確的架構模式,Kafka 帶來的服務解耦、事件溯源(Event Sourcing)能力與近乎線性的水平擴展能力,將是微服務系統面對業務爆炸式增長時最強大的武器。

延伸閱讀:若您的場景對訊息延遲要求極端嚴格(毫秒等級)或流量規模相對輕量,建議參考本站的 Redis 實戰指南,評估以輕量的 Redis Streams 作為替代的可能性。所有工具都有其適用的甜蜜點,沒有最好的架構,只有最適合當前場景的架構。