宋靈城
(東南大學(xué)軟件學(xué)院,江蘇 蘇州 215000)
隨著數(shù)據(jù)量的增加,單單使用批處理框架已經(jīng)不能滿足對大數(shù)據(jù)量的處理。流處理在很多應(yīng)用場景中被應(yīng)用,如實(shí)時(shí)監(jiān)控、金融科技以及傳感器網(wǎng)絡(luò)數(shù)據(jù)的實(shí)時(shí)處理等。不同的流計(jì)算框架在大量的需求中應(yīng)運(yùn)而生。現(xiàn)下最流行的流計(jì)算框架有Twitter開源出來成為apache頂級項(xiàng)目的Storm、基于內(nèi)存計(jì)算的Spark上的流式框架Spark Streaming以及認(rèn)為流處理是“一等公民”的Flink。
本文主要分為4個(gè)部分:第1部分介紹Spark Streaming,第2部分介紹Flink,第3部分深入對比兩個(gè)框架,第4部分是總結(jié)。
介紹Spark Streaming前,先介紹Spark。Spark[1]是UC Berkeley AMP Lab開源的類似于MapReduce的通用的并行計(jì)算框架,同時(shí)兼顧分布式的并行計(jì)算模型和基于內(nèi)存計(jì)算的特點(diǎn)。Spark優(yōu)于MapReduce[2]的最大的好處是作業(yè)計(jì)算的中間結(jié)果不需要再像MapReduce一樣刷寫到hdfs等外部存儲,而是保存在內(nèi)存中,因此不需要與外部存儲來回讀寫,能極大提升性能。圖1為Spark的部署圖。

圖1 Spark框架
Spark的部署采用Master-Slave模型,運(yùn)行時(shí)會在集群中啟動Driver節(jié)點(diǎn)和多個(gè)Worker節(jié)點(diǎn)。Driver在接受客戶端提交上來的作業(yè)后,建立RDD的血緣關(guān)系,記錄血緣狀態(tài),分發(fā)任務(wù)到Worker節(jié)點(diǎn)上進(jìn)行計(jì)算,并接受所有Worker節(jié)點(diǎn)的計(jì)算結(jié)果。
Spark Streaming是建立在Spark之上的流式計(jì)算框架,通過Spark提供的API和基于內(nèi)存的高速計(jì)算引擎,用戶可以使用批處理進(jìn)行micro-batch流式計(jì)算,做到代碼邏輯上的重復(fù)使用。和Spark中的RDD非常相似,Spark Streaming中使用離散化流(Discretized Stream)作為抽象的表示,叫做DStream。它是隨時(shí)間推移而收集數(shù)據(jù)的序列,每個(gè)時(shí)間段收集到的數(shù)據(jù)在DStream內(nèi)以一個(gè)RDD的形式存在。Spark Streaming的執(zhí)行流程圖如圖2所示。

圖2 Spark Streaming執(zhí)行流程
Spark Streaming框架提供了良好的可擴(kuò)展性和容錯(cuò)性[3]。在對數(shù)據(jù)的處理模式上,Spark Streaming是處理某個(gè)時(shí)間窗口內(nèi)的事件流,因此相對于Strom、Flink等處理獨(dú)立事件的計(jì)算引擎,延遲相對較高。
Apache Flink[4]是一個(gè)面向分布式數(shù)據(jù)流處理和批量數(shù)據(jù)處理的開源計(jì)算框架,能在同一個(gè)Flink運(yùn)行時(shí)(Flink Runtime)支持分布式流處理和批處理兩種類型功能應(yīng)用,這里主要介紹Flink的流處理能力。它對流處理的支持是全面的。當(dāng)作為流處理時(shí),輸入數(shù)據(jù)流是無界的。批處理[5]則可認(rèn)為是一種特殊的流處理,即它的輸入數(shù)據(jù)流被定義為有界。這與傳統(tǒng)的一些方案完全不同,F(xiàn)link將二者合二為一,分別提供了流處理和批處理的API,這些API為實(shí)現(xiàn)上層應(yīng)用提供了選擇。Flink在處理流數(shù)據(jù)時(shí)具有高吞吐、低延遲和高性能的特性,同時(shí)也支持帶有事件時(shí)間的窗口(Window)操作、高度靈活的窗口操作以及基于輕量級分布式快照(Snapshot)實(shí)現(xiàn)的容錯(cuò),F(xiàn)link的內(nèi)存管理是在JVM內(nèi)部實(shí)現(xiàn)的。
Flink系統(tǒng)也是基于Master-Slave風(fēng)格的架構(gòu),如圖3所示。

