詳解 Flink 實時應用的肯定性

做者:林小鉑(網易遊戲)html

肯定性(Determinism)是計算機科學中十分重要的特性,肯定性的算法保證對於給定相同的輸入老是產生相同的輸出。在分佈式實時計算領域,肯定性是業界一直難以解決的課題,由此致使用離線計算修正實時計算結果的 Lambda 架構成爲大數據領域過去近十年的主流架構。算法

而在最近幾年隨着 Google The Dataflow Model 的提出,實時計算和離線計算的關係逐漸清晰,在實時計算中提供與離線計算一致的肯定性成爲可能。本文將基於流行實時計算引擎 Apache Flink,梳理構建一個肯定性的實時應用要知足什麼條件。數據庫

肯定性與準確性

比起肯定性,準確性(Accuracy)多是咱們接觸更多的近義詞,大多數場景下二者能夠混用,但其實它們稍有不一樣: 準確的東西必定是肯定的,但肯定性的東西未必百分百準確。在大數據領域,很多算法能夠根據需求調整成本和準確性的平衡,好比 HyperLogLog 去重統計算法給出的結果是有必定偏差的(所以不是準確的),但卻同時是肯定性的(重算能夠獲得相同結果)。apache

要分區肯定性和準確性的緣故是,準確性與具體的業務邏輯緊密耦合難以評估,而肯定性則是通用的需求(除去少數場景用戶故意使用非肯定性的算法)。當一個 Flink 實時應用提供肯定性,意味着它在異常場景的自動重試或者手動重流數據的狀況下,都能像離線做業通常產出相同的結果,這將很大程度上提升用戶的信任度。緩存

影響 Flink 應用肯定性的因素

投遞語義

常見的投遞語義有 At-Most-Once、At-Least-Once 和 Exactly-Once 三種。嚴格來講只有 Exactly-Once 知足肯定性的要求,但若是整個業務邏輯是冪等的, 基於 At-Least-Once 也能夠達到結果的肯定性。架構

實時計算的 Exactly-Once 一般指端到端的 Exactly-Once,保證輸出到下游系統的數據和上游的數據是一致的,沒有重複計算或者數據丟失。要達到這點,須要分別實現讀取數據源(Source 端)的 Exactly-Once、計算的 Exactly-Once 和輸出到下游系統(Sink 端)的 Exactly-Once。框架

其中前面兩個都比較好保證,由於 Flink 應用出現異常會自動恢復至最近一個成功 checkpoint,Pull-Based 的 Source 的狀態和 Flink 內部計算的狀態都會自動回滾到快照時間點,而問題在於 Push-Based 的 Sink 端。Sink 端是否能順利回滾依賴於外部系統的特性,一般來講須要外部系統支持事務,然而很多大數據組件對事務的支持並非很好,即便是實時計算最經常使用的 Kafka 也直到 2017 年的 0.11 版本才支持事務,更多的組件須要依賴各類 trick 來達到某種場景下的 Exactly-Once。運維

整體來講這些 Trick 能夠分爲兩大類:分佈式

  • 依賴寫操做的冪等性。好比 HBase 等 KV 存儲雖然沒有提供跨行事務,但能夠經過冪等寫操做配合基於主鍵的 Upsert 操做達到 Exactly-Once。不過因爲 Upsert 不能表達 Delete 操做,這種模式不適合有 Delete 的業務場景。
  • 預寫日誌(WAL,Write-Ahead-Log)。預寫日誌是普遍應用於事物機制的技術,包括 MySQL、PostgreSQL 等成熟關係型數據庫的事物都基於預寫日誌。預寫日誌的基本原理先將變動寫入緩存區,等事務提交的時候再一次所有應用。好比 HDFS/S3 等文件系統自己並不提供事務,所以實現預寫日誌的重擔落到了它們的用戶(好比 Flink)身上。經過先寫臨時的文件/對象,等 Flink Checkpoint 成功後再提交,Flink 的 FileSystem Connector 實現了 Exactly-Once。然而,預寫日誌只能保證事務的原子性和持久性,不能保證一致性和隔離性。爲此 FileSystem Connector 經過將預寫日誌設爲隱藏文件的方式提供了隔離性,至於一致性(好比臨時文件的清理)則沒法保證。

