概述:
Apache Flink是一個框架和分佈式處理引擎,用於在無界和有界數據流上進行有狀態計算。Flink被設計成可以在所有常見的集羣環境中運行,以內存速度和任何規模執行計算。
一.Flink體系結構介紹
1.1 處理無界和有界數據
任何類型的數據都是作爲事件流產生的。信用卡交易、傳感器測量、機器日誌、網站或移動應用程序上的用戶交互,所有這些數據都以流的形式生成。
數據可以被處理爲無邊界或有邊界的流。
- 無邊界流有一個開始,但沒有定義結束。它們不會在生成數據時終止並提供數據。不受限制的流必須被連續地處理,也就是說,事件必須在被攝取之後被迅速地處理。等待所有輸入數據到達是不可能的,因爲輸入是無界的,在任何時間點都不會完成。處理無界數據通常要求以特定的順序(例如事件發生的順序)攝取事件,以便能夠推斷結果的完整性。
- 有界流有定義的開始和結束。有界流可以通過在執行任何計算之前獲取所有數據來處理。處理有界流不需要有序的輸入,因爲有界數據集總是可以排序的。有界流的處理也稱爲批處理。
Apache Flink擅長處理無界和有界數據集。對時間和狀態的精確控制使Flink的運行時能夠在無邊界流上運行任何類型的應用程序。有界流在內部由專爲固定大小的數據集設計的算法和數據結構處理,產生卓越的性能。
1.2 部署應用程序在任何地方
Apache Flink是一個分佈式系統,需要計算資源來執行應用程序。Flink集成了所有常見的集羣資源管理器,如Hadoop YARN、Apache Mesos和Kubernetes,但也可以設置爲作爲獨立的集羣運行。
Flink被設計成能夠很好地工作於前面列出的每個資源管理器。這是通過特定於資源管理器的部署模式實現的,這種部署模式允許Flink以其慣用的方式與每個資源管理器交互。
在部署Flink應用程序時,Flink根據應用程序配置的並行性自動識別所需的資源,並從資源管理器請求它們。在失敗的情況下,Flink通過請求新的資源來替換失敗的容器。提交或控制應用程序的所有通信都是通過REST調用進行的。這簡化了Flink在許多環境中的集成。
1.3 在任何規模上運行應用程序
Flink被設計爲在任何規模上運行有狀態流應用程序。應用程序被並行地分成數千個任務,這些任務分佈在一個集羣中並併發地執行。因此,應用程序可以利用幾乎不受限制的cpu、主內存、磁盤和網絡IO。而且,Flink可以輕鬆維護非常大的應用程序狀態。它的異步和增量檢查點算法確保了對處理延遲的最小影響,同時保證了精確一次的狀態一致性。
用戶報告了在他們的生產環境中運行的Flink應用程序令人印象深刻的可伸縮性數字,例如:
- 應用程序每天處理數萬億個事件,
- 維護多個tb狀態的應用程序
- 運行在數千個內核上的應用程序。
1.4 利用內存性能
有狀態Flink應用程序爲本地狀態訪問進行了優化。任務狀態始終在內存中維護,如果狀態大小超過可用內存,則在訪問效率高的磁盤數據結構中維護。因此,任務通過訪問本地(通常在內存中)狀態來執行所有計算,從而產生非常低的處理延遲。Flink通過定期和異步地將本地狀態檢查點指向持久存儲來保證故障時的精確一次狀態一致性。
二.應用程序
2.1 流應用程序的構建塊
可以用流處理框架構建和執行的應用程序類型由框架控制流、狀態和時間的程度來定義。在下面的文章中,我們將描述流處理應用程序的這些構建塊,並解釋Flink處理它們的方法。
流
顯然,流是流處理的一個基本方面。然而,流可以有不同的特徵,這些特徵會影響流可以和應該如何處理。Flink是一個通用的處理框架,可以處理任何類型的流。
有界和無界流:流可以是無界的,也可以是有界的,例如,固定大小的數據集。Flink具有處理無邊界流的複雜功能,但也有專門的操作符來有效地處理有邊界流。
實時記錄流:所有數據都以流的形式生成。處理數據有兩種方法。在它生成時實時處理它,或將流持久化到存儲系統,例如文件系統或對象存儲,並在稍後處理它。Flink應用程序可以處理記錄的或實時的流。
狀態
每個重要的流應用程序都是有狀態的,也就是說,只有對單個事件應用轉換的應用程序纔不需要狀態。運行基本業務邏輯的任何應用程序都需要記住事件或中間結果,以便在以後的時間點訪問它們,例如在接收到下一個事件時或在特定的持續時間之後。
應用狀態是Flink中的一等公民。您可以通過查看Flink在狀態處理上下文中提供的所有特性來了解這一點。
多個狀態原語:Flink爲不同的數據結構(如原子值、列表或映射)提供狀態原語。開發人員可以根據函數的訪問模式選擇最有效的狀態原語。
可插狀態後端:應用狀態在可插狀態後端管理和檢查點。Flink具有不同的狀態後端,它們將狀態存儲在內存或RocksDB中,RocksDB是一種高效的嵌入式磁盤數據存儲。也可以插入自定義狀態後端。
精確一次狀態一致性:Flink的檢查點和恢復算法保證了應用程序在出現故障時狀態的一致性。因此,故障是透明處理的,不會影響應用程序的正確性。
非常大的狀態:由於它的異步和增量檢查點算法,Flink能夠維護數tb大小的應用程序狀態。
可擴展的應用程序:Flink支持通過將狀態重新分配給更多或更少的工人來擴展有狀態的應用程序。
時間
時間是流媒體應用程序的另一個重要組成部分。大多數事件流都有固有的時間語義,因爲每個事件都是在特定的時間點產生的。此外,許多常見的流計算都是基於時間的,例如窗口聚合、會話化、模式檢測和基於時間的連接。流處理的一個重要方面是應用程序如何度量時間,即事件時間和處理時間的差異。
Flink提供了一組豐富的時間相關特性。
事件時間模式:使用事件時間語義處理流的應用程序根據事件的時間戳計算結果。因此,無論記錄的事件還是實時事件是被處理的,事件時間處理都能得到準確和一致的結果。
水印支持:Flink在事件時間應用中使用水印進行時間推理。水印也是一種靈活的機制來權衡結果的延遲和完整性。
延遲數據處理:當以事件-時間模式處理帶有水印的流時,可能會發生在所有相關事件到達之前計算已經完成。這樣的事件被稱爲晚事件。Flink提供了多個選項來處理後期事件,比如通過端輸出重新路由事件和更新之前完成的結果。
processing-time Mode:除了事件-時間模式外,Flink還支持processing-time語義,即根據處理器的時鐘時間觸發計算。處理時間模式適合某些嚴格要求低延遲、能夠容忍近似結果的應用程序
2.2 分層的api
Flink提供了三層api。每種API都在簡潔性和表達性之間提供了不同的權衡,並針對不同的用例。
我們將簡要介紹每個API,討論它的應用程序,並展示一個代碼示例。
The ProcessFunctions
ProcessFunctions是Flink提供的最有表現力的函數接口。Flink提供ProcessFunctions來處理來自一個或兩個輸入流或在窗口中分組的事件的單個事件。ProcessFunctions提供對時間和狀態的細粒度控制。ProcessFunction可以任意修改其狀態,並註冊將來觸發回調函數的計時器。因此,ProcessFunctions可以實現許多有狀態事件驅動的應用程序所需的複雜的每事件業務邏輯。
下面的示例顯示了一個KeyedProcessFunction,它操作KeyedStream並匹配START和END事件。當接收到START事件時,該函數記住其狀態的時間戳,並在4小時內註冊一個計時器。如果在定時器觸發之前收到一個END事件,函數計算END和START事件之間的持續時間,清除狀態並返回值。否則,計時器就會觸發並清除狀態。
/**
* Matches keyed START and END events and computes the difference between
* both elements' timestamps. The first String field is the key attribute,
* the second String attribute marks START and END events.
*/
public static class StartEndDuration
extends KeyedProcessFunction<String, Tuple2<String, String>, Tuple2<String, Long>> {
private ValueState<Long> startTime;
@Override
public void open(Configuration conf) {
// obtain state handle
startTime = getRuntimeContext()
.getState(new ValueStateDescriptor<Long>("startTime", Long.class));
}
/** Called for each processed event. */
@Override
public void processElement(
Tuple2<String, String> in,
Context ctx,
Collector<Tuple2<String, Long>> out) throws Exception {
switch (in.f1) {
case "START":
// set the start time if we receive a start event.
startTime.update(ctx.timestamp());
// register a timer in four hours from the start event.
ctx.timerService()
.registerEventTimeTimer(ctx.timestamp() + 4 * 60 * 60 * 1000);
break;
case "END":
// emit the duration between start and end event
Long sTime = startTime.value();
if (sTime != null) {
out.collect(Tuple2.of(in.f0, ctx.timestamp() - sTime));
// clear the state
startTime.clear();
}
default:
// do nothing
}
}
/** Called when a timer fires. */
@Override
public void onTimer(
long timestamp,
OnTimerContext ctx,
Collector<Tuple2<String, Long>> out) {
// Timeout interval exceeded. Cleaning up the state.
startTime.clear();
}
}
該示例說明了KeyedProcessFunction的表達能力,但也強調了它是一個相當冗長的接口
The DataStream API
DataStream API爲許多常見的流處理操作提供了原語,比如開窗、一次記錄轉換,以及通過查詢外部數據存儲來豐富事件。DataStream API可用於Java和Scala,並基於map()、reduce()和aggregate()等函數。函數可以通過擴展接口定義,也可以定義爲Java或Scala lambda函數。
下面的示例演示如何對點擊流進行會話並計算每個會話的點擊次數。
// a stream of website clicks
DataStream<Click> clicks = ...
DataStream<Tuple2<String, Long>> result = clicks
// project clicks to userId and add a 1 for counting
.map(
// define function by implementing the MapFunction interface.
new MapFunction<Click, Tuple2<String, Long>>() {
@Override
public Tuple2<String, Long> map(Click click) {
return Tuple2.of(click.userId, 1L);
}
})
// key by userId (field 0)
.keyBy(0)
// define session window with 30 minute gap
.window(EventTimeSessionWindows.withGap(Time.minutes(30L)))
// count clicks per session. Define function as lambda function.
.reduce((a, b) -> Tuple2.of(a.f0, a.f1 + b.f1));
SQL & Table API
Flink具有兩個關係API, Table API和SQL。這兩個api都是批處理和流處理的統一api,也就是說,查詢以相同的語義在無邊界的實時流或有邊界的記錄流上執行,併產生相同的結果。Table API和SQL利用Apache Calcite進行解析、驗證和查詢優化。它們可以與DataStream和DataSet api無縫集成,並支持用戶定義的標量、聚合和表值函數。
Flink的關係api旨在簡化數據分析、數據管道和ETL應用程序的定義。
下面的示例顯示了會話化單擊流並計算每個會話的單擊次數的SQL查詢。這與DataStream API示例中的用例相同。
SELECT userId, COUNT(*)
FROM clicks
GROUP BY SESSION(clicktime, INTERVAL '30' MINUTE), userId
2.3 庫
Flink爲常見的數據處理用例提供了幾個庫。這些庫通常嵌入到API中,並不是完全自包含的。因此,它們可以從API的所有特性中受益,並與其他庫集成。
複雜事件處理(CEP):模式檢測是事件流處理的一個非常常見的用例。Flink的CEP庫提供了一個API來指定事件模式(可以考慮正則表達式或狀態機)。CEP庫與Flink的DataStream API集成在一起,這樣就可以在DataStreams上評估模式。CEP庫的應用程序包括網絡入侵檢測、業務流程監控和欺詐檢測。
DataSet API: DataSet API是Flink用於批處理應用程序的核心API。DataSet API的原語包括map、reduce、(外部)連接、co-group和iterate。所有操作都由算法和數據結構支持,這些算法和數據結構對內存中的序列化數據進行操作,如果數據大小超過內存預算,就會溢出到磁盤。Flink的DataSet API的數據處理算法受到了傳統數據庫操作符的啓發,比如混合散列連接或外部合併排序。
Gelly: Gelly是一個用於可伸縮圖形處理和分析的庫。Gelly是在DataSet API之上實現並與之集成的。因此,它得益於其可擴展和健壯的操作符。Gelly提供了內置算法,如標籤傳播、三角形枚舉和頁面排名,但還提供了一個Graph API,可以簡化自定義圖算法的實現。
三. 操作
3.1 7*24 不間斷運行應用程序
機器和進程故障在分佈式系統中是普遍存在的。像Flink這樣的分佈式流處理器必須從故障中恢復,才能全天候運行流應用程序。顯然,這不僅意味着在故障後重新啓動應用程序,而且還意味着確保其內部狀態保持一致,以便應用程序可以繼續處理,就像故障從未發生過一樣。
Flink提供了幾個特性,以確保應用程序保持運行和保持一致:
一致的檢查點:Flink的恢復機制基於應用程序狀態的一致檢查點。如果出現故障,則重新啓動應用程序,並從最新的檢查點加載其狀態。結合可重置的流源,該特性可以保證精確一次的狀態一致性。
高效的檢查點:如果應用程序維護着tb級的狀態,那麼檢查點應用程序的狀態是非常昂貴的。Flink可以執行異步和增量檢查點,以保持檢查點對應用程序延遲sla的影響非常小。
端到端只寫一次:Flink爲特定的存儲系統提供了事務接收功能,保證數據只寫一次,即使發生故障。
與集羣管理器集成:Flink與集羣管理器緊密集成,如Hadoop YARN、Mesos或Kubernetes。當一個流程失敗時,會自動啓動一個新的流程來接管它的工作。
高可用性設置:Flink具有高可用性模式,消除了所有單點故障。ha模式基於Apache ZooKeeper,這是一個經過實戰驗證的可靠的分佈式協調服務。
3.2 更新、遷移、掛起和恢復應用程序
支持關鍵業務服務的流應用程序需要維護。漏洞需要修復,改進或新功能需要實現。然而,更新有狀態流應用程序並非易事。通常不能簡單地停止應用程序並重新啓動一個固定的或改進的版本,因爲不能失去應用程序的狀態。
Flink的Savepoints是一個獨特而強大的特性,它解決了更新有狀態應用程序和許多其他相關挑戰的問題。保存點是應用程序狀態的一致快照,因此非常類似於檢查點。然而,與檢查點不同的是,保存點需要手動觸發,並且在應用程序停止時不會自動刪除。保存點可用於啓動與狀態兼容的應用程序並初始化其狀態。保存點啓用以下功能:
應用程序演進:保存點可用於演進應用程序。應用程序的固定版本或改進版本可以從取自該應用程序以前版本的保存點重新啓動。也可以從較早的時間點(假設存在這樣的保存點)啓動應用程序,以修復由有缺陷的版本產生的不正確結果。
集羣遷移:使用保存點,可以將應用程序遷移(或克隆)到不同的集羣。
Flink版本更新:可以使用保存點將應用程序遷移到新的Flink版本上運行。
應用程序擴展:保存點可以用來增加或減少應用程序的並行度。
A/B測試和假設場景:一個應用程序的兩個(或更多)不同版本的性能或質量可以通過從同一個保存點啓動所有版本來進行比較。
暫停和恢復:可以通過獲取保存點並停止它來暫停應用程序。在以後的任何時間點,應用程序都可以從保存點恢復。
存檔:可以對保存點進行存檔,以便能夠將應用程序的狀態重置爲更早的時間點
3.3 監控您的應用程序
就像任何其他服務一樣,持續運行的流應用程序需要被監督並集成到運營基礎設施中,即組織的監視和日誌服務。監控有助於預測問題並提前做出反應。日誌記錄使根源分析能夠調查失敗。最後,控制正在運行的應用程序的易於訪問的接口是一個重要特性。
Flink很好地集成了許多常見的日誌和監視服務,並提供了一個REST API來控制應用程序和查詢信息。
Web UI: Flink提供Web UI,用於檢查、監控和調試運行中的應用程序。它還可以用於提交執行或取消執行。
日誌:Flink實現了流行的slf4j日誌接口,並與日誌框架log4j或logback集成。
度量:Flink提供了一個複雜的度量系統來收集和報告系統和用戶定義的度量。指標可以導出到多個記者,包括JMX、Ganglia、Graphite、Prometheus、StatsD、Datadog和Slf4j。
REST API: Flink公開REST API以提交新應用程序、獲取運行應用程序的保存點或取消應用程序。REST API還公開元數據和收集的運行或完成應用程序的指標。