Kafka 的核心模型:Topic、Partition、Offset 與 Consumer Group
· tech
#kafka#data-engineering#messaging
第一篇把 Kafka 的心智模型定成一句話:一條可重播的事件 log。但那條 log 實際上怎麼擺、怎麼平行化、一群消費者又怎麼分工?這篇把 Kafka 的四個核心名詞講透 —— Topic、Partition、Offset、Consumer Group —— 它們合起來決定了吞吐、順序與擴展。
Topic 與 Partition:一條 log 怎麼變成可平行的多條
Topic 是一個具名的事件流,是邏輯上的分類 —— 例如 orders、clicks。但如果一個 topic 就只是「一條 log」,那它的吞吐就被單一檔案、單一機器卡死了。
所以 Kafka 把每個 topic 切成多個 Partition:真正那條 append-only log,其實是 partition,而不是 topic。 Topic 只是「這些 partition 的集合」這個名字。Partition 是 Kafka 一切平行與順序的單位 —— 不同 partition 可以散在不同 broker 上同時讀寫,吞吐就隨 partition 數放大。
Producer 怎麼決定事件進哪個 partition
寫入時,有沒有指定 key,決定了事件落點 —— 也決定了順序保證:
- 有 key:Kafka 對 key 取 hash 再對 partition 數取模,同一個 key 永遠進同一個 partition。把
order_id當 key,同一筆訂單的所有事件就保證落在同一條 log、嚴格有序。 - 沒有 key:平均分散(round-robin / sticky)到各 partition,換取最大吞吐,但放棄跨事件的順序。
這是 Kafka 最關鍵的一個設計選擇:「要哪些事件之間有序」這件事,是你透過 key 自己決定的,不是 Kafka 免費給的。
Offset:事件在 partition 內的位置
Offset 是每筆事件在「它那條 partition」裡單調遞增的序號(0、1、2…)。它只在單一 partition 內有意義,跨 partition 不能比大小。
呼應上一篇的書籤比喻:消費者讀到哪,就是「我在 P0 讀到 offset 5、在 P1 讀到 offset 3」。這個「讀到哪」的位置會被提交(commit)起來,讓消費者重啟後能接著讀 —— 而 commit 的時機,正是下一篇要談的「重複或漏掉」問題的源頭。
Consumer Group:消費者怎麼分工與擴展
單一消費者讀不完怎麼辦?把多個消費者組成一個 Consumer Group,Kafka 會把 partition 分配給組裡的成員,平行分攤。
規則只有兩條,但很關鍵:
- 同一個 group 內,每個 partition 只會指派給一個 consumer。 所以同 group 的消費者是「分攤」一個 topic —— 這讓 Kafka 表現得像個可水平擴展的佇列。
- 不同 group 各自獨立讀全量。 帳務組一個 group、推薦組一個 group,兩邊都讀到每一筆 —— 這讓 Kafka 同時又像個廣播。
兩個推論很實用:
- 平行度的上限 = partition 數。 group 裡的消費者比 partition 還多,多出來的只能閒置 —— 因為一個 partition 不會同時餵給兩個成員。
- 成員變動會觸發重新平衡(rebalance)。 有消費者加入或掛掉,Kafka 會重新分配 partition。rebalance 期間消費會短暫暫停,partition 多、成員多時尤其有感 —— 這是維運上要心裡有數的代價。
順序保證:只在 partition 內成立
把前面收斂成一句最該記住的話:Kafka 只保證「同一個 partition 內」嚴格有序,跨 partition 不保證任何順序。
所以「順序」在 Kafka 裡是設計出來的,不是預設的:你想要哪些事件之間有序,就讓它們用同一個 key、進同一個 partition。 整個 topic 的全域順序?那等於把 partition 數壓成 1 —— 也就放棄了平行,通常不值得。實務上是退一步問:「我真正需要有序的單位是什麼?」往往是「同一筆訂單」「同一個使用者」,而不是「全世界」。
反思
partition 數是最重要、也最難回頭的前期決定
partition 數一次同時決定了三件事:吞吐上限、消費者平行度上限、以及順序的顆粒度。偏偏它「能加不能減」——之後想加 partition 很容易,但加完會打亂既有 key 的 hash 落點(同一個 key 可能換到別的 partition,歷史順序就接不起來了)。所以我的習慣是前期就抓一個有餘裕、但別誇張的數字,寧可一開始稍微多給,也不要上線後才發現卡死又動不得。這是 Kafka 少數「開頭沒想清楚、後面很痛」的設計點。
選 key,就是在設計你的順序與負載
我認為 partition key 的選擇,被低估得很嚴重 —— 它表面上只是「資料進哪條 log」,實際上一次決定了順序與負載分布。key 選太粗(例如固定一個值),所有資料擠進同一個 partition,變成熱點、平行度歸零;key 選得好(例如 order_id),既保住「同一筆訂單有序」,又把負載均勻打散。先想清楚「我的順序單位是什麼」,再回頭挑 key —— 順序需求驅動 key,而不是隨手抓一個欄位。
Consumer Group 是 Kafka 能同時當佇列又當廣播的關鍵
剛學的時候我一直困惑:Kafka 到底是佇列還是發布訂閱?後來才懂,答案藏在 consumer group 這個設計裡 —— 同一個 group 內分攤(像佇列),不同 group 間廣播(像 pub/sub)。 要水平擴一個服務的消費速度,就往同一個 group 加消費者;要讓一個新團隊接這份事件,就開一個新 group。同一套機制兩種用法,這是我覺得 Kafka 模型最漂亮的地方。
但這套順序與分配,還沒回答「會不會重複或漏掉」
這篇講的是「事件怎麼擺、誰來讀」,但有個更尖銳的問題還沒碰:消費者把 offset commit 在哪個時機,決定了崩潰重啟後是重複處理還是漏掉。這正是 Kafka 作為基礎建設最需要被認真對待的部分 —— 投遞保證(delivery guarantee),留到下一篇講 at-least-once、exactly-once 與複本機制時,一次說清楚。