爲了保證 Flink 應用的肯定性,在選用官方 Connector,特別是 Sink Connector 時,用戶應該留意官方文檔關於 Connector 投遞語義的說明[3]。此外,在實現定製化的 Sink Connector 時也須要明確達到何種投遞語義,能夠參考利用外部系統的事務、寫操做的冪等性或預寫日誌三種方式達到 Exactly-Once 語義。函數

函數反作用

函數反作用是指用戶函數對外界形成了計算框架意料以外的影響。好比典型的是在一個 Map 函數裏將中間結果寫到數據庫,若是 Flink 做業異常自動重啓,那麼數據可能被寫兩遍,致使不肯定性。對於這種狀況,Flink 提供了基於 Checkpoint 的兩階段提交的鉤子(CheckpointedFunction 和 CheckpointListener),用戶能夠用它來實現事務,以消除反作用的不肯定性。另外還有一種常見的狀況是,用戶使用本地文件來保存臨時數據,這些數據在 Task 從新調度的時候極可能丟失。其餘的場景或許還有不少,總而言之,若是須要在用戶函數裏改變外部系統的狀態,請確保 Flink 對這些操做是知情的(好比用 State API 記錄狀態,設置 Checkpoint 鉤子)。

Processing Time

在算法中引入當前時間做爲參數是常見的操做,但在實時計算中引入當前系統時間,即 Processing Time,是形成不肯定性的最多見也最難避免的緣由。對 Processing 的引用能夠是很明顯、有完善文檔標註的,好比 Flink 的 Time Characteristic,但也多是徹底出乎用戶意料的,好比來源於緩存等經常使用的技術。爲此,筆者總結了幾類常見的 Processing Time 引用:

  • Flink 提供的 Time Characteristic。Time Characteristic 會影響全部使用與時間相關的算子,好比 Processing Time 會讓窗口聚合使用當前系統時間來分配窗口和觸發計算,形成不肯定性。另外,Processing Timer 也有相似的影響。
  • 直接在函數裏訪問外部存儲。由於這種訪問是基於外部存儲某個 Processing Time 時間點的狀態,這個狀態極可能在下次訪問時就發生了變化,致使不肯定性。要得到肯定性的結果,比起簡單查詢外部存儲的某個時間點的狀態,咱們應該獲取它狀態變動的歷史,而後根據當前 Event Time 去查詢對應的狀態。這也是 Flink SQL 中 Temporary Table Join 的實現原理[1]。
  • 對外部數據的緩存。在計算流量很大的數據時,不少狀況下用戶會選擇用緩存來減輕外部存儲的負載,但這可能會形成查詢結果的不一致,並且這種不一致是不肯定的。不管是使用超時閾值、LRU(Least Recently Used)等直接和系統時間相關的緩存剔除策略,仍是 FIFO(First In First Out)、LFU(Less Frequently Used)等沒有直接關聯時間的剔除策略,訪問緩存獲得的結果一般和消息的到達順序相關,而在上游通過 shuffle 的算子裏面這是難以保證的(沒有 shuffle 的 Embarrassingly Parallel 做業是例外)。
  • Flink 的 StateTTL。StateTTL 是 Flink 內置的根據時間自動清理 State 的機制,而這裏的時間目前只提供 Processing Time,不管 Flink 自己使用的是 Processing Time 仍是 Event Time 做爲 Time Characteristic。BTW,StateTTL 對 Event Time 的支持能夠關注 FLINK-12005[2]。

綜合來說,要徹底避免 Processing Time 形成的影響是很是困難的,不太輕微的不肯定性對於業務來講一般是能夠接受的,咱們要作的更可能是提早預料到可能的影響,保證不肯定性在可控範圍內。

Watermark

Watermark 做爲計算 Event Time 的機制,其中一個很重要的用途是決定實時計算什麼時候要輸出計算結果,相似文件結束標誌符(EOF)在離線批計算中達到的效果。然而,在輸出結果以後可能還會有遲到的數據到達,這稱爲窗口完整性問題(Window Completeness)。

