訊息與協調練習題 (Practice - Messaging & Coordination)
Related Concepts
- 09-Messaging-Coord/01-Data-Pipeline
- 09-Messaging-Coord/02-Queue
- 09-Messaging-Coord/03-Kafka
- 09-Messaging-Coord/04-Distributed-Lock
- 09-Messaging-Coord/05-Zookeeper
| 概念 | 預設做法 | 關鍵點 |
|---|---|---|
| 批次 vs 串流 | 由延遲容忍度決定 | < 10 分必須串流 |
| 批次框架 | Spark(快 MapReduce 10~100×) | 中間結果留記憶體 |
| 串流框架 | Kafka + Flink | Window + Watermark 處理 event time |
| 架構 | Kappa(單套串流)為現代主流 | Lambda 適合超大歷史分析 |
| 轉換時機 | ELT(現代雲端首選) | 改邏輯直接在倉儲重跑,不碰源頭 |
| 容錯 | at-least-once + 冪等寫入 | 比 exactly-once 簡單可靠 |
| 三大模式 | CDC / Fan-out / Enrichment | CDC + Kafka 是同步標準答案 |
| Queue 買到啥 | 非同步 / 解耦 / 削峰填谷 | 不增容量,過載要 backpressure |
| 投遞語義 | at-least-once + 消費端冪等 | ack 處理後丟失 → 重投,message_id 去重 |
| Kafka 順序 | 同一 key → 同一 partition | 順序只在 partition 內,選錯 key → hot partition |
| Consumer Group | 一 partition 組內只給一 consumer | 多 group 可獨立重讀(pub/sub) |
| 分散式鎖 | 先問「真的需要鎖嗎」 | 釋放走 Lua check-and-delete,配 TTL/ephemeral |
| 效率 vs 正確性鎖 | 效率 → Redis TTL;正確性 → fencing | GC pause 讓 TTL 失效 → 需 fencing token |
| ZooKeeper | 協調服務非資料庫,CP | ZNode + ensemble + watch;資料 < 1MB |
Part 1:Batch vs Stream(批次與串流)
Q1(Recall):批次處理和串流處理分別處理什麼狀態的資料?各適合什麼場景?
- Batch:處理「靜止資料」(data at rest),累積一批一起處理 → 報表、大規模轉換、ML 訓練、帳單。優點吞吐高成本低,缺點延遲高
- Stream:處理「移動中資料」(data in motion),每筆產生後立刻處理 → 詐欺偵測、即時儀表板、個人化推薦、告警。優點低延遲,缺點複雜度高
Q2(Recall):為什麼 Spark 比 Hadoop MapReduce 快 10~100×?
MapReduce 每個步驟之間都要把資料寫到磁碟(速度慢)。Spark 把中間結果盡量保留在記憶體裡 → 大幅加速。核心抽象是 RDD 和 DataFrame API。
Q3(Application):面試官問「我的延遲需求是 5 分鐘,用 Spark batch 跑得夠快嗎?」你的回答?
不夠 —— 5 分鐘需求應該轉串流。
Spark batch 一次完整生命週期(cluster 啟動、JVM startup、query planning、讀寫 commit)約 5~8 分鐘。雖然 scheduler 最小間隔 1 分鐘,但 batch job 合理最小間隔約 10 分鐘(排程間隔必須 > job 執行時間 + buffer)。
延遲需求 < 10 分鐘必須用串流架構(Flink / Spark Streaming,窗口設短)。
Q4(Application):Flink 的 Tumbling、Sliding、Session 三種視窗各適合什麼?
- Tumbling(滾動):每 5 分一個、不重疊 → 「過去 5 分鐘的訂單數」
- Sliding(滑動):每 1 分更新、覆蓋過去 5 分 → 「最近 5 分鐘訂單數,每分更新」
- Session(會話):按用戶活動分組,閒置超過 30 分算 session 結束 → 用戶行為 session
Q5(Analysis):什麼是 Event Time vs Processing Time 問題?Flink 用什麼解決遲到事件?
問題:事件的「發生時間」≠「到達處理器時間」(手機離線時產生的事件,網路恢復才送到)。用 processing time 會把離線事件分到錯誤視窗 → 應該用 event time,但要處理遲到事件(該屬於已關閉視窗的事件怎麼辦)。
解法:Watermark —— 處理器對「這個時間點之前所有事件都已到達」的聲明。watermark 推進就關閉對應視窗並輸出。合理設 watermark 延遲(如允許遲到 10 秒)在準確性和輸出延遲間平衡。
Part 2:架構與容錯
Q6(Recall):Lambda 和 Kappa Architecture 的核心差異?各自的主要缺點?
- Lambda:同時跑批次層(正確性)+ 速度層(低延遲),serving 層合併。缺點:維護兩套程式碼(批次+串流同邏輯),任何改動要同步兩處
- Kappa:只用一套串流,需重算歷史就從 Kafka 最早 offset 重播。缺點:重播大量歷史不如 Spark;Kafka 長期保留成本高
現代越來越傾向 Kappa(維護成本低)。
Q7(Application):你要設計分析系統,既要「過去 24h GMV 圖表」又要「即時訂單數」。選什麼架構?
兩個都要:
- 「過去 24h GMV」→ 批次處理可以(不需即時)
- 「即時訂單數」→ 需要串流
這時 Lambda Architecture(批次層保正確 + 速度層保即時)或「批次 + 小視窗串流」的混合方案是合理答案。先確認各指標的延遲需求再決定。
Q8(Recall):三種語意保證(at-most/at-least/exactly-once)的差異?實務上的推薦組合?
- At-most-once:最多一次、可能遺失(最簡單,但遺失通常不可接受)
- At-least-once:至少一次、可能重複(大多數系統預設,失敗重試)
- Exactly-once:精確一次(最強但代價最高,需框架 + 下游協作)
實務推薦:at-least-once + 下游冪等寫入(INSERT ... ON CONFLICT DO NOTHING 或 upsert)—— 即使同筆進來兩次結果也一樣,比追求 exactly-once 簡單可靠。
Q9(Application):ETL 和 ELT 的差異?為什麼 ELT 成為現代雲端首選?
- ETL:先轉換再載入,轉換邏輯在進倉儲前完成。改邏輯(如發現欄位計算 bug)要從 OLTP DB 重新抽取 → 耗時且衝擊線上系統
- ELT:原始資料先載入強大的分析 DB(BigQuery/Snowflake),再用 SQL 在裡面轉換。改邏輯直接在倉儲重跑,不碰源頭
ELT 首選原因:現代雲端倉儲算力強大,原始資料已在倉儲裡,修改轉換邏輯後直接重跑 SQL 即可。
Q10(Application):三個常見管線模式(CDC / Fan-out / Enrichment)各解決什麼問題?
- CDC(變更資料捕捉):不影響線上 DB 下同步到別系統 —— 監聽 WAL,把 INSERT/UPDATE/DELETE 轉 Kafka 事件(Debezium),下游訂閱
- Fan-out(扇出):一個事件觸發多個下游 —— App server 只發一個事件到 Kafka,各下游獨立訂閱(pub/sub),完全解耦
- Data Enrichment(豐富化):補充多來源資料 —— 流過管線時從 side input 動態補(用戶 profile 存 Redis 避免每筆查 DB)
Q11(Analysis):管線中斷幾小時後恢復,如何消化積壓(backlog)?
- 多開 consumer:Kafka partition 數決定最大並發消費數(100 partition → 100 consumer 加速消化)
- 批次補跑:若 Kafka 保留了積壓資料,起一個獨立 Spark batch job 專跑那段時間,和串流並行消化
- 優先級:若積壓好幾天,先處理最新資料(保當前結果及時性),再回頭補舊的
Q12(Analysis):上游系統要加一個欄位,如何做 Schema 演化(Evolution)而不弄壞下游?
- 向後相容(新增欄位、讓欄位可選):舊版消費者讀到有新欄位的資料,直接忽略即可
- 向前相容(移除欄位):新版消費者讀到沒這個欄位的舊資料,需有預設值
用 Apache Avro / Protobuf + Schema Registry(如 Confluent)強制執行相容性規則,讓 schema 變更安全可控。不合 schema 的事件自動被拒,解析失敗的送 DLQ 隔離。
Part 3:Message Queue(訊息佇列)
Q13(Recall):Queue 買到哪三件事?為什麼說它「不會增加系統容量」?
三件事:非同步(producer 丟完即返回)、解耦(producer 不需知道 consumer 是誰/在哪/在線)、削峰填谷(高峰堆在 queue、worker 動態擴容消化)。
Queue 只是緩衝層,不提供任何處理能力。若平均到達率 > 平均處理率,queue 只會無限增長 → 真正解法是 backpressure,不是堆 queue。
Q14(Recall):投遞語義(at-most / at-least / exactly-once)哪個是主流?為什麼會重複投遞?
主流是 at-least-once + consumer 冪等(用唯一 message_id 去重),業務上等效 exactly-once;純正 exactly-once 在分散式幾乎做不到。
重複原因:consumer 處理完但 ack 還沒送到就掛了 → broker 沒收到 ack → 超時重投 → 同一訊息被處理兩次。
Q15(Application):使用者上傳圖片後要做壓縮/縮圖,但有人說「我們延遲要求 < 500ms」。該不該用 queue?
看是哪一段。「立即回 200 OK」這段不該被壓縮阻塞 → 用 queue 讓 API 立刻回應、背景 worker 非同步壓縮,是經典模式。
但若是呼叫方需要立即拿到結果的同步操作(< 500ms SLA)→ 加 queue 會排隊+等拉取+可能重試,幾乎必破 SLA。判準:要即時結果 → 同步;只要「之後會做完」→ queue。
Q16(Analysis):多個 consumer 平行消費後順序就亂了,怎麼救?代價是什麼?
把需要保序的訊息用同一 partition key 路由到同一分區,且該分區只給一個 consumer(如同一 user_id 的事件進同一 partition)。
代價:該 key 的吞吐量被單一 consumer 限制 —— 這是順序與吞吐的根本取捨,Kafka 的 partition 模型正是這樣設計。
Part 4:Kafka
Q17(Recall):Topic 和 Partition 的差別?Kafka 的順序保證範圍到哪?
Topic 是「邏輯」分組(publish/subscribe 的對象,用來組織資料);Partition 是「實體」分片(append-only log,用來擴展與保證順序),散落在不同 broker 上。
順序保證只存在單一 partition 內,不是整個 topic。要某類事件有序就用對的 key 把它們導進同一 partition;要整個 topic 嚴格有序只能用 1 個 partition(放棄並行)。
Q18(Recall):Consumer Group 的核心規則是什麼?acks=all 代表什麼?
一個 partition 在一個 group 內只會分給一個 consumer → 每則訊息組內只被處理一次;consumer 數量 > partition 數時多出來的會閒置。多個 group 可各自獨立讀同一份資料(pub/sub)。
acks=all:producer 等**所有 ISR(in-sync replica)**都收到才回覆 → 持久化最強、延遲最高(對比 acks=0 送出不管、acks=1 等 leader)。
Q19(Application):Ad Click Aggregator 用 ad id 當 partition key,Nike 推 LeBron 廣告後該 partition 爆量(hot partition)。怎麼處理?
視順序需求選:① No key 讓 Kafka sticky 分散(犧牲順序,順序不重要時最簡單)② Random salting(ad id 加隨機數/時間戳,但 consumer 端彙總變複雜)③ Compound key(ad id + 地區/user 區段,更均勻)④ Back pressure(檢查 partition lag 過高就讓 producer 減速)。
Q20(Analysis):設計 YouTube 時有人想把影片直接丟進 Kafka 讓 worker 拉,為什麼是 anti-pattern?正確做法?
Kafka 不是資料庫也不是存大檔的地方,它存小訊息(單則建議 < 1MB)供快速處理;塞大檔會拖垮吞吐與 replication。
正確做法:影片存 Blob Storage(S3),Kafka 裡只放一則「指向 S3 位置」的小訊息,worker 拿訊息再去 S3 處理。
Part 5:Distributed Lock(分散式鎖)
Q21(Recall):釋放鎖為什麼不能裸 DEL?正解是什麼?
裸 DEL 有嚴重 bug:若 A 的鎖已 TTL 過期、B 已拿到鎖,A 才慢吞吞跑來 DEL → 誤刪了 B 的鎖。
正解:取鎖時寫入唯一 value(UUID/token),釋放時用 Lua script 原子地「GET 比對 token 相符才 DEL」(check-and-delete)。
Q22(Recall):為什麼光有 TTL 不能保證互斥?fencing token 怎麼解決?
Client 1 取得鎖後遇到長 GC pause,鎖 TTL 期間到期、Client 2 拿到鎖,Client 1 醒來仍以為自己持鎖 → 兩人同時寫 → 資料毀損(鎖過期是管理者那邊的事,Client 1 不知情)。
Fencing token:每次發鎖附帶單調遞增 token,被保護的資源拒絕比已見過更小的 token 寫入 → 過期持有者寫不進去。
Q23(Application):電商結帳要避免超賣,你會用什麼鎖?為什麼鎖不是最終防線?
用 Redis SET NX EX 鎖住 SKU 約 10 分鐘(結帳視窗),付款完成或逾時釋放 —— 這是效率/體驗鎖(避免重複處理、改善體驗)。
但超賣的最終防線是 DB 的 SELECT ... FOR UPDATE 庫存扣減(交易語意保正確性)。效率鎖偶爾失誤可接受,正確性必須靠 DB 交易或 fencing token 兜底。
Q24(Analysis):面試官問「設計分散式鎖」,你的回答框架是什麼?
分三層:① 先問「真的需要鎖嗎」——能否用單一寫入者(sharding 把同資源路由到同節點)或冪等設計避開競爭。② 效率鎖還是正確性鎖——效率鎖用 Redis TTL(簡單快),正確性鎖要 fencing token 或 ZooKeeper/etcd(zxid/revision 天然可當 fence)。③ 失敗怎麼處理——一律配 TTL/ephemeral 自動釋放,釋放走原子 check-and-delete。
Part 6:ZooKeeper
Q25(Recall):ZooKeeper 三大 primitives 是什麼?三種 ZNode 各自用途?
三大 primitives:階層式 ZNode(資料模型)+ ensemble(多數決叢集)+ watches(變更通知)。
三種 ZNode:Persistent(明確刪除前一直存在 → 設定)、Ephemeral(建立它的 session 結束就自動刪 → 存活偵測/服務發現)、Sequential(名稱加單調遞增序號 → 排序/leader 選舉/鎖)。
Q26(Application):ZooKeeper 的 leader 選舉如何避免羊群效應(herd effect)?
每台建立 sequential ephemeral node,序號最小者當 leader;其餘各自只 watch 序號比自己小一號的那個(不是全部 watch leader)。
leader 掛掉 → 只有「次小」那台被喚醒接任,其餘不受打擾 → 把 O(n) 喚醒降成 O(1)。分散式鎖也用同樣技巧。
Q27(Analysis):ZooKeeper 鎖 vs Redis 鎖該怎麼選?為什麼 Kafka 要從 ZooKeeper 遷到 KRaft?
選鎖:正確性/長時間鎖/階層鎖/金融交易 → ZooKeeper(強一致 CP + ephemeral 自動釋放 + 多節點 watch);高吞吐/短鎖/可容忍極端偶發雙持 → Redis(簡單快)。
Kafka 遷 KRaft:去掉外部 ZooKeeper 解三痛——① 維運複雜度(要另運維 ZK 叢集)② 擴展瓶頸(metadata 規模大時 ZK 吃緊)③ 多一個故障點與一致性邊界。KRaft 把共識內建(Raft 管 metadata),反映「自帶共識 > 依賴外部協調」的趨勢。
| 問題 | 解法 |
|---|---|
| 批次 vs 串流 | 由延遲容忍度決定;< 10 分必須串流 |
| 為何 Spark 快 | 中間結果留記憶體(vs MapReduce 每步寫磁碟) |
| batch 最小間隔 | ~10 分鐘(完整生命週期 5~8 分 + buffer) |
| event time 遲到事件 | Watermark(聲明「此時間前都到了」) |
| 架構選擇 | Kappa(單套)為主流;超大歷史分析 → Lambda |
| 轉換時機 | ELT(改邏輯在倉儲重跑,不碰源頭) |
| 語意保證 | at-least-once + 冪等寫入(勝過追求 exactly-once) |
| 不影響線上 DB 同步 | CDC + Kafka(Debezium) |
| 一事件觸發多下游 | Fan-out(Kafka pub/sub 解耦) |
| backlog 消化 | 多開 consumer / 批次補跑 / 先處理最新 |
| schema 演化 | Avro/Protobuf + Schema Registry;DLQ 隔離壞事件 |
| 面試起手 | 先問延遲需求,別上來就「Kafka + Flink」 |
| Queue 買到啥 | 非同步 / 解耦 / 削峰;不增容量 → 過載要 backpressure |
| 為何重複投遞 | ack 處理後丟失 → 超時重投;at-least-once + 冪等去重 |
| 何時不該用 queue | 同步、強延遲 SLA(< 500ms)的場景 |
| RabbitMQ vs Kafka | 靈活路由/任務隊列 vs 高吞吐可重播 log stream |
| Kafka 順序範圍 | 只在單一 partition 內;同 key → 同 partition |
| Consumer Group 規則 | 一 partition 組內只給一 consumer;多 group 可獨立重讀 |
| Hot partition 解法 | no key / salting / compound key / back pressure |
| Kafka anti-pattern | 大檔塞 Kafka → 改存 S3,topic 只放指標訊息 |
| 釋放鎖的坑 | 不能裸 DEL;Lua 原子 check-and-delete(比對 token) |
| TTL 仍不夠 | GC pause 讓鎖過期但節點以為持鎖 → fencing token |
| 效率鎖 vs 正確性鎖 | 效率 → Redis TTL;正確性 → fencing / ZK·etcd |
| 設計鎖第一問 | 「真的需要鎖嗎」——單寫入者 / 冪等可避開 |
| ZK 三大 primitives | ZNode(資料)+ ensemble(多數決)+ watches(通知) |
| ZK 避免羊群效應 | 只 watch 序號小一號的節點,非全部 watch leader |
| ZK 鎖 vs Redis 鎖 | 強一致/長鎖/階層 → ZK;高吞吐/短鎖/簡單 → Redis |
| Kafka 為何遷 KRaft | 降維運/移瓶頸/減故障點,內建共識取代外部協調 |