Flink CDC如何保障數據的一致性
前言
在大規模流處理中,故障是無可避免的。機器會宕機,網絡會抖動。一個可靠的流處理引擎不僅要能高效地處理數據,更要在遇到這些故障時,保證計算結果的正確性。Apache Flink 正是因其強大的容錯機制和一致性保障而聞名。
本文將深入探討 Flink 如何實現其核心的精確一次(Exactly-Once) 狀態一致性,并分析在與外部系統交互時,如何結合冪等性來實現端到端的精確一次語義。
一、從最多一次到精確一次
在流處理中,我們通常關心三種一致性語義:
- At-Most-Once(最多一次): 消息可能丟失,但絕不會重復處理。
- At-Least-Once(至少一次): 消息可能重復處理,但絕不會丟失。
- Exactly-Once(精確一次): 消息肯定被處理且只被處理一次,仿佛故障從未發生。
Flink 的核心優勢在于其原生支持了狀態層面的精確一次語義。這意味著 Flink 內部維護的計數器、窗口狀態或用戶自定義狀態在故障恢復后都能保持絕對正確。
二、 分布式快照cp
Flink 實現精確一次的核心機制是基于 Chandy-Lamport 分布式快照算法的 檢查點(Checkpoint)。
1. 什么是 checkpoint?
Checkpoint 是 Flink 應用在某個時間點的全局一致性快照,它包含了:
- 所有算子(Operator)的狀態(如
sum()
的累加值)。 - 所有數據源(Source)的讀取位置(如 Kafka 的 Offset)。
- 所有正在傳輸中的數據記錄。
這個快照會被持久化到一個可靠的分布式存儲系統(如 HDFS、S3)中。
2. 核心原理:屏障(Barrier)
JobManager(主節點)會定期觸發 Checkpoint。它向所有 Source 算子發送一個特殊的標記,稱為 Checkpoint Barrier。
- 廣播與對齊: Source 算子收到 Barrier 后,會立即快照自己的狀態(記錄當前 Offset),然后將 Barrier 廣播給下游所有算子。下游算子需要收到所有輸入流的 Barrier 后,才會對自己的狀態做快照。這個“等待所有 Barrier 到達”的過程稱為對齊,它是實現精確一次的關鍵。
- 異步快照: 狀態快照是異步執行的,這意味著算子在做快照時,仍然可以處理數據,幾乎不影響性能。
- 確認完成: 每個算子完成自己的快照后,會向 JobManager 發送確認(Ack)。當所有算子都確認后,這次 Checkpoint 才被視為全局完成。
3. 故障恢復:時光倒流
當發生故障時(如某個 TaskManager 宕機),Flink 的容錯機制會自動執行:
- 自動檢測: JobManager 檢測到故障,暫停整個作業。
- 狀態回滾: JobManager 找到最近一次成功的 Checkpoint。
- 重新部署: 重啟整個作業拓撲,并將所有算子的狀態重置到 Checkpoint 記錄的那個時間點。
- 重置源: 通知所有 Source 算子,從 Checkpoint 中記錄的 Offset 開始重新消費數據。
通過這一機制,從上一個 Checkpoint 完成到故障發生之間所處理的所有數據及其產生的所有狀態變更,都被“回滾”了。系統仿佛進行了一次時光倒流,然后從那個保存點重新開始處理,從而保證了內部狀態的精確一次。
三、 端到端的精確一次
上述的 Checkpoint 機制完美保證了 Flink 內部狀態的精確一次。然而,一個完整的流處理應用通常包含:
- 輸入源(Source): 如 Kafka, Pulsar
- 處理邏輯(Flink Job)
- 輸出匯(Sink): 如 MySQL, Elasticsearch, Kafka, HBase
要保證端到端(End-to-End) 的精確一次,就必須確保數據從被源讀取,到處理,再到最終寫入輸出匯的整個過程中,即使發生故障,結果也是精確一次的。
這需要外部系統也參與到 Flink 的分布式快照事務中來。Flink 通過 兩階段提交協議(Two-Phase Commit Protocol, 2PC) 來實現這一點。
兩階段提交 Sink 的工作原理
Flink 提供了通用的 TwoPhaseCommitSinkFunction
抽象類,用于實現支持 2PC 的 Sink。其工作流程與 Checkpoint 周期緊密綁定:
-
預提交階段(Pre-commit):
- 當 Checkpoint Barrier 流過 Sink 算子時,Sink 會觸發
preCommit
操作。 - 此時,Sink 會將當前批次的數據預先寫入外部系統,但不提交(例如,寫入 Kafka 的一個事務中,或者向數據庫寫入一條待提交的數據)。這個操作對外是不可見的。
- Sink 將“預提交是否成功”的信息作為自己的狀態,保存到當前的 Checkpoint 中。這意味著對外部系統的“預提交”動作被原子性地包含在了 Flink 的 Checkpoint 里。
- 當 Checkpoint Barrier 流過 Sink 算子時,Sink 會觸發
-
提交階段(Commit):
- 當 JobManager 收到所有算子的 Ack,確認本次 Checkpoint 全局成功后,它會回調 Sink 算子的
commit
方法。 - Sink 算子此時才正式提交之前預寫入的事務(例如,提交 Kafka 事務),讓數據真正對外可見。
- 當 JobManager 收到所有算子的 Ack,確認本次 Checkpoint 全局成功后,它會回調 Sink 算子的
-
中止階段(Abort):
- 如果 Checkpoint 失敗(比如某個算子沒有成功快照),JobManager 會回調 Sink 算子的
abort
方法。 - Sink 算子則中止之前預提交的事務(例如,回滾 Kafka 事務),清理掉預寫入的數據。
- 如果 Checkpoint 失敗(比如某個算子沒有成功快照),JobManager 會回調 Sink 算子的
通過這種方式,Flink 確保了 Sink 端的數據輸出與自身的 Checkpoint 成功與否保持原子性:要么整個 Checkpoint 成功,數據對外可見;要么整個 Checkpoint 失敗,數據被完全撤銷。
四、 冪等性
兩階段提交協議雖然強大,但也有一些缺點:
- 協議開銷: 預提交、提交、中止等操作需要與外部系統進行多輪交互。
- 外部系統支持: 要求外部系統必須提供事務支持(如 Kafka 0.11+),這并非所有系統都具備。
在這種情況下,冪等性(Idempotence) 提供了一個更輕量級、更簡單的替代方案。
什么是冪等性?
冪等性是指:一個操作被執行一次與被執行多次,對系統產生的副作用(Side Effect)是相同的。
一個經典的例子是:將某個賬戶的余額設置為 100 元。無論你執行這個操作一次、兩次還是一百次,最終的結果都是余額為 100 元。這是一個冪等操作。而將余額增加 100 元則不是冪等的。
如何利用冪等性實現精確一次?
如果我們的 Sink 操作是冪等的,那么 Flink 的“至少一次”語義就可以輕松達到“端到端的精確一次”效果。
-
工作流程:
- Flink 內部仍使用 Checkpoint 機制保證狀態是精確一次的。
- 在 Sink 端,我們設計一個冪等寫入器。
- 當發生故障并從 Checkpoint 恢復時,某些數據可能會被重復處理和重復寫入 Sink(即“至少一次”)。
- 但由于寫入操作是冪等的,即使同一批數據被寫了多次,結果也和只寫一次完全相同。從外部看,效果就是精確一次的。
-
實現關鍵:
- 為每一條數據生成一個唯一標識符(如
UUID
,或由源Topic+分區+Offset
組成)。 - 在寫入外部系統時,以這個唯一ID作為主鍵或唯一索引。
- 當寫入時,如果發現相同ID的數據已存在,則執行覆蓋(
UPDATE
)或忽略(INSERT ... ON DUPLICATE KEY UPDATE
)操作,而不是追加。
- 為每一條數據生成一個唯一標識符(如
適用場景: 數據庫(如 MySQL, HBase, Redis)的 UPSERT
操作,或者任何支持基于主鍵的覆蓋寫入的系統。
五、 總結與對比
機制 | 原理 | 優點 | 缺點 | 適用場景 |
---|---|---|---|---|
Flink 內部 Checkpoint | 分布式快照 + 狀態回滾 | 原生支持,高效可靠 | 只保障內部狀態 | Flink 應用內部 |
兩階段提交 (2PC) | 與 Checkpoint 綁定的預提交和提交 | 真正的端到端精確一次,通用性強 | 延遲較高,需要外部系統支持事務 | Kafka、支持事務的數據庫 |
冪等寫入 | 利用操作的冪等性對抗日志重復 | 實現簡單,延遲低,不要求事務 | 需要設計唯一ID,只能用于支持冪等寫入的系統 | 支持 UPSERT 的數據庫(MySQL, HBase, Redis) |
結論
Flink 通過其精巧的分布式快照機制,為內部狀態提供了堅固的精確一次保障。當需要與外部世界交互時,我們可以根據外部系統的能力,靈活選擇兩階段提交或冪等性方案來實現端到端的精確一次。
- 如果外部系統支持事務,兩階段提交是最標準、最通用的選擇。
- 如果外部系統支持冪等寫入(如多數數據庫),那么采用冪等性方案通常更簡單、更高效。
理解這兩種模式的原理和適用場景,是設計一個高可靠性、高一致性 Flink 流處理應用的關鍵。Flink 的強大之處在于,它為我們提供了這兩種強大的工具,以應對各種復雜的生產環境挑戰。
=========================================================
人生得意須盡歡,莫使金樽空對月!
__一個熱愛說唱的程序員。
今日份推薦音樂:楊宗緯/姚曉棠《我會好好的 (Live版)》
=========================================================