1. Dataflow Programming
在討論流處理的基本概念之前,我們首先介紹一下數據流編程(dataflow programming)的基本概念與術語。
數據流圖
數據流程序一般在由數據流圖表示,數據流圖描述了數據如何在操作之間流動。在數據流圖中,節點被稱為operator,代表計算;邊代表數據依賴。
Operator是dataflow 應用中的基本單元,它們從輸入消費數據,在之上執行計算,並生產數據提供給下一步處理。
沒有輸入的operators 稱為數據源(data sources),沒有輸出的operator稱為數據接收器(data sink)。一個dataflow graph 必須有至少一個data source以及一個data sink。例如:
類似上圖的dataflow graph 稱為邏輯的(logical)數據流圖,因為它們從高層的視角展示了計算邏輯。在執行時,邏輯圖會被轉換為物理圖(physical dataflow graph),具體的執行邏輯會在物理數據流圖中給出,如下圖:
例如,如果我們使用分布式處理引擎,每個operator可能有多個並行的任務跑在不同的物理機器上。邏輯圖表示了執行的邏輯,而物理圖表示了具體的任務。
數據並行與任務並行
數據並行是指:將輸入數據做partition,然后使用多個同樣的task並行處理數據的子集。數據並行的意義在於將數據分散到多個計算節點上。
任務並行是指:有多個不同的task任務並行處理相同的或不同的數據。任務並行的意義在於更好的使用集群中的計算資源。
數據交換策略
數據交換策略定義了:在physical dataflow graph中,數據條目如何分發到task 中。下面是幾種常見的數據交換策略:
- 前向(forward)策略:從一個task發送數據到另一個接受task。如果兩個task均在一個機器上,則可以避免網絡傳輸
- 廣播(broadcast)策略:數據發送到所有並行task中。此策略涉及到數據復制及網絡傳輸,所以較為消耗資源
- key-based 策略:根據key做partition,使具有相同key 的條目可以被同一個task處理
- 隨機(random)策略:隨機均勻分布數據到task中,均衡集群計算負載
2. 並行流處理
在了解以上概念后,我們接下來討論並行流處理。首先,我們定義數據流(data stream):數據流是一個(可能)無限的事件序列。
延遲與吞吐
對於批處理應用,我們一般關注的是一個job的整個執行時間,或是處理引擎需要多長時間讀數據、計算、以及寫入結果。而流處理應用是持續運行的,並且輸入數據可能是無限的,所以對於整個應用的執行時間其實並沒有太多關注。但是,流處理程序在處理高頻率的事件輸入的同時,還必須要在輸入數據后盡可能快的提供結果。我們使用延遲(latency)與吞吐(throughput)來衡量這個需求。
延遲
延遲表示的是處理一個event所需要的時間。本質上,它是從:接受到event -> 到處理完此event -> 並在結果中有體現,這段時間。舉個例子,假設你去咖啡店買咖啡,前面有人排隊,在到你點完單后,店里會做咖啡,做好后叫號,然后你來取,取完后開始喝。這里的latency指的就是從你進咖啡店開始,一直到你喝到第一口咖啡的間隔時間。
在data streaming 中,latency由時間衡量,例如毫秒。根據application的不同,你可能會關注平均延遲、最高延遲、或是百分位數延遲(percentile latency)。例如:平均延遲為10ms,表示events平均在10ms內被處理。而百分位 95 的延遲為10ms表示的是有95% 的events在10ms內被處理。平均延遲值隱藏了處理延遲的分布,可能會難以定位問題。例如:如果咖啡師在為你准備咖啡時用光了牛奶,則你不得不去等待咖啡師去拿牛奶,這里你的咖啡會有更大的延遲,但是其他大部分用戶並不會受到影響。
對於大部分流應用來說(例如系統告警、欺詐檢測、網絡監控等),保證低延遲至關重要。低延遲在流處理中是一個重要的特性,它是實現“實時”應用的基礎。當前主流的流處理器(如Flink),可以提供低至幾毫秒的延遲。相對而言,傳統的批處理系統的延遲可一般會達到幾分鍾到幾小時不等。在批處理中,首先需要的是將events收集為batch,然后再處理它。所以它的延遲取決於batch中最后一個event到達的時間,以及batch 的大小。真正的流處理並不引入這種延遲,所以可以實現真正的低延遲。在真正的流模型中,events在到達流系統后可以被立即處理,此時的延遲反應的是:在此event上執行的操作時間。
吞吐
吞吐用於衡量系統的處理能力:處理率。也就是說,它可以告訴我們,系統在每個時間片內可以處理多少個events。以咖啡店為例,如果咖啡店從早上7點開到晚上7點,每天服務600個客戶,則它的平均吞吐為 50個顧客/每小時。在流系統中,我們需要延遲盡可能的低,而吞吐盡可能的高。
吞吐由每個時間單位內處理的evnets衡量。這里需要注意的是:處理速率取決於events的到達速率。低吞吐並不能完全說明系統性能低。在流系統中,一般希望確保系統最高能處理events的速率。也就是說,我們主要關心的是確定吞吐的峰值(peak throughput):在系統處於最高負載時的性能極限。為了更好地理解頂峰吞吐(peak throughput),我們考慮一個流處理應用,它一開始並不接收任何輸入,所以此時並不小號任何系統資源。當第一個event到來時,它會立即(盡量)以最小的latency 處理。例如你是咖啡館開門的第一個顧客,店員會立即為你去做咖啡。在理想情況下,你會希望隨着更多events的進入,latency 可以保持較小值不發生太大的變動。然而,一旦輸入的events到達某個速率,使得系統資源被完全使用時,就不得不開始緩存(buffering)events。拿咖啡店舉例,在中午的時候,人流量會特別大,達到了咖啡店的頂峰,則這時候就需要開始排隊了。這時候系統即達到了它的peak throughput,而更大的event rate只會使得latency變得更糟。如果系統繼續以更高的速率接收輸入(超過了它可以處理的速率),緩沖區可能會爆掉,並導致數據丟失。常規的解決方案是背壓(backpressure),並有不同的策略去處理。
延遲 vs 吞吐
在這里需要明確的是,延遲與吞吐並不是兩個互相獨立的指標。如果事件到達數據處理管道的事件較長,便無法保證高吞吐。類似的,如果系統的性能較低,則events 會被緩存並等待,直到系統有能力處理。
再次以咖啡店為例,首先比較好理解的是,在負載低的時候,可以達到很好的一個latency。例如咖啡店里你是第一個也是唯一的一個顧客。但是在咖啡店較忙的時候,顧客就需要排隊等待,此時的latency即會增加。另外一個影響延遲的因素(並繼而影響到吞吐)是處理一個事件的時間。例如咖啡店為每個顧客做咖啡所消耗的時間。假設在一個聖誕節,咖啡師需要在每杯咖啡上畫一個聖誕老人。也就是說,每杯咖啡制作的時間會增加,導致每個顧客在咖啡店消耗更多的時間,最終使得整體吞吐下降。
那是否可以同時達到低延遲與高吞吐?在咖啡店的例子中,你可以招聘更有經驗的咖啡師,讓做咖啡的效率更高。這里主要考量的地方是:減少延遲以增加吞吐。如果一個系統執行的操作更快,則它就可以在同一時間內處理更多的event。另外的方法是招聘更多的咖啡師,讓同一時間有更多的客戶被服務到。在流處理管道中,通過使用多個stream並行處理events,在獲取更低的延時的同時,也可以在同一時間內處理更多的events。
References:
Vasiliki Kalavri, Fabian Hueske. Stream Processing With Apache Flink. 2019