[TOC]
Dataflow 圖
顧名思義, Dataflow 程序描述了數(shù)據(jù)如何在不同操作之間流動。 Dataflow 程序通常表示為有向圖怪蔑。圖中頂點稱為算子,表示計算:而邊表示數(shù)據(jù)依賴關系吁伺。算子是 Dataflow程序的基本功能單元饮睬,它們從輸入獲取數(shù)據(jù),對其進行計算篮奄, 然后產(chǎn)生數(shù)據(jù)并發(fā)往輸出以供后續(xù)處理。沒有輸入端的算子稱為數(shù)據(jù)掘窟却,沒 有輸出端的算子稱為數(shù)據(jù)匯昼丑。 一個 Dataflow 圖至少要有一個數(shù)據(jù)源和一個數(shù)據(jù)匯。圖 2-1展示了一個從推文輸入流中提取并統(tǒng)計主題標簽的 Dataflow程序夸赫。
國 2-1 :一個持續(xù)統(tǒng)計主題標簽數(shù)目的 Dataflow邏輯圖 (頂點表示算子菩帝, 邊表示數(shù)據(jù)依賴)
類似圖 2-1 的 Dataflow 圖被稱作邏輯圖,因為它們表達了高層視角下的計算 邏輯茬腿。為了執(zhí)行Dataflow程序呼奢, 需要將邏輯圖轉化為物理Dataflow圖,后 者會指定程序的執(zhí)行細節(jié)切平。例如: 當我們使用分布式處理引擎時握础,每個算子 可能會在不同物理機器上運行多個并行任務。醫(yī)I 2-2 展示了圖 2-1 中邏輯圖 所對應的物理 Dataflow 圖悴品。在邏輯 Dataflow 圖中禀综,頂點代表算子;在物理 Dataflow 圖中简烘,頂點代表任務。 “抽取主題標簽”和“計數(shù)”算子都包含兩 個并行算子任務定枷,每個任務負責計算一部分輸入數(shù)據(jù) 孤澎。
數(shù)據(jù)并行和任務并行
Dataflow 圖的井行性可以通過多種方式 加 以利用。首先欠窒,你可 以 將輸入數(shù) 據(jù)分組覆旭,讓同一操作的多個任務并行執(zhí)行在不同數(shù)據(jù)子集上,這種井行稱為 數(shù)據(jù)井行( data para!!巳!ism)岖妄。數(shù)據(jù)并行非常有用姐扮,因為它能夠將計算負載 分配到多個節(jié)點上從而允許處理大規(guī)模的數(shù)據(jù)。再者衣吠,你可以讓不同算子的任務(基于相同或不同的數(shù)據(jù))并行計算,這種并行稱為任務井行( task parallelism)壤靶。通過任務并行缚俏,可以更好地利用集群的計算資掘。
數(shù)據(jù)交換策略
數(shù)據(jù)交換策略定義了如何將數(shù)據(jù)項分配給物理 Dataflow 圖中的不同任務贮乳。這 些策略可以由執(zhí)行引擎根據(jù)算子的語義自動選擇忧换,也可以由 Dataflow 編程人 員顯式指定。接下來向拆,我們結合圖 2-3 來簡單了解一下常見的數(shù)據(jù)交換策略 亚茬。
- 轉發(fā)策略( forward strategy)在發(fā)送端任務和接收端任務之 間一對一地進 行數(shù)據(jù)傳輸。如果兩端任務運行在同 一物理機 器上(通常 由任務調(diào)度器決 定)浓恳,該交換策略可以避免網(wǎng)絡通信刹缝。
- 廣播策略(broadcast strategy)會把每個數(shù)據(jù)項發(fā)往下游算子的全部并行 任務。 i亥策略會把數(shù)據(jù)復制多份且涉及網(wǎng)絡通信颈将,因此代價十分昂貴梢夯。
- 基于鍵值的策略(key-based strategy)根據(jù)某一鍵值屬性對數(shù)據(jù)分區(qū),并保證鍵值相同的數(shù)據(jù)項會交由同一任務處理晴圾。圖2-2中颂砸, “抽取主題標簽” 算子的輸出就是按照鍵值(主題標簽)戈!J分的,因 此下游的計數(shù)算子可以 正確計算出每個主題標簽的出現(xiàn)次數(shù)死姚。
- 隨機策略(random strategy)會將數(shù)據(jù)均勻分配至算子的所有任務人乓,以實 現(xiàn)計算任務的負載均衡。
并行流處理
現(xiàn)在你已經(jīng)對 Dataflow 編程的基礎有所了解 都毒。接下來我們看一下如何將這些 概念應用到井行數(shù)據(jù)流處理中色罚。在 此之 前我 們 先給出數(shù)據(jù)流 的 定義 : 數(shù)據(jù)流 是一個可能無限的事件序列。
數(shù)據(jù)流中的事 件可以 表示監(jiān)控數(shù)據(jù)温鸽、傳感器 測 量 值保屯、信用卡交易手负、氣象站觀 切!|數(shù)據(jù)、在線用戶交互姑尺,以及網(wǎng)絡搜索等竟终。本節(jié)你將學到 如何利用 Dataflow 編程范式并行處理無限數(shù)據(jù)流。
延遲和吞吐
在第 l 章切蟋, 你已經(jīng)了解到流式應用和傳統(tǒng)批處理程序在操作需求上有所差異统捶, 而這些需求差異還體現(xiàn)在性能 i平測方面。對批處理應用而言柄粹,我們通常會關 心作業(yè)的總執(zhí)行時間喘鸟,或者說處理引擎讀取輸入、執(zhí)行計算驻右、寫回結果總共 需要多長 時間 什黑。但由于流式應用會持續(xù)執(zhí)行且輸入可能是無限的, 所以 在數(shù) 據(jù)流處理 中沒有總執(zhí)行時間的概 念堪夭。取 而代 之的是 愕把,流式 應 用 需要 針對到 來 數(shù)據(jù)盡 可能快地計 算結果,同 時還 要應對很高 的 事件接 入速 率森爽。 我們用延遲 和吞吐來表示這兩方面的性能需求 恨豁。
延遲
延遲表示處理一個事件所需的時間。本質上爬迟,它是從接收事件到在輸出中觀 察到事件處理效果的時間 |同隔橘蜜。為了直觀地理解延遲,想 一 下你每天都會光 顧自己喜歡的咖啡店付呕。當你進 門的時候计福,可能已經(jīng)有別的顧客在里面了。這 時候你就需要排隊凡涩,等輪到你的時候再開始點單棒搜。收銀員收到你的付款后會 把訂單交給幫你準備飲品的咖啡師』罨咖啡制作完成后力麸,咖啡師會叫你的名字, 你來從吧臺取走咖啡育韩。所謂服務延遲就是你在店內(nèi)買咖啡的時間克蚂,即從你進 門的 一 刻到你喝到第一口咖啡的時間。
在流處理中筋讨,延遲是以時間片(例如毫秒)為單位測量的埃叭。根據(jù)應用的不同,你可能會關注平均延遲悉罕,最大延遲或延遲的百分位數(shù)值赤屋。例如:平均延遲為 10 毫秒表示平均每條數(shù)據(jù)會在 10 毫秒 內(nèi)處理;而第 95 百分位延遲在 10 毫 秒意味著 95% 的事件會在 10毫秒內(nèi)處理 立镶。平均值會掩蓋處理延遲的真實分布, 從而導致難以發(fā)現(xiàn)問題类早。如果咖啡師在給你準備卡布奇諾前剛好把牛奶用光 了媚媒,那么你必須等他從供應間再拿 一 些出來。雖然你可能因為這次耽擱而不高興涩僻,但其余大多數(shù)顧客可能絲毫不會為此影響缭召,心情。
保證低延遲對很多流式應用(例如:詐騙識別逆日、系統(tǒng)告 警 嵌巷、網(wǎng)絡監(jiān)測,以及 遵循服務級別協(xié)議( SLA)的服務)而言至關重要室抽。低延遲是流處理的一個 關鍵特性搪哪,它滋生出了所謂的實時應用。像 Apache Flink 這樣的現(xiàn)代化流處 理引擎可以提供低至幾毫秒的延遲坪圾。相反噩死,傳統(tǒng)批處理的延遲可能從幾分鐘 到幾小時不等。在批處理中神年,你先要批量收集事件,然后才能處理它們行嗤。因 此處理延遲受制于每個批次最遲事件的時間已日,且天然受到批次大小的影響。 真正的流處理不會引人人為延遲等要素栅屏,只有這樣才能將延遲將至極低飘千。在 真正的流模型中,事件一到達系統(tǒng)就可以進行處理栈雳,延遲會更加真實地反映 出每個事件都要經(jīng)歷的實際處理工作护奈。
吞吐
吞吐是用來衡量系統(tǒng)處理能力(處理速率)的指標,它告訴我們系統(tǒng)每單位 時間可以處理多少事件哥纫∶蛊欤回到剛剛咖啡店的例子,如果它的營業(yè)時間是早 7 點到晚 7 點蛀骇,并且 一天 服務了 600 名顧客厌秒,那么它的平均吞吐是 so 人/小時。 通常情況下延遲是越低越好擅憔,而顯然吞吐則是越高越好 鸵闪。
吞吐的衡量方式是計算每個單位時間的 事件或操作數(shù)。但要注意暑诸,處理速率 取決于數(shù)據(jù)到來速率蚌讼,因此吞吐低不 一 定意味著性能 差辟灰。在 流處理系統(tǒng)中, 你通常希望系統(tǒng)有能力應對以最大期望速率到來的事件 篡石。 換言之芥喇,首要的關 注點是確定峰值吞吐,即系統(tǒng)楠負載時的性能上限夏志。為了更好地理解峰值吞 吐的概念乃坤,我們先假設某個流處理應用沒有在接收任何數(shù)據(jù),也因此無需占 用任何系統(tǒng)資源沟蔑。當首個事件進入時湿诊,系統(tǒng)會立刻以盡可能低的延遲進行處理。 這就如同你是早晨咖啡店開門后的首位顧客瘦材,會立即享受服務厅须。理想情況下, 你會希望延遲保持平穩(wěn)食棕,不受事件到來速率的影響朗和。但現(xiàn)實中, 一旦事 件到 達速率過高致使系統(tǒng)沒有空閑資源簿晓,系統(tǒng)就會被迫開始緩沖事件眶拉。在咖啡店 的例子中,你很有可能在午 每后見到這種情況 :店內(nèi) 突然 間涌入大 量 顧客憔儿, 點單的人排起了長隊 忆植。 此時系統(tǒng)吞吐已到極限, 一味提高事件到達速率只會 讓延遲更糟谒臼。如果系統(tǒng)持續(xù)以力不能及的高速率接收數(shù)據(jù)朝刊,那么緩沖區(qū)可能 會用盡,繼而可能導致數(shù)據(jù)丟失蜈缤。這種情形通常被稱為背壓( backpressure) , 我們有多種可選策略來處理它拾氓。
延遲與吞吐
至此你應該已經(jīng)清楚,延遲和吞吐并非相 互獨立 的指標 底哥。如果事件在數(shù)據(jù)處 理管道中傳輸時間太久咙鞍,我們將難以確保高吞吐;同樣,如果系統(tǒng)性能不足趾徽, 事件很容易堆積緩沖奶陈,必須等待一段時間才能處理。
我們再通過咖啡店的例子來解釋一下延遲和吞吐如何相互影響附较。首先需要明 確的是吃粒,在空負載的情況下延遲會達到最優(yōu)。也就是說拒课,如果咖啡店只有你 一 名顧客徐勃, f爾將獲得最快的服務事示。然而,在高峰時段僻肖,顧客必須要排隊肖爵,此 時延遲將增加。影響延遲和相應吞吐的另 一 因素是處理單個事件的時間臀脏,即 在咖啡店服務每一名顧客所需的時間劝堪。假設現(xiàn)在正值圣誕假期,咖啡師要在 他們完成的每杯咖啡的杯 子上畫一個圣誕老人揉稚。這意味著準備單杯 咖啡的時 間會延長秒啦,繼而導致每位顧客在店里花 費的時間增加, 此時 整體吞吐量將會 下降搀玖。
既然這樣余境,可以通過某種方式同時獲得低延遲和高吞吐嗎?還是說這根本不 切實際?在咖啡店的例子中,為了降低延遲灌诅,店家可以雇傭更嫻熟的咖啡師芳来, 他們制作咖啡會更快 一 些。這樣在高峰時段猜拾,相同時間內(nèi)可以服務的顧客數(shù) 量多了即舌,吞吐量 自然也會提高。另一個殊途同歸的辦怯是再雇一個咖啡師挎袜, 即利用并行解決問題 侥涵。 此處的要點在于:降低延遲實際上可以提高吞吐。顯然宋雏, 系統(tǒng)執(zhí)行操作越快,相同時間內(nèi)執(zhí)行的操作數(shù)目就會越 多 务豺。 事 實上磨总,這就是 在流處理管道中利用井行實現(xiàn)的效果。通過井行處理多條數(shù)據(jù)流笼沥,可 以在處 理更多事件的同時降低延遲蚪燕。
數(shù)據(jù)流上的操作
流處理引擎通常會提供一系列內(nèi)置操作來實現(xiàn)數(shù)據(jù)流的獲取、轉換奔浅,以及輸出 馆纳。 這些算子可以組合生成 Dataflow 處理圖,從而實現(xiàn)流式應用所需的邏輯汹桦。本 節(jié)我們將介紹最常見的流式操作鲁驶。
這些操作既可以是無狀態(tài)( stateless)的,也可以是有狀態(tài)( stateful)的舞骆。無 狀態(tài)的操作不會維持內(nèi)部狀態(tài)钥弯,即處理事件時無需依賴己處理過的事件径荔,也 不保存歷史數(shù)據(jù)。由于事件處理互不影響且與事件到來的時間無關脆霎,無狀態(tài) 的操作很容易并行化总处。此外,如果發(fā)生故障睛蛛,無狀態(tài)的算子可以很容易地重啟鹦马, 并從中斷處繼續(xù)工作。 相反忆肾,有狀態(tài)算子可能需要維護之前接收的事件信息荸频。 它們的狀態(tài)會根據(jù)傳入的 事件更新,并用于未來 事件的處理邏輯中难菌。有狀態(tài) 的流處理應用在并行化和容錯方面會更具挑戰(zhàn)性试溯,因為它們需要對狀態(tài)進行 高效 劃分,并且 在出錯時需進行可靠 的故障恢 復郊酒。
數(shù)據(jù)接入和數(shù)據(jù)輸出
數(shù)據(jù)接入和數(shù)據(jù)輸出操作允許流處理引擎和外部系統(tǒng)進行通信遇绞。數(shù)據(jù)接入操作是從外部數(shù)據(jù)驚獲取原始數(shù)據(jù)并將其轉換成適合后續(xù)處理的格式。實現(xiàn)數(shù)據(jù)接入操作邏輯的算子稱為數(shù)據(jù)源燎窘。數(shù)據(jù)驚可 以從 TCP 套 接 字 摹闽、文件、 Kafka主題或傳感器數(shù)據(jù)接口中獲取數(shù)據(jù)褐健。數(shù)據(jù)輸出操作是將數(shù)據(jù)以適合外部 系統(tǒng)使用的格式輸出付鹿。負責數(shù)據(jù)輸出的算子稱為數(shù)據(jù)匯,其寫入的目標可以 是文件蚜迅、數(shù)據(jù)庫舵匾、消息隊列或監(jiān)控接口等。
轉換操作
轉換操作是一類“只過一次”的操作谁不,它 們 會分 別處理每個 事 件坐梯。這些操作逐個讀取事件,對其應用某些轉換并產(chǎn)生一條新的輸出流刹帕。 如圖 2-4所示吵血, 轉換邏輯 可以 是算子內(nèi)置的, 也可以由用 戶自定義函數(shù)提供偷溺。函數(shù) 由應用開 發(fā)人員編寫蹋辅,可用來實現(xiàn)某些自定義的計算邏輯。
算子 既可以同時接收 多 個輸入流或 產(chǎn)生多條 輸出流挫掏,也可以通過單流分割或 合并多條 流來改變 Dataflow 圖的結構
滾動聚合
滾動聚合 (如求和侦另、求最小值和求最大值 ) 會根據(jù)每個到來的事件持續(xù)更新 結果。聚合操作都是有狀態(tài)的,它 們通過將 新到來的事件合并到 已有狀態(tài) 來 生成更新后的聚合值淋肾。注意硫麻,為了更有效地合并事件和當前 狀態(tài)并生成單個 結果,聚合函數(shù)必須滿足可結合( associative)及可交換( commutative)的條件, 否則算子就需要存儲整個流的歷史記錄。圖 2-5展示了一個求最小值的攘動 聚合凶伙,其算子會維護當前的最小值,并根據(jù)每個到來的事件去更新這個值浇辜。
窗口操作
轉換操作和該動聚合每次處理一個事件來產(chǎn)生輸出井(可能)更新狀態(tài)。然而唾戚, 有些操作必須收集并緩沖記錄才能計算結果柳洋,例如流式 Join 或像是求中位數(shù) 的整體聚合( holistic aggregate)。為了在無限數(shù)據(jù)流上高效地執(zhí)行這些操作叹坦, 必須對操作所維持的數(shù)據(jù)量加以限制熊镣。本節(jié)我們將討論支持該項功能的窗口 操作。
除了產(chǎn)生單個有用的結果募书,窗口操作還支持在數(shù)據(jù)流上完成一些具有切實語 義價值的查詢绪囱。你已經(jīng)了解攘動聚合是如何將整條歷史流壓縮成一個聚合值, 以及如何針對每個事件在極低延遲內(nèi)產(chǎn)生結果莹捡。 i亥操作對某些應用而言是可 行的鬼吵,但如果你只對最新的那部分數(shù)據(jù)感興趣該怎么辦?假設有 一個應用能 向司機提供實時路況信息以幫助他們躲避擁堵 。在該場景下篮赢, 你只想知道在 最近幾分鐘內(nèi)某個特定位置有沒有發(fā)生交通事故齿椅,而可能對該位置發(fā)生過的 所有事故并不感興趣。此外启泣,將整條歷史流 合并為單個聚合值會丟失數(shù)據(jù)隨 時間變化的信息涣脚。例如,你可能想了解某路口每 5 分鐘的 車流量寥茫。
窗口操作會持續(xù)創(chuàng)建 一 些稱為“桶”的有限事件集合遣蚀,并允許我們基于這些 有限集進行計算。事件通常會根據(jù)其時間或其他數(shù)據(jù)屬性分配到不同桶中坠敷。 為了準確定義窗口算子語義,我們需要決定事件如何分配到桶中以及窗口用 怎樣的頻率產(chǎn)生結果射富。窗口的行為是由 一 系列策略定義的膝迎,這些窗口策略決 定了什么時間創(chuàng)建桶,事件如何分配到桶中以及桶內(nèi)數(shù)據(jù)什么時間參與計算胰耗。
其中參與計算的決策會根據(jù)觸發(fā)條件判定限次,當觸發(fā)條件滿足時,桶內(nèi)數(shù)據(jù)會 發(fā)送給一個計算函數(shù)(evaluation function),由它來對桶中的元素應用計算 邏輯卖漫。這些計算函數(shù)可以是某些聚合(例如求和费尽、求最小值), 也 可以是 一 些直接作用于桶內(nèi)收集元素的自定義操作羊始。策略的指定可以基于 時 間(例如 最近 5秒鐘接收的事件)旱幼、數(shù)量 (例如最新 100個事件)或其他數(shù)據(jù)屬性。 我們會在接下來介紹常見窗口類型的語義突委。
- 攘動窗口(tumbling window)將事件分配到長度固定且互不重疊的桶中柏卤。在 窗口邊界通過后,所有事件會發(fā)送給計算函數(shù)進行處理匀油≡蹈浚基于數(shù)量的( count- based)該動窗口定義了在觸發(fā)計算前需要集齊多少條事件。圖 2-6中基于 數(shù)量的攘動窗口將輸入流按每 4個元素一組分配到不同的桶中敌蚜∏疟酰基于時間的
(time-based)滾動窗口定義了在桶中緩沖數(shù)據(jù)的時間間隔。圖 2-7 中基于時 間的攘動窗口將事件匯集到桶中弛车, 每 10分鐘觸發(fā)一次計算齐媒。
- 滑動窗口(sliding window)將事件分配到大小固定且允許相互重疊的桶中, 這意味著每個事件可能會同時屬于多個桶帅韧。 我們通過指定長度和滑動間隔來 定義滑動窗口里初。滑動間隔決定每隔多久生成一個新的桶忽舟。在圖 2-8 中双妨,基于 數(shù)量的滑動窗口的長度為 4 個事件,滑動間隔為 3 個事件叮阅。
會話窗口(session window)在一些常見的真實場景中非常有用刁品,這些場 景既不適合用攘動窗口也不適合用滑動窗口。假設有 一 個應用要在線分析 用戶行為浩姥,在該應用中我們要把事件按照用戶的同 一活動或會話來源進行 分組挑随。會店由發(fā)生在相鄰時間內(nèi)的一系列事件外加一段非活動時間組成。 例如勒叠,用戶瀏覽一連串新聞文章的交互過程可以看作一個會話兜挨。由于會話 長度并非預先定義好,而是和實際數(shù)據(jù)有關眯分,所以無論是滾動還是滑動窗 口都無怯用于該場景拌汇。而我們需要一個窗口操作,能將屬于同一會話的事 件分配到相同桶中弊决。會i舌窗口根據(jù)會i舌間隔(session gap)將事件分為不 同的會話噪舀, i亥間隔值定義了會 i舌在關閉前的非活動時間長度魁淳。圖 2-9 展示 了一個會 i舌窗口。
迄今為止你所 見 到的所有窗口都是基于全局流數(shù)據(jù)的窗口与倡。但在實際應用中界逛, 你可能會想將數(shù)據(jù)流劃分為多條邏輯流井定義 一 些并行窗口。例如纺座,如果你 在收集來自不同傳感器的測量值息拜,那么可能會想在應用窗口計算前按照傳感 器 ID 對數(shù)據(jù)流進行劃分。并行窗口中比驻,每個數(shù)據(jù)分區(qū)所應用的窗口策略都相 互獨立该溯。圖 2-10展示了一個按事件顏色劃分、基于數(shù)量 2的并行該動窗口别惦。
窗口操作與流處理中兩個核心概念密切相關 :時 間語義( time semantics)和 狀態(tài)管理( state management)狈茉。時間可能是流處理中最重要的一個方面。盡 管低延遲是流處理中一個很吸引人的特性掸掸,但流處理的真正價值遠不止提供 快速分析÷惹欤現(xiàn)實世界的系統(tǒng)、網(wǎng)絡及通信信道往往充斥著缺陷扰付,因此流數(shù)據(jù) 通常都會有所延遲或者以亂序到達堤撵。了解如何在這種情況下提供精準、確定 的結果就變得至關重要羽莺。此外实昨,處理實時事件的流處理應用還應以相同的方 式處理歷史事件,這樣才能支持離線分析盐固,甚至時間旅行式分析( time travel analyse)荒给。當然,如果你的系統(tǒng)無告在故障時保護狀態(tài)刁卜,那 一切都是空談志电。 至今為止你見到的所有窗口類型都要在生成結果前緩沖數(shù)據(jù)。實際上蛔趴,如果 你想在流式應用中計算任何有意義的結果(即便是簡單的計數(shù))挑辆,都需要維 護狀態(tài)⌒⑶椋考慮到流式應用可能需要整日鱼蝉、甚至長年 累月 地運行,因此必須保 證出錯時其狀態(tài)能進行可靠的恢復箫荡,并且即使系統(tǒng)發(fā)生故障系統(tǒng)也能提供準 確的結果魁亦。在本章剩余部分,我們將深入研究流處理中的時間以及在發(fā)生故 障時和狀態(tài)保障相關概念菲茬。
時間語義
本節(jié)我們將介紹流式場景中時間語義和不同的時間概念吉挣。我們將討論流處理引 擎 如何基于亂序事件產(chǎn)生精確結果,以及如何使用數(shù)據(jù)流進行歷史 事件處 理并實現(xiàn)“時 間旅行” 婉弹。
流處理場景下一分鐘的含義
當處理 個持續(xù)到達且可能無窮的事件流時睬魂,時間便成了應用中最為核心的 要素。假如你想持續(xù)計算結果镀赌,比如每分鐘計算一次氯哮,那么一分鐘在流式應 用環(huán)境中的含義到底是什么?
假設有某個應用程序會分析用戶玩在線手游時產(chǎn)生的事件。該應用將用戶組織成不同團隊商佛,并會收集每個團隊的活動信息喉钢,這樣就能基于團隊成員完成游戲目標的速度,提供諸如額外生命或等級提升的游戲獎勵(例如良姆,如果團 隊所有成員在一分鐘內(nèi)消除了 500 個泡泡肠虽,他們就會提升一級)。愛麗絲是 個鐵桿玩家玛追,每天早晨上班路上都會玩這個游戲税课。但是有個問題:愛麗絲住 在柏林,每天乘地鐵上班痊剖。而眾所周知韩玩,柏林地鐵上手機上網(wǎng)信號很差。因 此考慮如下情況:愛麗絲開始消泡泡的時候手機還能聯(lián)網(wǎng)向分析應用發(fā)送事 件陆馁。突然找颓,地鐵開進隧道,手機斷網(wǎng)了叮贩。愛麗絲繼續(xù)玩她的击狮,此時游戲產(chǎn)生 的事件會緩存在手機里。在地鐵離開隧道妇汗,愛麗絲重新上線后帘不,之前緩存的 事件才會發(fā)送給應用。此時應用該怎么辦?在上述示例中一分鐘的含義又是 什么?需要把愛麗絲離線的時間考慮在內(nèi)嗎?圖 2-11 說明了這個問題杨箭。
在線游戲這個簡單場景展示了算子語義應該依賴事件實際發(fā)生時間而非應用收到 事件的 時間寞焙。在這個手游例子中,后果可能非常糟礁互婿,以至于愛麗絲和 她團隊的其他玩家失 望透 頂捣郊,再也不想碰這個游戲 。但其實還有更多 時間敏 感應用慈参,需要我 們 對其處理語義進行保障呛牲。如果我 們 僅考慮現(xiàn)實時間一分鐘 內(nèi)收到多少數(shù)據(jù),到 rs結果可能會隨網(wǎng)絡連接速度或處理速度而改變驮配。而事實 上每分鐘收到事件數(shù)目的是由數(shù)據(jù)本身的時間來定義的娘扩。
在愛麗絲游戲的例子中着茸,流式應用可以使用兩個不同概念的時間,即處理時 間( processing time)和事件時間( event time) 琐旁。 我們將在接下來的幾節(jié)對 它們進行介紹涮阔。
處理時間
處理時間是當前流處理 算子 所在機器上的本地時鐘 時 間』遗梗基于處理時間的窗 口會包含那些恰好在 一 段時間內(nèi)到達窗口算子的事件敬特,這里的 時間 段是按照 機器時間測量的 。 如圖二 12 所示牺陶,在愛麗 絲 的例子中伟阔,處理時間窗口在她手機離線后會繼續(xù)計時,因此不會把她離線那段時間的活動考慮在內(nèi)掰伸。
事件時間
事件時間是數(shù)據(jù)流中事件實際發(fā)生的時間皱炉,它以附加在數(shù)據(jù)流中事件的時間 戳為依據(jù)。這些時間戳通常在事件數(shù)據(jù)進入流處理管道之前就存在(例如事 件的生成時間)狮鸭。如圖 2-13 所示娃承,目|]便事件有延遲, 事件 時間 窗 口也能準確 地將事件分配到窗口中怕篷,從而反映出真實發(fā)生的情況历筝。
事件時間將處理速度和結果內(nèi)容徹底解禍±任剑基于事件時間 的操作是可預測的梳猪, 其結果具有確定性。無論數(shù)據(jù)流的 處理速度如 何 蒸痹、事件到達算子的 順序怎樣春弥, 基于事件 時間的 窗口都會生成同樣的結果。
使用 事件時間要克服的挑戰(zhàn)之 一是如何處理延遲事件叠荠。普遍存在的無序問題 也可以借此解決匿沛。假設有另一位名叫鮑勃的玩家也在玩那個在線手游,他恰 好和愛麗絲在同 一趟地鐵上榛鼎。雖然玩的游戲相同逃呼,但鮑勃和愛麗絲的移動網(wǎng) 絡供應商不同。當愛麗絲的手機在隧道里沒信號的時候者娱,鮑勃的手機依然能 聯(lián)網(wǎng)向后端游戲應用發(fā)送 事件抡笼。
依靠事件 時間 ,我們可以保證在數(shù)據(jù)亂序的情況下結果依然正確黄鳍, 而 且結合 可重放的數(shù)據(jù)流推姻,時間戳所帶來的確定性允許你對歷史數(shù)據(jù)“快進”。這意 味著你可以通過重放數(shù)據(jù)流來分析歷史數(shù)據(jù)框沟,就如同它們是實時產(chǎn)生的一樣藏古。
此外增炭,你可以把計算“快進”到現(xiàn)在,這樣一旦你的程序趕上了當前事件產(chǎn) 生的進度拧晕,它能夠以完全相同的程序邏輯作為 實 時應用繼續(xù)運行 弟跑。
水位線
在到目前為止有關事件時間窗口的討論中,我們 一直 忽略了 一 個非常重要的 方面:怎樣決定事件時間窗口的觸發(fā)時機?換言之 防症,我們需要等多久才能確 定已經(jīng)收到了所有發(fā)生在某個特定時間點之前的事件?此外,我們?nèi)绾蔚弥?數(shù)據(jù)會產(chǎn)生延遲?鑒于分布式系統(tǒng)現(xiàn)實的不確定性以及外部組件可能引發(fā)任 意延遲哎甲,這兩個問題都沒有完美的答案蔫敲。在本節(jié)中,我們將了解如何利用水 位線來設定 事件時間窗口的行為 炭玫。
水位線是一個全局進度指標 奈嘿,表示我們確信不會再有延遲事件到來的某個時 間點。本質上吞加,水位線提供了 一 個邏輯時·鐘裙犹,用來通知系統(tǒng)當前的事件時間。 當一個算子接收到時間為 T 的水位線衔憨,就可以認為不會再收到任何時間戳小 于或等于 T 的事件了叶圃。水位錢無論對于事件時間窗口還是處理亂序事件的算 子都很關鍵。算子 一 且收到某個水位線践图,就相當于接到信號:某個特定時間 區(qū)間的時間戳已經(jīng)到齊掺冠,可以觸發(fā)窗口計算或對接收的數(shù)據(jù)進行排序了。
水位線允許我們 在結果的準確性和延遲之間做出取舍码党。激進的水位線策略保 證了低延遲德崭,但隨之而來的 是低可信度。 i亥情況下 揖盘,延遲事件可能會在水位 線之后到來眉厨,我們必須額外加一些代碼來處理它們。反之兽狭,如果水位線過于 保守憾股,雖然可信度得以保證,但可能會無謂地增加處理延遲箕慧。
在很多現(xiàn)實應用中荔燎,系統(tǒng)無戰(zhàn)獲取足夠多的信息來完美地確定水位線。以手 游場景為例销钝,現(xiàn)實中根本無法得知用戶會離線多久有咨。他們可能正在過隧道, 可能正在上飛機蒸健,也可能直接退坑不玩了座享。無論水位線是由用戶定義還是自 動生成婉商,只要存在“拖后腿”的任務,追蹤分布式系統(tǒng)中的全局進度就可能 出現(xiàn)問題渣叛,因此簡單地依賴水位 線并不總是可以高枕無 憂 丈秩。 而流處理系統(tǒng)很關鍵的 一 點是能提供某些機制來處理那些可能晚于水位線的遲到事件。根據(jù) 應用需求的不同淳衙,你可能想直接忽略這些事件蘑秽,將它們寫入日志或利用它們 去修正之前的結果。
處理時間與事件時間
此刻你可能心存疑惑:既然事件時間能夠解決所有問題箫攀,為何還要去關心處理時 間?事實上肠牲,處理時間的確有其特定的適用場景。處理時間窗口能夠將延遲降至 最低靴跛。由于無需考慮缀雳、遲到或亂序的事件,窗口只需簡單地緩沖事件梢睛,然后在達到 特定時間后立即觸發(fā)窗口計算即可肥印。因此對于那些更重視處理速度而非準確度的 應用,處理時間就會派上用場绝葡。另一種情況是深碱,你需要周期性地實時報告結果而 無論其準確性如何。 一個常見示例應用是實時監(jiān)控儀表盤藏畅,它會接收井展示事件 聚合結果莹痢。最后,處理時間窗口能夠表示數(shù)據(jù)流自身的真實情況墓赴,這可能會在某 些用例中派上用場竞膳。例如,你可能想觀察數(shù)據(jù)流的接入情況诫硕,通過計算每秒事件 數(shù)來檢測數(shù)據(jù)中斷坦辟。總而言之章办,雖然處理時間提供了很低的延遲锉走,但它的結果 依賴處理速度,具有不確定性藕届。事件時間 則 與之相反挪蹭,能保證結果的準確性,并允許你處理延遲甚至無序的事件休偶。
狀態(tài)和一致性模型
我們現(xiàn)在要轉向流處理中另 一個卡分重要的方面 狀態(tài)梁厉。狀態(tài)在數(shù)據(jù)處理 中無處不在,任何 一 個稍復雜的計算者R要用它。為了生成結果词顾,函數(shù)會在 一 段時間或基于 一 定個數(shù)的事件來累積狀態(tài)(例如計算聚合或檢測某個模式)八秃。 有狀態(tài)算子同時使用傳入的事件和內(nèi)部狀態(tài)來計算輸出。以某個滾動聚合算 子為例肉盹,假設它會輸出至今為止所 見到的 全部事件之和昔驱。該算子以 內(nèi)部狀態(tài) 形式存儲當前的累加值,并會在每次收到新事件時對其進行更新上忍。類似地骤肛, 假設還有一個算子,會在每次檢測到“高溫”事件且在隨后 10分鐘內(nèi)出現(xiàn)“煙 霧”事件時報警窍蓝。這個算子需要將“高溫”事件存為內(nèi)部狀態(tài)腋颠,直到接下來 發(fā)現(xiàn)“煙霧”事件或超過 10分鐘的時間限制。
在使用批處理系統(tǒng)分析無限數(shù)據(jù)集的情況下它抱,狀態(tài)的重要性會越發(fā)凸顯。在 現(xiàn)代流處理引擎興起之前朴艰,處理無限數(shù)據(jù)的通用辦陸是將到來事件分成小批 次观蓄,然后不停地在批處理系統(tǒng)上調(diào)度并運行作業(yè)。每當 一個作業(yè)結束祠墅,其結 果都會寫入持久化存儲中侮穿,同時所有算子的狀態(tài)將不復存在。 一 旦某個作業(yè) 被調(diào)度到下個批次上執(zhí)行毁嗦,它將無怯訪問之前的狀態(tài)亲茅。該問題通常的解決方 案是將狀態(tài)管理交由某個外部系統(tǒng)(如數(shù)據(jù)庫)完成。反之狗准,在持續(xù)運行的 流式作業(yè)中克锣,每次處理事件所用到的狀態(tài)都是持久化的,我們完全可以將其 作為編程模型中的最高級別腔长。按理說袭祟,我們也可以使用外部系統(tǒng)來管理流處 理過程中的狀態(tài),只是這樣可能會引入額外延遲捞附。
由于流式算子處理的都是潛在無窮無盡的數(shù)據(jù)巾乳,所以必須小心避免內(nèi)部狀態(tài) 無限增長。為了限制狀態(tài)大小鸟召,算子通常都會只保留到目前為止所見事件的 摘要或概覽胆绊。這種摘要可能是一個數(shù)量值,一個累加值欧募,一個對至今為止全 部事件的 抽樣压状, 一 個窗口緩沖或是一個保留了應用運行過程中某些有價值信 息的自定義數(shù)據(jù)結構。
不難想象跟继,支持有狀態(tài)算子將面臨很多實現(xiàn)上的挑戰(zhàn):
狀態(tài)管理: 系統(tǒng)需要高效地管理狀態(tài)并保證它 們不 受并發(fā)更新的影響何缓。
狀態(tài)劃分: 由于結果需要同時依賴狀態(tài)和到來的事件肢础,所以狀態(tài)并行化會變得異常復 雜。幸運的是碌廓,在很多情況下可以把狀態(tài)按照鍵值劃分传轰,井獨立管理每 一部分。舉例而言谷婆,如果你要處理從一組傳感器得到的測量值數(shù)據(jù)流慨蛙,則可 以用分區(qū)算子狀態(tài)、(partitioned operator state)來單獨維護每個傳感器的 狀態(tài)纪挎。
狀態(tài)恢復: 最后 一 個也是最大的挑戰(zhàn)在于期贫,有狀態(tài)算子需要保證狀態(tài)、可以恢復异袄,并且 即使出現(xiàn)故障也要確保結果正確通砍。
任務故障
在流式作業(yè)中,算子的狀態(tài)十分重要烤蜕,因此需要在故障時予以保護封孙。如果狀 態(tài)在故障期間丟失,那恢復后的結果就會不正確讽营。流式作業(yè)通常 會運行較長 時間 虎忌,因 此狀態(tài)可能是經(jīng)過數(shù)天甚至數(shù)月才收 集得到。通過重新處理所有輸 入來 重 建故障期間丟失的狀態(tài)橱鹏,不僅代價高膜蠢,而且還很耗時。
在本章開頭莉兰,你學到了如何將流處理程序建模成 Dataflow 圖挑围。在實際執(zhí)行前, 它們需要被翻譯成物理 Dataflow 圖糖荒,其中會包含很多相連的并行任務贪惹。每個 任務都要運行一部分算子邏輯,消費輸入流并為其他任務生成輸出流寂嘉。典型 的現(xiàn)實系統(tǒng)設 置 都可以輕松做到在很多物理機器上 并行運行數(shù) 以百計的任務奏瞬。 對于長期運行的流式作業(yè)而 言 ,每個任務都隨時有可能出現(xiàn)故障泉孩。如何確保 能夠透明地處理這些故障硼端,讓流式作業(yè)得以繼續(xù)運行? 事實上,你不僅需要 流處理引擎在出現(xiàn)任務故障時可以繼續(xù)運行寓搬,還需要它能保證結果和 算子 狀 態(tài)的正確性珍昨。我們將在本節(jié) 一一 討論這些問題 。
什么是任務故障 ?
對于輸入流中的每個事件,任務都需要執(zhí)行以下步驟: 1接收事件井將它們 存在本地緩沖區(qū);2選擇性地更新內(nèi)部狀態(tài)镣典、;1 產(chǎn)生 輸出記錄兔毙。上述任何 一 個步驟都可能發(fā)生故障,而系統(tǒng)必須在故障情況下明確定義其行為 兄春。 如果故 障發(fā)生在第一步澎剥,事件是否會丟失?如果在更新內(nèi)部狀態(tài)后發(fā)生故障,系統(tǒng) 恢復后是否會重復更新?在上述情況下赶舆,結果是否確定?
在批處理場景下哑姚,上面提到的都算不上問題。由于批處理任務可以輕易“從 頭再來’\所以不會有任何事件丟失芜茵,狀態(tài)也可以完全從最初開始構建叙量。然 而在流式場景中,處理故障就沒那么容易了九串。流處理系統(tǒng)通過不同的結果保 障來定義故障時的行為 绞佩。 接下來我們回顧 一 下現(xiàn)代流處理引擎所提供的不同 種類的結果保障以及它們相應的實現(xiàn)機制。
結果保障
在討論不同類型的保障之前猪钮,我們需要澄清 一 些在討論流處理引擎任務故障 時容易導致困惑的點品山。在本章剩余部分,當提到“結果保障”躬贡,我們指的是 流處理引擎內(nèi)部狀態(tài)的 一致性谆奥。也就是說眼坏,我們關注故障恢復后應用代碼能 夠看到的狀態(tài)值拂玻。請注意,保證應用狀態(tài)的 一 致性和保證輸出的一致性并不 是一 回事兒宰译。一旦數(shù)據(jù)從數(shù)據(jù)匯中寫出檐蚜,除非目標系統(tǒng)支持事務,否則結果 的正確性將難以保證沿侈。
至多一次
任務發(fā)生故障時最簡單的措施就是既不恢復丟失的狀態(tài)闯第,也不重放丟失的事 件。至多 一 次是 一 種最簡單的情況缀拭,它保證每個 事件至多被 處理 一次咳短。換句 話說,事件可以隨意丟棄蛛淋,沒有任何機制來保證結果的正確性咙好。這類保障也 被稱作“沒有保障”,因為即便系統(tǒng)丟掉所有事件也能滿足其條件褐荷。無論如何勾效, 沒有保障聽上去都是個不靠譜的主意。但如果你能接受近似結果并且僅關注 怎樣降低延遲,這種保障似乎也可以接受层宫。
至少一次
對大多數(shù)現(xiàn)實應用而言杨伙,用戶期望是不丟事件,這類保障稱為至少一次萌腿。它 意味著所有事件最終都會處理限匣,雖然有些可能會處理多次。如果正確性僅依 賴哮奇、信息的完整度膛腐,那 重復 處理或許可以接受 。 例如鼎俘,確定某個事件是否在輸 入流中出現(xiàn)過哲身,就可以利用至少一次保障正確地實現(xiàn)。它最壞的情況也無非 就是多幾次定位到目標事件贸伐。但如果要計算某個事件在輸入流中出現(xiàn)的次數(shù)勘天, 至少一次保障可能就會返回錯誤的結果。
為了確保至少 一 次結果語義的正確性捉邢,需要想辦怯從驚頭或緩沖區(qū)中重 放事件脯丝。持久化事件日志會將所有事件寫入永久存 ft者,這樣在任務故障 時就可以重放它們 伏伐。實現(xiàn)該功 能的另一個方告是采用記 錄確認( record acknowledgments )宠进。該方邑會將所有事件存在緩沖區(qū)中,直到處理管道中所 有任務都確認某個事件已經(jīng)處理完畢才會將事件丟棄藐翎。
精確一次
精確一次是最嚴格材蹬, 也是最難實現(xiàn)的一類保障,它表示不但沒有事件丟失吝镣, 而且每個事件對于內(nèi)部狀態(tài)的更新都只有 一次堤器。本質上,精確一次保障意味 著應用總會提供正確的結果末贾,就如同故障從未發(fā)生過一般闸溃。
提供精確 一 次保障是以至少 一 次保障為前提,因此同樣需要數(shù)據(jù)重放機制拱撵。 此外辉川,流處理引 擎需要確保內(nèi)部狀態(tài) 的一致性,即在故障恢復后拴测,引擎需要 知道某個 事件對應的更新是否已經(jīng)反映 到狀態(tài)上 乓旗。事務性更新是實現(xiàn)該目標 的一個方蓓,但它可能會帶來極大 的性能開銷昼扛。 Flink 采用了 輕量級檢查點機 制來實現(xiàn)精確一次結果保障寸齐。
端到端的精確一次
至今為止你看到的保障類型都僅限于流處理引擎自身的應用狀態(tài)欲诺。在實際流 處理應用中,除了流處理引擎也至少還要有一個數(shù)據(jù)來源組件和 一個數(shù)據(jù)終 點組件渺鹦。端到端的保障指的是在整個數(shù)據(jù)處理管道上結果都是正確的扰法。在每 個組件都提供自身的保障情況下,整個處理管道上端到端的保障會受制于保 障最弱的那個組件毅厚。注意塞颁,有時候你可以通過弱保障來實現(xiàn)強語義。 一個常 見情況就是某個任務執(zhí)行 一 些諸如求最大值或最小值的幕等操作吸耿。該情況下祠锣, 你可以用至少 一 次保障來實現(xiàn)精確 一 次的語義。
小結
本章主要教給你數(shù)據(jù)流處理相關的基礎知識咽安。我們介紹了 Dataflow 編程模型 以及如何將一個流式應用表示為分布式 Dataflow 圖 伴网。接下來 ,你學習了并行 處理無限流的需求妆棒,了解了延遲和吞吐對于流式應用的重要性澡腾。本章還涵蓋 了基本的流式操作以及如何利用窗口在無限輸入上計算出有意義的結果。你 學習了流式應用中時間的含義糕珊,并比較了事件時間和處理時間的概念动分。最后 我們介紹了狀態(tài)對流式應用的重要性,以及如何應對故|璋并確保結果正確红选。
到目前為止澜公,我們考慮的流處理相關概念都還是獨立于 Apache Flink 的。在后續(xù)喇肋,我們會介紹 Flink是如何實現(xiàn)這些概念的坟乾,以及怎樣利用它 的 DataStrearnAPI來編寫一些涵蓋了目前為止所講特性應用。