窗口完整性問題沒法避免,應對辦法是要麼更新計算結果,要麼丟棄這部分數據。由於離線場景延遲容忍度較大,離線做業能夠推遲必定時間開始,儘量地將延遲數據歸入計算。而實時場景對延遲有比較高的要求,所以通常是輸出結果後讓狀態保存一段時間,在這段時間內根據遲到數據持續更新結果(即 Allowed Lateness),此後將數據丟棄。由於定位,實時計算自然可能出現更多被丟棄的遲到數據,這將和 Watermark 的生成算法緊密相關。

雖然 Watermark 的生成是流式的,但 Watermark 的下發是斷點式的。Flink 的 Watermark 下發策略有 Periodic 和 Punctuated 兩種,前者基於 Processing Time 定時觸發,後者根據數據流中的特殊消息觸發。

Periodic Watermark 正常狀態與重放追數據狀態

基於 Processing Time 的 Periodic Watermark 具備不肯定。在平時流量平穩的時候 Watermark 的提高多是階梯式的(見圖1(a));然而在重放歷史數據的狀況下,相同長度的系統時間內處理的數據量可能會大不少(見圖1(b)),而且伴有 Event Time 傾斜(即有的 Source 的 Event Time 明顯比其餘要快或慢,致使取最小值的整體 Watermark 被慢 Watermark 拖慢),致使原本丟棄的遲到數據,如今變爲 Allowed Lateness 以內的數據(見圖1中紅色元素)。

2.jpg

相比之下 Punctuated Watermark 更爲穩定,不管在正常狀況(見圖2(a))仍是在重放數據的狀況(見圖2(b))下,下發的 Watermark 都是一致的,不過依然有 Event Time 傾斜的風險。對於這點,Flink 社區起草了 FLIP-27 來處理[4]。基本原理是 Source 節點會選擇性地消費或阻塞某個 partition/shard,讓整體的 Event Time 保持接近。

除了 Watermark 的下發有不肯定以外,還有個問題是如今 Watermark 並無被歸入 Checkpoint 快照中。這意味着在做業從 Checkpoint 恢復以後,Watermark 會從新開始算,致使 Watermark 的不肯定。這個問題在 FLINK-5601[5] 有記錄,但目前只體現了 Window 算子的 Watermark,而在 StateTTL 支持 Event Time 後,或許每一個算子都要記錄本身的 Watermark。

綜上所述,Watermark 目前是很難作到很是肯定的,但由於 Watermark 的不肯定性是經過丟棄遲到數據致使計算結果的不肯定性的,只要沒有丟棄遲到數據,不管中間 Watermark 的變化如何,最終的結果都是相同的。

總結

肯定性不足是阻礙實時計算在關鍵業務應用的主要因素,不過當前業界已經具有了解決問題的理論基礎,剩下的更可能是計算框架後續迭代和工程實踐上的問題。就目前開發 Flink 實時應用而言,須要注意投遞語義、函數反作用、Processing Time 和 Watermark 這幾點形成的不肯定性。

參考:

  1. Flux capacitor, huh? Temporal Tables and Joins in Streaming SQL
    https://flink.apache.org/2019/05/14/temporal-tables.html
  2. FLINK-12005 Event time support
    https://issues.apache.org/jira/browse/FLINK-12005
  3. Fault Tolerance Guarantees of Data Sources and Sinks
    https://ci.apache.org/projects/flink/flink-docs-release-1.10/dev/connectors/guarantees.html
  4. FLIP-27: Refactor Source Interface
    https://cwiki.apache.org/confluence/display/FLINK/FLIP-27%3A+Refactor+Source+Interface
  5. [FLINK-5601] Window operator does not checkpoint watermarks
    https://issues.apache.org/jira/browse/FLINK-5601

做者介紹:

林小鉑,網易遊戲高級開發工程師,負責遊戲數據中心實時平臺的開發及運維工做,目前專一於 Apache Flink 的開發及應用。探究問題原本就是一種樂趣。

相關文章
相關標籤/搜索