圖3 Flink架構(gòu)
Flink運(yùn)行時(shí)會同時(shí)啟動JobManager節(jié)點(diǎn)和多個(gè)TaskManager節(jié)點(diǎn),用戶將Flink作業(yè)提交到客戶端,客戶端會對作業(yè)做第一步的預(yù)處理優(yōu)化,并且以JobGraph拓?fù)鋱D的形式提交給JobManager。JobManager會將作業(yè)分發(fā)到TaskManager節(jié)點(diǎn)上進(jìn)行計(jì)算處理,最后將結(jié)果返回給客戶端。Flink最大的特點(diǎn)是有狀態(tài)的流式計(jì)算,從Source開始每一個(gè)算子或者每一次計(jì)算都會在State中記錄其中間狀態(tài),這個(gè)中間狀態(tài)在容錯(cuò)恢復(fù)或者迭代計(jì)算中起到很大的作用。
流處理程序在時(shí)間概念上共有3個(gè)時(shí)間概念,分別是處理時(shí)間(Processing time)、事件時(shí)間(Event time)和注入時(shí)間(Ingestion Time)。其中,處理時(shí)間主要是指每臺機(jī)器的系統(tǒng)時(shí)間不需要進(jìn)行流與機(jī)器之間的協(xié)調(diào),能提供最好的性能和最低的延遲,但是在分布式環(huán)境中難以提供時(shí)間的時(shí)序性保證。事件時(shí)間是指事件在其設(shè)備上發(fā)生的時(shí)間,可以在事件發(fā)生時(shí)將時(shí)間嵌入事件。基于事件時(shí)間進(jìn)行處理的流式計(jì)算程序,可以保證事件的時(shí)序性。注入時(shí)間是指事件注入到計(jì)算引擎的時(shí)間。相對事件時(shí)間,注入時(shí)間沒法處理無序事件和滯后事件。Flink支持所有的三種事件,且可以用waterMark機(jī)制[6]處理滯后的數(shù)據(jù),保證事件時(shí)間下的時(shí)序性。Spark Streaming只支持處理時(shí)間,最新的Structured streaming則可以支持處理時(shí)間和事件時(shí)間,同時(shí)與Flink一樣支持waterMark機(jī)制處理滯后數(shù)據(jù)。圖4是Flink中三種時(shí)間的區(qū)別。

圖4 Flink時(shí)間機(jī)制
流處理引擎通常會讓用戶指定處理語義級別來對程序中數(shù)據(jù)處理過程中提供對應(yīng)級別的保障。程序運(yùn)行中會因?yàn)楦鞣N內(nèi)外因素而可能導(dǎo)致數(shù)據(jù)丟失,所以這個(gè)語義上的保障對于流式計(jì)算中意義重大。流處理引擎通常提供最多一次(at most once)、至少一次(at least once)和精確一次(exactly once)三種數(shù)據(jù)處理語義。最多一次本質(zhì)上是保證數(shù)據(jù)或事件最多由應(yīng)用程序中的所有算子處理一次,缺點(diǎn)是數(shù)據(jù)在被程序完全處理前丟失將不會重發(fā)。至少一次本質(zhì)上是應(yīng)用程序中所有算子都保證數(shù)據(jù)或事件至少被處理一次,缺點(diǎn)是會出現(xiàn)數(shù)據(jù)的重復(fù)處理。最好的處理方式是精確一次,單條數(shù)據(jù)或者事件只會被精確處理一次,也是流處理引擎保證數(shù)據(jù)可靠性的重要特性。
Spark Streaming保證精確一次語義取決于上游數(shù)據(jù)源和下游輸出的特性。上游數(shù)據(jù)源的特性取決于上游系統(tǒng)的特性。例如,從HDFS這類支持容錯(cuò)的文件系統(tǒng)中讀取文件,能夠直接支持精確一次語義。Kafka消息系統(tǒng)是基于偏移量(Offset)的,它的Direct API可以提供精確一次語義。而Spark RDD在對數(shù)據(jù)進(jìn)行處理轉(zhuǎn)換時(shí),天然獲得精確一次語義,因?yàn)镽DD本身是一種具備容錯(cuò)性、不變性以及計(jì)算確定性的數(shù)據(jù)結(jié)構(gòu)。只要數(shù)據(jù)來源是可用的,且處理過程中沒有副作用(Side effect),就能一直得到相同的計(jì)算結(jié)果。對于輸出結(jié)果需要保證冪等或者事務(wù)更新的特點(diǎn)。
Flink內(nèi)部通過Checkpoint機(jī)制實(shí)現(xiàn)精確一次的語義,Checkpoint機(jī)制是基于Chandy-Lamport算法的分布式一致性快照,通過在數(shù)據(jù)輸入源發(fā)送Barrier同步全局狀態(tài),從而保證Flink內(nèi)部的一致性語義。在Flink1.4版本之后,在sink function中增加了TwoPhaseCommitSinkFunction函數(shù),通過兩段提交構(gòu)建從數(shù)據(jù)源到數(shù)據(jù)輸出的一個(gè)端到端的精確一次語義的Flink作業(yè)。當(dāng)然,輸出端也必須要有事務(wù)回滾的特性,如Kafka0.11版本等。
Spark和Flink都是通用計(jì)算引擎,支持大規(guī)模數(shù)據(jù)處理和各種類型的數(shù)據(jù)處理,每一個(gè)都有很多值得探索的地方,如SQL優(yōu)化和機(jī)器學(xué)習(xí)集成。本文比較的主要目的是回顧兩個(gè)系統(tǒng)的基本架構(gòu)和設(shè)計(jì)特點(diǎn)。
Spark Streaming和Flink執(zhí)行模型的最大區(qū)別在于對流處理的支持。最初,Spark Streaming流處理方法過于簡單,導(dǎo)致在更復(fù)雜的處理中出現(xiàn)問題。Spark 2.0中引入結(jié)構(gòu)化流,不再使用流語義,增加了對時(shí)間事件(event-time)的處理和端到端一致性的支持。而Flink從最開始的設(shè)計(jì)理念上就以流為核心,批處理只是流處理的一個(gè)特例,并致力于流批統(tǒng)一。通過本文多角度的介紹對比,從引用場景、時(shí)間機(jī)制、一致性語義上對兩個(gè)框架做出深入分析,希望在使用過程中用戶能更好地根據(jù)使用場景和業(yè)務(wù)需求選擇合適的流式計(jì)算框架。