分散式串流平台 Kafka (Kafka)
Kafka 是分散式、append-only 的 commit log,可以同時當「能水平擴展的 message queue」和「能重播的 event stream」用。
核心三件事:① Partition 是擴展與順序的單位(同一 key 進同一 partition、partition 內有序);② Consumer Group 決定每則訊息被處理幾次(一個 partition 只給組內一個 consumer);③ Replication + acks 決定持久化保證。面試一句總結:Kafka is always available, sometimes consistent(CAP 中偏 AP)。
Kafka 是什麼
Apache Kafka 是開源、分散式的 event streaming 平台。它的強項是高吞吐、可水平擴展、持久化,在設定得當(replication + acknowledgment)時對訊息遺失提供很強的保證。
它跨越了兩個角色,差別其實不大 —— 消費模式不同而已:
| 用法 | 消費語意 | 典型場景 |
|---|---|---|
| Message Queue | 每則訊息由 group 裡一個 consumer 處理後即算「被消費」 | 非同步任務解耦、削峰、保證順序 |
| Event Stream | log 保留可重播,多個 consumer group 各自獨立讀同一份資料 | 即時彙總、pub/sub、多下游消費 |
兩種模式下 consumer 都用 offset commit 記錄進度;差別只在「訊息被讀走後會不會留下」與「能不能多組重複消費」。
核心術語(由上而下)
Kafka Cluster
├── Broker 1 ──┐
├── Broker 2 │ 每台 broker = 一台伺服器,存資料 + 服務 client
└── Broker 3 ──┘ broker 越多 → 能存更多資料、服務更多 client
Topic "soccer" ← 邏輯分組,publish/subscribe 的單位
├── Partition 0 → [m0][m1][m2][m3] ... append-only log(實體分組)
├── Partition 1 → [m0][m1][m2] ... 每則訊息有唯一遞增 offset
└── Partition 2 → [m0][m1] ... 不同 partition 可在不同 broker
Producer ──寫──> Topic Consumer ──pull──> Partition
| 名詞 | 是什麼 | 一句記憶 |
|---|---|---|
| Broker | 一台伺服器(實體/虛擬),存資料並服務 client | 加 broker = 加容量 |
| Topic | 訊息的邏輯分組;publish/subscribe 的對象 | 用來「組織」資料 |
| Partition | topic 底下的實體分片,append-only log | 用來「scale」與「保證順序」資料 |
| Offset | 訊息在某 partition 內的唯一遞增序號 | consumer 用它記「讀到哪」 |
| Producer | 把訊息寫進 topic 的程式 | 多 producer 可寫同一 topic |
| Consumer | 從 topic 讀訊息的程式(pull 模式) | 自己控制消費速率 |
| Consumer Group | 一群 consumer 協作消費一個 topic;每 partition 只給組內一個 | 決定「每則訊息處理幾次」 |
| Controller | cluster 內負責管理 metadata、leader 指派與 replication 的角色 | broker 掛了它負責 failover |
Topic 是「邏輯」分組,partition 是「實體」分組。 一個 topic 可以有多個 partition,散落在不同 broker 上。Topic 用來組織資料,partition 用來擴展資料與保證順序。
為什麼需要 Partition:擴展 + 順序
用世足賽即時數據網站當例子(進球、吃牌、換人都要即時更新):
單一 queue + 單一 consumer:
事件暴增 → queue 撐不住,consumer 像被消防水柱灌一樣被壓垮 ❌
水平擴展但「隨機」分散事件:
比賽還沒開始就出現進球、球員還沒犯規就被記牌(順序全亂)❌
依「哪一場比賽」當 key 分配:
同一場比賽的事件 → 同一 partition → 天然按 offset 順序處理 ✅
這就是 Kafka 的核心取捨:順序保證只存在於單一 partition 之內,不是整個 topic 之內。 想保證某類事件有序,就用對的 key 把它們導進同一個 partition。
Kafka 不保證跨 partition 的全域順序。若你需要「整個 topic 嚴格有序」,唯一辦法是只用 1 個 partition —— 但那等於放棄並行度。實務上是「分對 key」讓需要有序的那一群落在同一 partition。
訊息結構與 partition 決定流程
一則訊息(record)有四個欄位,技術上都可不填:
| 欄位 | 用途 |
|---|---|
| value | payload,真正的資料 |
| key | 決定進哪個 partition(hash),是順序與分布的關鍵 |
| timestamp | 建立/寫入時間(注意:partition 內順序由 offset 定,非 timestamp) |
| headers | 類似 HTTP headers 的 key-value metadata(可放 trace id) |
訊息 publish 時分兩步定位:
1. Partition Determination(決定 partition)
partition = hash(key) % num_partitions ← 預設 hash 用 murmur2
- 有 key:同一 key → 同一 partition(partition 內保證順序)
- 無 key:現代 client 用 "sticky" partitioner(先集中一 partition 再輪替,利批次效率)
2. Broker Assignment(對應 broker)
依 controller 維護的 cluster metadata 找出該 partition 的 leader 在哪台 broker
→ producer 直接把訊息送給那台 broker
partition = hash(key) % num_partitions。選錯 key → hot partition(流量集中);選對 key → 資料均勻分布、並行度最大化。討論 scaling 時,從 partition strategy 談起就對了。
Append-only Log:為什麼這樣設計
每個 partition 本質上是一支 append-only 的 log 檔,訊息只往尾端追加,所以 Kafka 常被稱為 distributed commit log。
| 好處 | 原因 |
|---|---|
| Immutability 不可變 | 寫入後不就地修改,只依 retention / log compaction 刪除;簡化 replication、加速 recovery、避開可改寫系統的一致性問題 |
| Efficiency 效能 | 只做尾端追加 → 減少磁碟 seek(seek 通常是儲存瓶頸),可順序寫入 |
| Scalability 可擴展 | 簡單模型利於水平擴展:加 partition、分散到多 broker、複製到多 broker 提升容錯 |
這與 04-Database-Indexing/03-LSM-Tree 的「append-only / 順序寫入避開 seek」是同一套效能哲學。
Offset 與 Consumer Group:每則訊息處理幾次
Topic "orders" (3 partitions) Consumer Group "billing"
┌── P0 ──────────────┐ ───────────► Consumer A (讀 P0)
├── P1 ──────────────┤ ───────────► Consumer B (讀 P1, P2)
└── P2 ──────────────┘ ───────────► (若加 Consumer C → rebalance 分 P2 給它)
規則:一個 partition 在「一個 group 內」只會分給「一個 consumer」
→ 正常情況每則訊息只被組內一個 consumer 處理一次
→ consumer 數量 > partition 數量時,多出來的 consumer 會閒置
- Offset commit:consumer 處理完訊息後,把目前 offset commit 回 Kafka(「我處理完這則了」)。重啟/故障後從上次 commit 的 offset 繼續,所以不漏訊息。
- 多個 consumer group 可各自獨立讀同一份資料(stream / pub-sub 模式)——彼此的 offset 互不影響。
若 consumer 在處理完訊息後、commit offset 前掛掉,重啟後該訊息會再被處理一次。
- 想要 exactly-once 需額外設定:idempotent producer + transactional API,或讓 consumer 端的處理冪等(idempotent)。
- 故障情境下 Kafka 仍不會把同一訊息拆給多個 consumer —— 它只會重送,不會重分。
- 設計取捨:commit 越晚越安全(重做成本低),但重複機率越高。把 consumer 單次工作量壓到最小(見 Web Crawler 拆兩階段)。
Replication:Leader-Follower 與持久化保證
訊息寫入 partition 後,Kafka 靠 replication 保證持久與可用,採 leader-follower 模型:
Partition 0:
Broker 1: [Leader] ← producer 寫入 / consumer 預設讀(2.4+ 可從 follower 讀降延遲)
Broker 2: [Follower] ← 被動從 leader 複製(in-sync replica, ISR)
Broker 3: [Follower] ← 被動從 leader 複製
Leader 掛掉 → controller 把某個 in-sync follower 提升為新 leader → partition 續活
| 角色 / 設定 | 說明 |
|---|---|
| Leader replica | 每 partition 一個,負責寫入(與預設讀取);由 controller 均衡分散到各 broker |
| Follower replica | 不服務 client,被動複製,當備援;leader 掛時可接手 |
| ISR(in-sync replicas) | 已跟上 leader 的 replica 集合;只有 ISR 內的能被提升為 leader |
| replication factor | 每 partition 有幾份 replica,常見 3(1 leader + 2 follower) |
Producer 的 acks 設定決定持久化強度:
| acks 值 | 行為 | 取捨 |
|---|---|---|
acks=0 |
送出就不管 | 最快、可能丟訊息 |
acks=1 |
等 leader 寫入就回覆 | 折衷;leader 在複製前掛會丟 |
acks=all |
等所有 ISR 都收到才回覆 | 持久化最強、延遲最高 |
「Kafka is always available, sometimes consistent」—— 面試官問「Kafka 掛掉怎麼辦」其實不太實際,可以禮貌反問。真正該答的是 consumer 掛掉:
- Offset Management:consumer 重啟時讀上次 commit 的 offset,從那繼續,不漏訊息(但 commit 前掛 → at-least-once 重複)。
- Rebalancing:group 內某 consumer 掛掉,Kafka 把它的 partition 重新分配給剩下的 consumer,確保每個 partition 都有人處理。
Pull-based 消費模型
Kafka 的 consumer 是**主動向 broker 拉(pull)**新訊息,間隔由自己控制,而非 broker push。官方說這是刻意設計:
- consumer 控制消費速率(不會被 push 灌爆)
- 簡化錯誤處理
- 避免拖慢較慢的 consumer
- 利於批次處理
對比 01-Networking/05-Realtime-Protocols 的 push(SSE/WebSocket):Kafka 選 pull 換取背壓(backpressure)天然由 consumer 控制,這正是 12-Ops-and-Reliability/02-Overload-Protection 想要的特性。
Scalability:先估算,再決定要不要談
單一 broker 量級參考(依訊息大小與硬體變動,僅數量級):約可存 1TB 資料、每秒處理約 100 萬則訊息。設計若不超過這個量,可能根本不必談 scaling。
擴展策略:
| 策略 | 做法 |
|---|---|
| 加 broker(水平擴展) | 最直接;但要確保 topic 有足夠 partition,否則新 broker 沒事做 |
| Partitioning Strategy | 面試 scaling 的重點:選對 message key 讓資料均勻分布(managed 服務常動態處理底層,但概念要懂) |
| 以 topic 為單位討論 | 不同 topic 需求不同:高吞吐 topic 多 partition、低吞吐 topic 單 broker 即可;擴展某 topic = 增加其 partition 數 |
Kafka 不是資料庫、也不是存大檔的地方,它存小訊息供快速處理(單則建議 < 1MB,message.max.bytes 雖可調但別硬塞)。
設計 YouTube 時,不要把影片丟進 Kafka 讓 worker 拉。正確做法:影片存 07-Caching-Storage/02-Blob-Storage(S3),Kafka 裡只放一則「指向 S3 位置」的小訊息,worker 拿訊息去處理。
Hot Partition:面試官最愛問
例:Ad Click Aggregator 用 ad id 當 partition key。當 Nike 推出 LeBron 新廣告,那個 partition 爆量 → hot partition。
ad_id = "nike_lebron" → hash → 永遠同一個 partition → 該 partition 過載 🔥
其他 partition 閒置
| 解法 | 做法 / 取捨 |
|---|---|
| 1. No key(預設分散) | 不給 key,Kafka sticky 分散到各 partition;犧牲順序,順序不重要時的好選項 |
| 2. Random salting | ad id 加隨機數/時間戳當 key → 負載均勻,但 consumer 端彙總邏輯變複雜 |
| 3. Compound key | ad id + 另一屬性(地區 / user id 區段)組合 → 更均勻,尤其找得到獨立變化的屬性時 |
| 4. Back pressure | 讓 producer 減速:自建 cluster 可檢查 partition lag,過高就減速;managed 服務常內建 |
例 Design a Web Crawler:要等 raw HTML 確定存進 blob storage 再 commit,否則 consumer 掛掉重啟會重做。consumer 單次做的事越多 → 失敗重做成本越高。
策略:把 consumer 的工作壓到最小。 Web Crawler 拆兩階段:① 下載 HTML(commit)② 解析。這樣任一階段失敗,重做的範圍都小。
可靠性:Retry / DLQ / Performance
Producer Retries:送訊息可能因網路/broker 不可用而失敗。Kafka producer 支援自動重試(retries: 5, initialRetryTime: 100)。務必同時開 idempotent: true,避免重試造成重複送出。
Consumer Retries:Kafka 沒有內建 consumer 重試(注意:09-Messaging-Coord/02-Queue 的 AWS SQS 有)。要自己實作:設一個「失敗訊息」專用 topic,由另一個 consumer 負責重試;重試太多次再移到 dead letter queue (DLQ) 供事後排查。
① Batching:單次 send() 送多則(producer 本來就會 batch 減少網路開銷),或 sendBatch() 跨 topic。
② Compression:送訊息設 compression(支援 GZIP / Snappy / LZ4),讓訊息變小送更快。
③ Partition key:影響最大的往往是 key 選得好不好 —— 均勻分布才能最大化並行。
Topic 由 retention.ms(時間)與 retention.bytes(大小)控制訊息保留多久,預設 7 天。要更長保留可調大,但要考慮儲存成本與效能。這正是「能重播 / 多 group 重讀」的基礎。
面試話術
而是說清楚「我為什麼用 Kafka、用在哪一段、key 怎麼選、順序與重複怎麼處理」。
何時在設計裡加 Kafka:
| 你想要的 | Kafka 怎麼幫 | 範例題 |
|---|---|---|
| 非同步處理工作 | 先回標準畫質,把轉檔任務丟 topic,系統有空再做 | YouTube 上傳轉檔 |
| 保證按順序處理 | 用對的 key 讓相關事件進同一 partition | Ticketmaster 虛擬排隊 |
| producer/consumer 解耦擴展 | 兩端各自擴展,避免一個服務拖垮另一個 | Microservices 解耦 |
| 連續即時處理(stream) | consumer 持續即時彙總新進資料 | Ad Click Aggregator |
| 一份資料給多個下游 | 多個 consumer group 各自獨立讀(pub/sub) | FB Live Comments |
收尾金句:
- 「談 scale 我會先從 partitioning strategy 和 hot partition 處理講起。」
- 「Kafka 優先可用性而非一致性,是 CAP 中的 AP 系統。」
- 「預設 at-least-once,所以下游處理我會做冪等或上 transactional API。」
自我測驗重點
| 問題 | 重點 |
|---|---|
| Topic vs Partition | 邏輯分組 vs 實體分片;組織資料 vs 擴展+順序 |
| 順序保證的範圍 | 只在單一 partition 內,非整個 topic |
| partition 公式 | hash(key) % num_partitions(murmur2);選錯 key → hot partition |
| Consumer Group 規則 | 一個 partition 組內只給一個 consumer;多 consumer 可閒置 |
| 預設消費語意 | at-least-once;exactly-once 需 idempotent + transactional |
| acks 三檔 | 0 / 1 / all;acks=all 等所有 ISR,最強 |
| 單 broker 量級 | ~1TB、~100 萬則/秒(數量級),不超過可能不必談 scaling |
| Hot partition 解法 | no key / salting / compound key / back pressure |
| Anti-pattern | 把大檔塞 Kafka;應存 S3,topic 只放指標 |
| Consumer 重試 | Kafka 無內建(SQS 有);自建 retry topic + DLQ |
| CAP 定位 | AP(always available, sometimes consistent) |
Related Notes
- 09-Messaging-Coord/02-Queue — Kafka 可當 message queue 用;對比 SQS 的內建重試與消費語意
- 09-Messaging-Coord/01-Data-Pipeline — Kafka 是 data pipeline 的核心傳輸層,串接 ingest 與下游處理
- 10-Design-Patterns/03-Long-Running-Tasks — 非同步任務解耦的經典載體(YouTube 轉檔丟 topic)
- 10-Design-Patterns/02-Scaling-Writes — partition 水平分片寫入正是 scaling writes 的手段
- 12-Ops-and-Reliability/03-Reliable-Delivery — at-least-once、冪等、DLQ 是可靠投遞的核心議題
- 12-Ops-and-Reliability/02-Overload-Protection — pull 模型 + back pressure 天然提供背壓保護
- 05-Database-Advanced/03-Replication — leader-follower + ISR 與資料庫複製是同一套模型
- 02-Distributed-Systems/02-CAP-Theorem — Kafka 是偏 AP 的系統(always available, sometimes consistent)
- 02-Distributed-Systems/04-Consistent-Hashing — partition 分配與 key hashing 的延伸對照
- 07-Caching-Storage/02-Blob-Storage — 大檔存 S3、Kafka 只放指標訊息的標準搭配
- 04-Database-Indexing/03-LSM-Tree — append-only / 順序寫入避開磁碟 seek 的同一效能哲學
- 09-Messaging-Coord/05-Zookeeper — 舊版 Kafka 用 Zookeeper 管 controller/metadata(新版改 KRaft)