我們?cè)谒伎剂魈幚韱?wèn)題上花了很多時(shí)間,更酷的是,我們也花了很多時(shí)間幫助其他人認(rèn)識(shí)流處理,以及如何在他們的組織里應(yīng)用流處理來(lái)解決數(shù)據(jù)問(wèn)題。
我們首先要做的是糾正人們對(duì)流處理(作為一個(gè)快速變化的領(lǐng)域,這里有很多誤見(jiàn)值得我們思考)的錯(cuò)誤認(rèn)識(shí)。
在這篇文章里,我們選出了其中的六個(gè)作為例子。因?yàn)槲覀儗?duì)Apache Flink比較熟悉,所以我們會(huì)基于Flink來(lái)講解這些例子。
謬見(jiàn)1:沒(méi)有不使用批處理的流(Lambda架構(gòu))
謬見(jiàn)2:延遲和吞吐量:只能選擇一個(gè)
謬見(jiàn)3:微批次意味著更好的吞吐量
謬見(jiàn)4:Exactly once?完全不可能
謬見(jiàn)5:流只能被應(yīng)用在“實(shí)時(shí)”場(chǎng)景里
謬見(jiàn)6:不管怎么樣,流仍然很復(fù)雜
謬見(jiàn)1:沒(méi)有不使用批處理的流(Lambda架構(gòu))
“Lambda架構(gòu)”在A(yíng)pache Storm的早期階段和其它流處理項(xiàng)目里是一個(gè)很有用的設(shè)計(jì)模式。這個(gè)架構(gòu)包含了一個(gè)“快速流層”和一個(gè)“批次層”。
之所以使用兩個(gè)單獨(dú)的層,是因?yàn)長(zhǎng)ambda架構(gòu)里的流處理只能計(jì)算出大致的結(jié)果(也就是說(shuō),如果中間出現(xiàn)了錯(cuò)誤,那么計(jì)算結(jié)果就不可信),而且只能處理相對(duì)少量的事件。
就算Storm的早期版本存在這樣的問(wèn)題,但現(xiàn)今的很多開(kāi)源流處理框架都具有容錯(cuò)能力,它們可以在出現(xiàn)故障的前提下生成準(zhǔn)確的計(jì)算結(jié)果,而且具有高吞吐的計(jì)算能力。所以沒(méi)有必要再為了分別得到“快”和“準(zhǔn)確”的結(jié)果而維護(hù)多層架構(gòu)?,F(xiàn)今的流處理器(比如Flink)可以同時(shí)幫你得到兩種結(jié)果。
好在人們不再更多地討論Lambda架構(gòu),說(shuō)明流處理正在走向成熟。
謬見(jiàn)2:延遲和吞吐量:只能選擇一個(gè)
早期的開(kāi)源流處理框架要么是“高吞吐”的,要么是“低延遲”的,而“海量且快速”一直未能成為開(kāi)源流處理框架的代名詞。
不過(guò)Flink(可能還有其它的框架)就同時(shí)提供了高吞吐和低延遲。這里有一個(gè)基準(zhǔn)測(cè)試結(jié)果的樣例。
讓我們從底層來(lái)剖析這個(gè)例子,特別是從硬件層,并結(jié)合具有網(wǎng)絡(luò)瓶頸的流處理管道(很多使用Flink的管道都有這個(gè)瓶頸)。在硬件層不應(yīng)該存在需要作出權(quán)衡的條件,所以網(wǎng)絡(luò)才是影響吞吐量和延遲的主要因素。
一個(gè)設(shè)計(jì)良好的軟件系統(tǒng)應(yīng)該會(huì)充分利用網(wǎng)絡(luò)的上限而不會(huì)引入瓶頸問(wèn)題。不過(guò)對(duì)Flink來(lái)說(shuō),總是有可優(yōu)化的空間,可以讓它更接近硬件所能提供的效能。使用一個(gè)包含10個(gè)節(jié)點(diǎn)的集群,F(xiàn)link現(xiàn)在每秒可以處理千萬(wàn)級(jí)別的事件量,如果擴(kuò)展到1000個(gè)節(jié)點(diǎn),它的延遲可以降低到幾十毫秒。在我們看來(lái),這種水平已經(jīng)比很多現(xiàn)有的方案高出很多。
謬見(jiàn)3:微批次意味著更好的吞吐量
我們可以從另一個(gè)角度來(lái)討論性能,不過(guò)先讓我們來(lái)澄清兩個(gè)容易混淆的概念:
微批次
微批次建立在傳統(tǒng)批次之上,是處理數(shù)據(jù)的一個(gè)執(zhí)行或編程模型。“通過(guò)這項(xiàng)技術(shù),進(jìn)程或任務(wù)可以把一個(gè)流當(dāng)作一系列小型的批次或數(shù)據(jù)塊”。
緩沖
緩沖技術(shù)用于對(duì)網(wǎng)絡(luò)、磁盤(pán)、緩存的訪(fǎng)問(wèn)進(jìn)行優(yōu)化。Wikipedia完美地把它定義為“物理內(nèi)存里的一塊用于臨時(shí)儲(chǔ)存移動(dòng)數(shù)據(jù)的區(qū)域“。
那么第3個(gè)繆見(jiàn)就是說(shuō),使用微批次的數(shù)據(jù)處理框架能夠比每次處理一個(gè)事件的框架達(dá)到更高的吞吐量,因?yàn)槲⑴卧诰W(wǎng)絡(luò)上傳輸?shù)男矢摺?/p>
這個(gè)繆見(jiàn)忽略了一個(gè)事實(shí),流框架不會(huì)依賴(lài)任何編程模型層面的批次,它們只會(huì)在物理層面使用緩沖。
Flink確實(shí)也會(huì)對(duì)數(shù)據(jù)進(jìn)行緩沖,也就是說(shuō)它會(huì)通過(guò)網(wǎng)絡(luò)發(fā)送一組處理過(guò)的記錄,而不是每次發(fā)送一條記錄。從性能方面說(shuō),不對(duì)數(shù)據(jù)進(jìn)行緩沖是不可取的,因?yàn)橥ㄟ^(guò)網(wǎng)絡(luò)逐個(gè)發(fā)送記錄不會(huì)帶來(lái)任何性能上的好處。所以我們得承認(rèn)在物理層面根本不存在類(lèi)似一次一條記錄這樣的情況。
不過(guò)緩沖只能作為對(duì)性能的優(yōu)化,所以緩沖:
對(duì)用戶(hù)是不可見(jiàn)的 不應(yīng)該對(duì)系統(tǒng)造成任何影響 不應(yīng)該出現(xiàn)人為的邊界 不應(yīng)該限制系統(tǒng)功能所以對(duì)Flink的用戶(hù)來(lái)說(shuō),他們開(kāi)發(fā)的程序能夠單獨(dú)地處理每個(gè)記錄,那是因?yàn)镕link為了提升性能隱藏了使用緩沖的細(xì)節(jié)。
事實(shí)上,在任務(wù)調(diào)度里使用微批次會(huì)帶來(lái)額外的開(kāi)銷(xiāo),而如果這樣做是為了降低延遲,那么這種開(kāi)銷(xiāo)會(huì)只增不減!流處理器知道該如何利用緩沖的優(yōu)勢(shì)而不會(huì)帶來(lái)任務(wù)調(diào)度方面的開(kāi)銷(xiāo)。
謬見(jiàn)4:Exactly once?完全不可能
這個(gè)繆見(jiàn)包含了幾個(gè)方面的內(nèi)容:
從根本上說(shuō),Exactly once是不可能的 從端到端的Exactly once是不可能的 Exactly once從來(lái)都不是真實(shí)世界的需求 Exactly once以犧牲性能為代價(jià)讓我們退一步講,我們并不介意“Exactly once”這種觀(guān)點(diǎn)的存在。“Exactly once”原先指的是“一次性傳遞”,而現(xiàn)在這個(gè)詞被隨意用在流處理里,讓這個(gè)詞變得令人困惑,失去了它原本的意義。不過(guò)相關(guān)的概念還是很重要的,我們不打算跳過(guò)去。
為了盡量準(zhǔn)確,我們把“一次性狀態(tài)”和“一次性傳遞”視為兩種不同的概念。因?yàn)橹叭藗儗?duì)這兩個(gè)詞的使用方式導(dǎo)致了它們的混淆。Apache Storm使用“at least once”來(lái)描述傳遞(Storm不支持狀態(tài)),而Apache Samza使用“at least once”來(lái)描述應(yīng)用狀態(tài)。
一次性狀態(tài)是指應(yīng)用程序在經(jīng)歷了故障以后恍如沒(méi)有發(fā)生過(guò)故障一樣。例如,假設(shè)我們?cè)诰S護(hù)一個(gè)計(jì)數(shù)器應(yīng)用程序,在發(fā)生了一次故障之后,它既不能多計(jì)數(shù)也不能少計(jì)數(shù)。在這里使用“Exactly once”這個(gè)詞是因?yàn)閼?yīng)用程序狀態(tài)認(rèn)為每個(gè)消息只被處理了一次。
一次性傳遞是指接收端(應(yīng)用程序之外的系統(tǒng))在故障發(fā)生后會(huì)收到處理過(guò)的事件,恍如沒(méi)有發(fā)生過(guò)故障一樣。
流處理框架在任何情況下都不保證一次性傳遞,但可以做到一次性狀態(tài)。Flink可以做到一次性狀態(tài),而且不會(huì)對(duì)性能造成顯著影響。Flink還能在與Flink檢查點(diǎn)相關(guān)的數(shù)據(jù)槽上做到一次性傳遞。
Flink檢查點(diǎn)就是應(yīng)用程序狀態(tài)的快照,F(xiàn)link會(huì)為應(yīng)用程序定時(shí)異步地生成快照。這就是Flink在發(fā)生故障時(shí)仍然能保證一次性狀態(tài)的原因:Flink定時(shí)記錄(快照)輸入流的讀取位置和每個(gè)操作數(shù)的相關(guān)狀態(tài)。如果發(fā)生故障,F(xiàn)link會(huì)回滾到之前的狀態(tài),并重新開(kāi)始計(jì)算。所以說(shuō),盡管記錄被重新處理,但從結(jié)果來(lái)看,記錄好像只被處理過(guò)一次。
那么端到端的一次性處理呢?通過(guò)恰當(dāng)?shù)姆绞阶寵z查點(diǎn)兼具事務(wù)協(xié)調(diào)機(jī)制是可能的,換句話(huà)說(shuō),就是讓源操作和目標(biāo)操作參與到檢查點(diǎn)里來(lái)。在框架內(nèi)部,結(jié)果是一次性的,從端到端來(lái)看,也是一次性的,或者說(shuō)“接近一次性”。例如,在使用Flink和Kafka作為數(shù)據(jù)源并發(fā)生數(shù)據(jù)槽(HDFS)滾動(dòng)時(shí),從Kafka到HDFS就是端到端的一次性處理。類(lèi)似地,在把Kafka作為Flink的源并且把Cassandra作為Flink的槽時(shí),如果針對(duì)Cassandra的更新是冪等時(shí),那么就可以實(shí)現(xiàn)端到端的一次性處理。
值得一提的是,利用Flink的保存點(diǎn),檢查點(diǎn)可以兼具狀態(tài)版本機(jī)制。使用保存點(diǎn),在保持狀態(tài)一致性的同時(shí)還可以“隨著時(shí)間移動(dòng)”。這樣可以讓代碼的更新、維護(hù)、遷移、調(diào)試和各種模擬測(cè)試變得簡(jiǎn)單。
謬見(jiàn)5:流只能被應(yīng)用在“實(shí)時(shí)”場(chǎng)景里
這個(gè)謬見(jiàn)包括幾點(diǎn)內(nèi)容:
“我沒(méi)有低延遲的應(yīng)用,所以我不需要流處理器” “流處理只跟那些持久化之前的過(guò)渡數(shù)據(jù)有關(guān)系” “我們需要批處理器來(lái)完成笨重的離線(xiàn)計(jì)算”現(xiàn)在是時(shí)候思考一下數(shù)據(jù)集的類(lèi)型和處理模型之間的關(guān)系了?!?br /> 首先,有兩種數(shù)據(jù)集:
沒(méi)有邊界的:從非預(yù)定義的端點(diǎn)持續(xù)產(chǎn)生的數(shù)據(jù) 有邊界的:有限且完整的數(shù)據(jù)很多真實(shí)的數(shù)據(jù)集是沒(méi)有邊界的,不管這些數(shù)據(jù)時(shí)存儲(chǔ)在文件里,還是在HDFS的目錄里,還是在像Kafka這樣的系統(tǒng)里。舉一些例子:
移動(dòng)設(shè)備或網(wǎng)站用戶(hù)的交互信息 物理傳感器提供的度量指標(biāo) 金融市場(chǎng)數(shù)據(jù) 機(jī)器日志數(shù)據(jù)實(shí)際上,在現(xiàn)實(shí)世界中很難找到有邊界的數(shù)據(jù)集,不過(guò)一個(gè)公司所有大樓的位置信息倒是有邊界的(不過(guò)它也會(huì)隨著公司業(yè)務(wù)的增長(zhǎng)而變化)。
其次,有兩種處理模型:
流:只要有數(shù)據(jù)生成就會(huì)一直處理 批次:在有限的時(shí)間內(nèi)結(jié)束處理,并釋放資源讓我們?cè)偕钊胍稽c(diǎn),來(lái)區(qū)分兩種沒(méi)有邊界的數(shù)據(jù)集:連續(xù)性流和間歇性流。
使用任意一種模型來(lái)處理任意一種數(shù)據(jù)集是完全可能的,雖然這不是最優(yōu)的做法。例如,批次處理模型被長(zhǎng)時(shí)間地應(yīng)用在無(wú)邊界的數(shù)據(jù)集上,特別是間歇性的無(wú)邊界數(shù)據(jù)集。現(xiàn)實(shí)情況是,大多數(shù)“批處理”任務(wù)是通過(guò)調(diào)度來(lái)執(zhí)行的,每次只處理無(wú)邊界數(shù)據(jù)集的一小部分。這意味著流的無(wú)邊界特質(zhì)會(huì)給某些人帶來(lái)麻煩(那些工作在流入管道上的人)。
批處理是無(wú)狀態(tài)的,輸出只取決于輸入?,F(xiàn)實(shí)情況是,批處理任務(wù)會(huì)在內(nèi)部保留狀態(tài)(比如reducer經(jīng)常會(huì)保留狀態(tài)),但這些狀態(tài)只限在批次的邊界內(nèi),而且它們不會(huì)在批次間流竄。
當(dāng)有人嘗試實(shí)現(xiàn)類(lèi)似帶有“事件時(shí)間戳”的時(shí)間窗,那么“批次的邊界內(nèi)狀態(tài)”就會(huì)變得很有用,這在處理無(wú)邊界數(shù)據(jù)集時(shí)是個(gè)很常用的手段。
處理無(wú)邊界數(shù)據(jù)集的批處理器將不可避免地遇到遲到事件(因?yàn)樯嫌蔚难舆t),批次內(nèi)的數(shù)據(jù)有可能因此變得不完整。要注意,這里假設(shè)我們是基于事件時(shí)間戳來(lái)移動(dòng)時(shí)間窗的,因?yàn)槭录r(shí)間戳是現(xiàn)實(shí)當(dāng)中最為準(zhǔn)確的模型。在執(zhí)行批處理的時(shí)候,遲到的數(shù)據(jù)會(huì)成為問(wèn)題,即使通過(guò)簡(jiǎn)單的時(shí)間窗修復(fù)(比如翻轉(zhuǎn)或滑動(dòng)時(shí)間窗)也解決不了這個(gè)問(wèn)題,特別是如果使用會(huì)話(huà)時(shí)間窗,就更難以處理了。
因?yàn)橥瓿梢粋€(gè)計(jì)算所需要的數(shù)據(jù)不會(huì)都在一個(gè)批次里,所以在使用批次處理無(wú)邊界數(shù)據(jù)集時(shí),很難保證結(jié)果的正確性。最起碼,它需要額外的開(kāi)銷(xiāo)來(lái)處理遲到的數(shù)據(jù),還要維護(hù)批次之間的狀態(tài)(要等到所有數(shù)據(jù)達(dá)到后才開(kāi)始處理,或者重新處理批次)。
Flink內(nèi)建了處理遲到數(shù)據(jù)的機(jī)制,遲到數(shù)據(jù)被視為真實(shí)世界無(wú)邊界數(shù)據(jù)的正常現(xiàn)象,所以Flink設(shè)計(jì)了一個(gè)流處理器專(zhuān)門(mén)處理遲到數(shù)據(jù)。
有狀態(tài)的流處理器更適合用來(lái)處理無(wú)邊界數(shù)據(jù)集,不管數(shù)據(jù)集是持續(xù)生成的還是間歇生成的。使用流處理器只是個(gè)錦上添花的事情。
Tyler Akidau的系列文章“超越批處理:流101”里有更多關(guān)于這方面內(nèi)容的描述。
繆見(jiàn)6:不管怎么樣,流仍然很復(fù)雜
這是最后一個(gè)繆見(jiàn)。你也許會(huì)想:“理論雖好,但我仍然不會(huì)采用流技術(shù),因?yàn)?hellip;…”:
流框架難以掌握 流難以解決時(shí)間窗、事件時(shí)間戳、觸發(fā)器的問(wèn)題 流需要結(jié)合批次,而我已經(jīng)知道如何使用批次,那為什么還要使用流?我們從來(lái)沒(méi)有打算慫恿你使用流,雖然我們覺(jué)得流是個(gè)很酷的東西。我們相信,是否使用流完全取決于數(shù)據(jù)和代碼的特點(diǎn)。
在做決定之前問(wèn)問(wèn)自己:“我正在跟什么樣類(lèi)型的數(shù)據(jù)集打交道?”
無(wú)邊界的(用戶(hù)活動(dòng)數(shù)據(jù)、日志、傳感器數(shù)據(jù)) 有邊界的然后再問(wèn)另一個(gè)問(wèn)題:“哪部分變化最頻繁?”
代碼比數(shù)據(jù)變化更頻繁 數(shù)據(jù)比代碼變化更頻繁對(duì)于數(shù)據(jù)比代碼變化更頻繁的情況,例如在經(jīng)常變化的數(shù)據(jù)集上執(zhí)行一個(gè)相對(duì)固定的查詢(xún)操作,這樣會(huì)出現(xiàn)流方面的問(wèn)題。
所以,在認(rèn)定流是一個(gè)“復(fù)雜”的東西之前,你可能在不知不覺(jué)中已經(jīng)解決過(guò)流方面的問(wèn)題!你可能使用過(guò)基于小時(shí)的批次任務(wù)調(diào)度,團(tuán)隊(duì)里的其他人可以創(chuàng)建和管理這些批次(在這種情況下,你得到的結(jié)果可能是不準(zhǔn)確的,而你意識(shí)不到這樣的結(jié)果是批次的時(shí)間問(wèn)題和之前提過(guò)的狀態(tài)問(wèn)題造成的)。
為了能夠提供一組封裝了這些時(shí)間和狀態(tài)復(fù)雜性的API,F(xiàn)link社區(qū)為此工作了很長(zhǎng)時(shí)間。在Flink里可以很簡(jiǎn)單地處理事件時(shí)間戳,只要定義一個(gè)時(shí)間窗口和一個(gè)能夠抽取時(shí)間戳和水印的函數(shù)(只在每個(gè)流上調(diào)用一次)。處理狀態(tài)也很簡(jiǎn)單,類(lèi)似于定義Java變量,再把這些變量注冊(cè)到Flink。使用Flink的StreamSQL可以在源源不斷的流上面運(yùn)行SQL查詢(xún)。
最后一點(diǎn):對(duì)代碼比數(shù)據(jù)變化更頻繁的情況該怎么辦?對(duì)于這種情況,我們認(rèn)為你遇到了探索性問(wèn)題。使用筆記本或其它類(lèi)似的工具進(jìn)行迭代可能適合用來(lái)解決探索性問(wèn)題。
在代碼穩(wěn)定了之后,你仍然會(huì)碰到流方面的問(wèn)題。我們建議從一開(kāi)始就使用長(zhǎng)遠(yuǎn)的方案來(lái)解決流方面的問(wèn)題。
流處理的未來(lái)
隨著流處理的日漸成熟和這些繆見(jiàn)的逐步淡去,我們發(fā)現(xiàn)流正朝著除分析應(yīng)用之外的領(lǐng)域發(fā)展。正如我們所討論的那樣,真實(shí)世界正連續(xù)不斷地生成數(shù)據(jù)。
傳統(tǒng)的做法會(huì)中斷這些連續(xù)的數(shù)據(jù),因?yàn)檫@些數(shù)據(jù)必須被聚合到一個(gè)集中的位置,或者被切分成批次,方便應(yīng)用程序使用。
像CQRS這樣的流處理模式越來(lái)越流行,應(yīng)用程序可以直接基于持續(xù)的數(shù)據(jù)流進(jìn)行開(kāi)發(fā),這樣可以在本地保留狀態(tài),可以更好地隔離應(yīng)用和團(tuán)隊(duì),可以更好地處理基于時(shí)間的數(shù)據(jù)。
隨著Flink不斷地演化改進(jìn),并被越來(lái)越多的企業(yè)所采用,我們相信它不僅僅能夠用來(lái)簡(jiǎn)化分析管道,還能夠?yàn)槲覀儙?lái)更強(qiáng)大的計(jì)算模型。
查看原文鏈接:Stream Processing Myths Debunked