介紹
概述
Apache Flink是一個(gè)面向數(shù)據(jù)流處理和批量數(shù)據(jù)處理的可分布式的開(kāi)源計(jì)算框架,它基于同一個(gè)Flink流式執(zhí)行模型(streaming execution model),能夠支持流處理和批處理兩種應(yīng)用類型。由于流處理和批處理所提供的SLA(服務(wù)等級(jí)協(xié)議)是完全不相同, 流處理一般需要支持低延遲、Exactly-once保證,而批處理需要支持高吞吐、高效處理,所以在實(shí)現(xiàn)的時(shí)候通常是分別給出兩套實(shí)現(xiàn)方法,或者通過(guò)一個(gè)獨(dú)立的開(kāi)源框架來(lái)實(shí)現(xiàn)其中每一種處理方案。比較典型的有:實(shí)現(xiàn)批處理的開(kāi)源方案有MapReduce、Spark;實(shí)現(xiàn)流處理的開(kāi)源方案有Storm;Spark的Streaming 其實(shí)本質(zhì)上也是微批處理。
Flink在實(shí)現(xiàn)流處理和批處理時(shí),與傳統(tǒng)的一些方案完全不同,它從另一個(gè)視角看待流處理和批處理,將二者統(tǒng)一起來(lái):Flink是完全支持流處理,也就是說(shuō)作為流處理看待時(shí)輸入數(shù)據(jù)流是無(wú)界的;批處理被作為一種特殊的流處理,只是它的輸入數(shù)據(jù)流被定義為有界的。
特性
- 有狀態(tài)計(jì)算的Exactly-once語(yǔ)義。狀態(tài)是指flink能夠維護(hù)數(shù)據(jù)在時(shí)序上的聚類和聚合,同時(shí)它的checkpoint機(jī)制
- 支持帶有事件時(shí)間(event time)語(yǔ)義的流處理和窗口處理。事件時(shí)間的語(yǔ)義使流計(jì)算的結(jié)果更加精確,尤其在事件到達(dá)無(wú)序或者延遲的情況下。
- 支持高度靈活的窗口(window)操作。支持基于time、count、session,以及data-driven的窗口操作,能很好的對(duì)現(xiàn)實(shí)環(huán)境中的創(chuàng)建的數(shù)據(jù)進(jìn)行建模。
- 輕量的容錯(cuò)處理( fault tolerance)。 它使得系統(tǒng)既能保持高的吞吐率又能保證exactly-once的一致性。通過(guò)輕量的state snapshots實(shí)現(xiàn)
- 支持高吞吐、低延遲、高性能的流處理
- 支持savepoints 機(jī)制(一般手動(dòng)觸發(fā))。即可以將應(yīng)用的運(yùn)行狀態(tài)保存下來(lái);在升級(jí)應(yīng)用或者處理歷史數(shù)據(jù)是能夠做到無(wú)狀態(tài)丟失和最小停機(jī)時(shí)間。
- 支持大規(guī)模的集群模式,支持yarn、Mesos。可運(yùn)行在成千上萬(wàn)的節(jié)點(diǎn)上
- 支持具有Backpressure功能的持續(xù)流模型
- Flink在JVM內(nèi)部實(shí)現(xiàn)了自己的內(nèi)存管理
- 支持迭代計(jì)算
- 支持程序自動(dòng)優(yōu)化:避免特定情況下Shuffle、排序等昂貴操作,中間結(jié)果進(jìn)行緩存
API支持
- DataStream API
- DataSet API
- Table API
- Streaming SQL
Libs支持
- 支持復(fù)雜事件處理(CEP)
- 支持機(jī)器學(xué)習(xí)(FlinkML)
- 支持圖分析處理(Gelly)
- 支持關(guān)系數(shù)據(jù)處理(Table)
整體組件棧
- Deployment層: 該層主要涉及了Flink的部署模式,F(xiàn)link支持多種部署模式:本地、集群(Standalone/YARN),(GCE/EC2)。
- Runtime層:Runtime層提供了支持Flink計(jì)算的全部核心實(shí)現(xiàn),比如:支持分布式Stream處理、JobGraph到ExecutionGraph的映射、調(diào)度等等,為上層API層提供基礎(chǔ)服務(wù)。
- API層: 主要實(shí)現(xiàn)了面向無(wú)界Stream的流處理和面向Batch的批處理API,其中面向流處理對(duì)應(yīng)DataStream API,面向批處理對(duì)應(yīng)DataSet API。
- Libraries層:該層也可以稱為Flink應(yīng)用框架層,根據(jù)API層的劃分,在API層之上構(gòu)建的滿足特定應(yīng)用的實(shí)現(xiàn)計(jì)算框架,也分別對(duì)應(yīng)于面向流處理和面向批處理兩類。面向流處理支持:CEP(復(fù)雜事件處理)、基于SQL-like的操作(基于Table的關(guān)系操作);面向批處理支持:FlinkML(機(jī)器學(xué)習(xí)庫(kù))、Gelly(圖處理)
編程模型
抽象的層級(jí)
有狀態(tài)的數(shù)據(jù)流處理層。最底層的抽象僅僅提供有狀態(tài)的數(shù)據(jù)流,它通過(guò)處理函數(shù)(Process Function)嵌入到數(shù)據(jù)流api(DataStream API). 用戶可以通過(guò)它自由的處理單流或者多流,并保持一致性和容錯(cuò)。同時(shí)用戶可以注冊(cè)事件時(shí)間和處理時(shí)間的回調(diào)處理,以實(shí)現(xiàn)復(fù)雜的計(jì)算邏輯。
核心API層。 它提供了數(shù)據(jù)處理的基礎(chǔ)模塊,像各種transformation, join,aggregations,windows,stat 以及數(shù)據(jù)類型等等
Table API層。 定了圍繞關(guān)系表的DSL(領(lǐng)域描述語(yǔ)言)。Table API遵循了關(guān)系模型的標(biāo)準(zhǔn):Table類型關(guān)系型數(shù)據(jù)庫(kù)中的表,API也提供了相應(yīng)的操作,像select,project,join,group-by,aggregate等。Table API聲明式的定義了邏輯上的操作(logical operation)不是code for the operation;Flink會(huì)對(duì)Table API邏輯在執(zhí)行前進(jìn)行優(yōu)化。同時(shí)代碼上,F(xiàn)link允許混合使用Table API和DataStram/DataSet API
SQL層。 它很類似Table API的語(yǔ)法和表達(dá),也是定義與Table API層次之上的,但是提供的是純SQL的查詢表達(dá)式。
程序和數(shù)據(jù)流
用戶實(shí)現(xiàn)的Flink程序是由Stream和Transformation這兩個(gè)基本構(gòu)建塊組成,其中Stream是一個(gè)中間結(jié)果數(shù)據(jù),而Transformation是一個(gè)操作,它對(duì)一個(gè)或多個(gè)輸入Stream進(jìn)行計(jì)算處理,輸出一個(gè)或多個(gè)結(jié)果Stream。當(dāng)一個(gè)Flink程序被執(zhí)行的時(shí)候,它會(huì)被映射為Streaming Dataflow。一個(gè)Streaming Dataflow是由一組Stream和Transformation Operator組成,它類似于一個(gè)DAG圖,在啟動(dòng)的時(shí)候從一個(gè)或多個(gè)Source Operator開(kāi)始,結(jié)束于一個(gè)或多個(gè)Sink Operator。
下面是一個(gè)由Flink程序映射為Streaming Dataflow的示意圖,如下所示:
上圖中,F(xiàn)linkKafkaConsumer是一個(gè)Source Operator,map、keyBy、timeWindow、apply是Transformation Operator,RollingSink是一個(gè)Sink Operator。
并行的數(shù)據(jù)流
在Flink中,程序天生是并行和分布式的:一個(gè)Stream可以被分成多個(gè)Stream分區(qū)(Stream Partitions),一個(gè)Operator可以被分成多個(gè)Operator Subtask,每一個(gè)Operator Subtask是在不同的線程中獨(dú)立執(zhí)行的。一個(gè)Operator的并行度,等于Operator Subtask的個(gè)數(shù),一個(gè)Stream的并行度總是等于生成它的Operator的并行度。有關(guān)Parallel Dataflow的實(shí)例,如下圖所示:
上圖Streaming Dataflow的并行視圖中,展現(xiàn)了在兩個(gè)Operator之間的Stream的兩種模式:
- One-to-one模式:比如從Source[1]到map()[1],它保持了Source的分區(qū)特性(Partitioning)和分區(qū)內(nèi)元素處理的有序性,也就是說(shuō)map()[1]的Subtask看到數(shù)據(jù)流中記錄的順序,與Source[1]中看到的記錄順序是一致的。
- Redistribution模式:這種模式改變了輸入數(shù)據(jù)流的分區(qū),比如從map()[1]、map()[2]到keyBy()/window()/apply()[1]、keyBy()/window()/apply()[2],上游的Subtask向下游的多個(gè)不同的Subtask發(fā)送數(shù)據(jù),改變了數(shù)據(jù)流的分區(qū),這與實(shí)際應(yīng)用所選擇的Operator有關(guān)系。
另外,Source Operator對(duì)應(yīng)2個(gè)Subtask,所以并行度為2,而Sink Operator的Subtask只有1個(gè),故而并行度為1。
窗口(Windows)
流處理中的聚合操作(counts,sums等等)不同于批處理,因?yàn)閿?shù)據(jù)流是無(wú)限,無(wú)法在其上應(yīng)用聚合,所以通過(guò)限定窗口(window)的范圍,來(lái)進(jìn)行流的聚合操作。例如:5分鐘的數(shù)據(jù)計(jì)數(shù),或者計(jì)算100個(gè)元素的總和等等。
窗口可以由時(shí)間驅(qū)動(dòng) (every 30 seconds) 或者數(shù)據(jù)驅(qū)動(dòng)(every 100 elements)。如:滾動(dòng)窗口tumbling windows(無(wú)疊加),滑動(dòng)窗口sliding windows(有疊加),以及會(huì)話窗口session windows(被無(wú)事件活動(dòng)的間隔隔開(kāi))
時(shí)間(Time)
三種不同的時(shí)間概念:
- 事件時(shí)間 Event Time:事件的創(chuàng)建時(shí)間,通常通過(guò)時(shí)間中的一個(gè)時(shí)間戳來(lái)描述
- 攝入時(shí)間 Ingestion time: 事件進(jìn)入Flink 數(shù)據(jù)流的source的時(shí)間
- 處理時(shí)間 Processing Time:Processing Time表示某個(gè)Operator對(duì)事件進(jìn)行處理時(shí)的本地系統(tǒng)時(shí)間(是在TaskManager節(jié)點(diǎn)上)
有狀態(tài)的數(shù)據(jù)操作(Stateful Operations)
在流處理中,有些操作僅僅在某一時(shí)間針對(duì)單一事件(如事件轉(zhuǎn)換map),有些操作需要記住多個(gè)事件的信息并進(jìn)行處理(window operators),后者的這些操作稱為有狀態(tài)的操作。
有狀態(tài)的操作一般被維護(hù)在內(nèi)置的key/value存儲(chǔ)中。這些狀態(tài)信息會(huì)跟數(shù)據(jù)流一起分區(qū)并且分布存儲(chǔ),并且可以通過(guò)有狀態(tài)的數(shù)據(jù)操作來(lái)訪問(wèn)。因此這些key/value的狀態(tài)信息僅在帶key的數(shù)據(jù)流(通過(guò)keyBy() 函數(shù)處理過(guò))中才能訪問(wèn)到。數(shù)據(jù)流按照key排列能保證所有的狀態(tài)更新都是本地操作,保證一致性且無(wú)事務(wù)問(wèn)題。同時(shí)這種排列方式使Flink能夠透明的再分發(fā)狀態(tài)信息和調(diào)整數(shù)據(jù)流分區(qū)。
容錯(cuò)的Checkpoint
Flink 通過(guò)流回放和設(shè)置檢查點(diǎn)的方式實(shí)現(xiàn)容錯(cuò)。一個(gè)checkpoint關(guān)聯(lián)了輸入流中的某個(gè)記錄和相應(yīng)狀態(tài)和操作。數(shù)據(jù)流可以從checkpoint中進(jìn)行恢復(fù),并保證一致性(exactly-once 的處理語(yǔ)義)。 Checkpoint的間隔關(guān)系到執(zhí)行是的容錯(cuò)性和恢復(fù)時(shí)間。
流上的批處理
Flink把批處理作為特殊的流處理程序來(lái)執(zhí)行,許多概念也都可以應(yīng)用的批處理中,除了一些小的不同:
- 批處理的API(DataSet API )不使用checkpoints,恢復(fù)通過(guò)完整的流回放來(lái)實(shí)現(xiàn)
- DataSet API的有狀態(tài)操作使用簡(jiǎn)單的內(nèi)存和堆外內(nèi)存 的數(shù)據(jù)結(jié)構(gòu),而不是key/value的索引
- DataSet API 引入一種同步的迭代操作,這個(gè)僅應(yīng)用于有界數(shù)據(jù)流。
分布式執(zhí)行環(huán)境
任務(wù)和運(yùn)算(算子)鏈(Tasks and Operator Chains)
在Flink分布式執(zhí)行環(huán)境中,會(huì)將多個(gè)運(yùn)算子任務(wù)Operator Subtask串起來(lái)組成一個(gè)Operator Chain,實(shí)際上就是一個(gè)運(yùn)算鏈。每個(gè)運(yùn)算會(huì)在TaskManager上一個(gè)獨(dú)立的線程中執(zhí)行。將算子串連到任務(wù)中是一種很好的優(yōu)化:它能減少線程間的數(shù)據(jù)交接和緩存,并且提高整體的吞吐,降低處理的時(shí)延。這種串聯(lián)的操作,可以通過(guò)API來(lái)進(jìn)行配置。如下圖的數(shù)據(jù)流就有5個(gè)子任務(wù),通過(guò)5個(gè)并行的線程來(lái)執(zhí)行,所示:
Job Managers,Task Managers,Clients
Flink的運(yùn)行時(shí),由兩種類型的進(jìn)程組成:
- JobManagers: 也就是masters ,協(xié)調(diào)分布式任務(wù)的執(zhí)行 。用來(lái)調(diào)度任務(wù),協(xié)調(diào)checkpoints,協(xié)調(diào)錯(cuò)誤恢復(fù)等等。至少需要一個(gè)JobManager,高可用的系統(tǒng)會(huì)有多個(gè),一個(gè)leader,其他是standby
- TaskManagers: 也就是workers,用來(lái)執(zhí)行數(shù)據(jù)流任務(wù)或者子任務(wù),緩存和交互數(shù)據(jù)流。 至少需要一個(gè)TaskManager
- Client: Client不是運(yùn)行是和程序執(zhí)行的一部分,它是用來(lái)準(zhǔn)備和提交數(shù)據(jù)流到JobManagers。之后,可以斷開(kāi)連接或者保持連接以獲取任務(wù)的狀態(tài)信息。
從上圖可以分析出Flink運(yùn)行時(shí)的整體狀態(tài)。 Flink的Driver程序會(huì)將代碼邏輯構(gòu)建成一個(gè)Program Dataflow(區(qū)分source,operator,sink等等),在通過(guò)Graph Builder構(gòu)建DAG的Dataflow graph, 構(gòu)建job,劃分出task 和subtask等等。 Client 將job 提交到JobManager. Client 通過(guò)Actor System和JobManager 進(jìn)行消息通訊,接收J(rèn)obManager返回的狀態(tài)更新和任務(wù)執(zhí)行統(tǒng)計(jì)結(jié)果。 JobMangaer 按照Dataflow的Task 和Subtask的劃分,將任務(wù)調(diào)度分配到各個(gè)TaskManager中進(jìn)行執(zhí)行。TaskManager會(huì)將內(nèi)存抽象成多個(gè)TaskSlot,用于執(zhí)行Task任務(wù)。JobManagers與TaskManagers之間的任務(wù)管理,Checkpoints的觸發(fā),任務(wù)狀態(tài),心跳等等消息處理都是通過(guò)ActorSystem。
Task Slots 和資源
每個(gè)Worker(Task Manager)是一個(gè)JVM進(jìn)程,通常會(huì)在單獨(dú)的線程里執(zhí)行一個(gè)或者多個(gè)子任務(wù)。為了控制一個(gè)Worker能夠接受多少個(gè)任務(wù),會(huì)在Worker上抽象多個(gè)Task Slot (至少一個(gè))。
每個(gè)Task Slot代表固定的資源子集。比如一個(gè)TaskManager有3個(gè)Slots,每個(gè)Slot能管理對(duì)這個(gè)Worker分配的資源的3分之1的內(nèi)存。 對(duì)資源分槽,意味著Subtask不會(huì)同其他Subtasks競(jìng)爭(zhēng)內(nèi)存,同時(shí)可以預(yù)留一定的可用內(nèi)存。目前Task Slot沒(méi)有對(duì)CPU進(jìn)行隔離,僅是針對(duì)內(nèi)存。通過(guò)動(dòng)態(tài)的調(diào)整task slots的個(gè)數(shù),用戶可以定義哪些子任務(wù)可以相互隔離。只有一個(gè)slot的TaskManager意味著每個(gè)任務(wù)組運(yùn)行在一個(gè)單獨(dú)JVM中。 在擁有多個(gè)slot的TaskManager上,subtask共用JVM,可以共用TCP連接和心跳消息,同時(shí)可以共用一些數(shù)據(jù)集和數(shù)據(jù)結(jié)構(gòu),從而減小任務(wù)的開(kāi)銷。
默認(rèn)情況下,F(xiàn)link允許子任務(wù)共享slots,即便它們是不同任務(wù)的子任務(wù),只要屬于同一個(gè)job。這樣的結(jié)果就是一個(gè)slot會(huì)負(fù)責(zé)一個(gè)job的整個(gè)pipeline。共用slot有兩個(gè)好處:
- Flink 集群的task slot的個(gè)數(shù)就是job的最高并行度。
- 更實(shí)現(xiàn)更好的資源利用。沒(méi)有共享的slots,非密集的source/map() subtask 會(huì)占用和 window 這類密集型的subtask 同樣多的資源。 使用共享的slot的將充分的利用分槽的資源,使代價(jià)較大的subtask能夠均勻的分布在TaskManager上。如,下圖中的共享slot的執(zhí)行模式中可以并行運(yùn)行6個(gè)pipeline而上圖的只可以運(yùn)行2個(gè)pipeline.
同時(shí)APIs也提供了資源組的機(jī)制,可以實(shí)現(xiàn)不想進(jìn)行資源隔離的情況。
實(shí)踐中,比較好的每個(gè)TaskManager的task slot的默認(rèn)數(shù)量最好是CPU的核數(shù)。
狀態(tài)后端
數(shù)據(jù)的KV索引信息存儲(chǔ)在設(shè)定的狀態(tài)后端的存儲(chǔ)中。一種是內(nèi)存中的Hash map,另一種是存在Rocksdb(KV存儲(chǔ))中。另外,狀態(tài)后端還是實(shí)現(xiàn)了在時(shí)間點(diǎn)上對(duì)KV狀態(tài)的快照,并作為Checkpoint的一部分存儲(chǔ)起來(lái)。
保存點(diǎn)(Savepoints)
通過(guò)Data Stream AP編寫(xiě)的程序可以從一個(gè)保存點(diǎn)重新開(kāi)始執(zhí)行。即便你更新了你的程序和Flink集群都不會(huì)有狀態(tài)數(shù)據(jù)丟失。
保存點(diǎn)是手動(dòng)觸發(fā)的,觸發(fā)時(shí)會(huì)將它寫(xiě)入狀態(tài)后端。Savepoints的實(shí)現(xiàn)也是依賴Checkpoint的機(jī)制。Flink 程序在執(zhí)行中會(huì)周期性的在worker 節(jié)點(diǎn)上進(jìn)行快照并生成Checkpoint。因?yàn)槿蝿?wù)恢復(fù)的時(shí)候只需要最后一個(gè)完成的Checkpoint的,所以舊有的Checkpoint會(huì)在新的Checkpoint完成時(shí)被丟棄。
Savepoints和周期性的Checkpoint非常的類似,只是有兩個(gè)重要的不同。一個(gè)是由用戶觸發(fā),而且不會(huì)隨著新的Checkpoint生成而被丟棄。
參考: https://ci.apache.org/projects/flink/flink-docs-release-1.3/concepts/programming-model.html