Streaming
1.1 Overviewer
(1)Data Sources
DataSources 操作 可以通過StreamExecutionEnvironment.addSource(sourceFunction) 方式將source加入到集羣內部中,Flink預先提供了很多Source方法來幫助你來實現數據操作。當然也可以通過實現SourceFunction 的方式來實現非並行數據,或者通過繼承RichSourceFunction類或實現ParallelSourceFunction接口來實現並行數據源的操作 。
pre-implemented source functions
readTextFile(path)
- Reads text files, i.e. files that respect theTextInputFormat
specification, line-by-line and returns them as Strings.
readFile(fileInputFormat, path)
- Reads (once) files as dictated by the specified file input format.
readFile(fileInputFormat, path, watchType, interval, pathFilter, typeInfo)
- 這是前兩個內部調用的方法。它根據給定的fileInputFormat讀取路徑中的文件。根據提供的watchType,此源可以定期監視(每隔ms)新數據的路徑(FileProcessingMode.PROCESS_CONTINUOUSLY),或者處理當前在路徑中的數據並退出(FileProcessingMode.PROCESS_ONCE)。使用pathFilter,用戶可以進一步排除處理文件。IMPLEMENTATION:
在引擎下,Flink將文件讀取過程分爲兩個子任務,即目錄監控和數據讀取。這些子任務中的每一個都由單獨的實體實現。監視由單線程任務實現,而讀取由並行運行的多個任務執行。後者的並行性等於工作並行性。單個監視任務的作用是掃描目錄(定期或僅一次,具體取決於watchType),找到要處理的文件,將它們分成分割,並將這些分割分配給下游讀取器。讀者是那些將閱讀實際數據的人。每個分割僅由一個讀取器讀取,而讀取器可以逐個讀取多個分割
IMPORTANT NOTES:
- 如果watchType設置爲FileProcessingMode.PROCESS_CONTINUOUSLY,則在修改文件時,將完全重新處理其內容。這可以打破“完全一次”的語義,因爲在文件末尾附加數據將導致其所有內容被重新處理。
- 如果watchType設置爲FileProcessingMode.PROCESS_ONCE,則源數據只會對數據掃描一次即退出,而不會等待讀取完纔會退出。當然會當所有的數據均被讀完纔會結束該讀取操作。關閉該文件會導致該數據讀取將不會再有checkpoint,這也會導致如果出現問題recovery時,恢復速度更慢。
Socket-based:
socketTextStream
- Reads from a socket. Elements can be separated by a delimiter.Collection-based:
fromCollection(Collection)
- Creates a data stream from the Java Java.util.Collection. All elements in the collection must be of the same type.fromCollection(Iterator, Class)
- Creates a data stream from an iterator. The class specifies the data type of the elements returned by the iterator.fromElements(T ...)
- Creates a data stream from the given sequence of objects. All objects must be of the same type.fromParallelCollection(SplittableIterator, Class)
- Creates a data stream from an iterator, in parallel. The class specifies the data type of the elements returned by the iterator.generateSequence(from, to)
- Generates the sequence of numbers in the given interval, in parallel.Custom:
addSource
- Attach a new source function. For example, to read from Apache Kafka you can useaddSource(new FlinkKafkaConsumer08<>(...))
. See connectors for more details.
(2)Data Sinks
writeAsText()
/TextOutputFormat
writeAsCsv(...)
/CsvOutputFormat
print()
/printToErr()
- 可以輸出文件內容,如果是並行輸出,將輸出文件爲task標識符對應的idwriteUsingOutputFormat()
/FileOutputFormat
- Method and base class for custom file outputs. Supports custom object-to-bytes conversion.writeToSocket
- Writes elements to a socket according to aSerializationSchema
addSink
- Invokes a custom sink function. Flink comes bundled with connectors to other systems (such as Apache Kafka) that are implemented as sink functions.
(3) Iterations
迭代器流程序實現步進功能並將其嵌入到IterativeStream中。由於DataStream程序可能永遠不會完成,因此沒有最大迭代次數。相反,您需要指定流的哪個部分反饋到迭代,哪個部分使用拆分轉換或者filter操作。在這裏,我們展示了使用Filter的示例。首先,我們定義一個IterativeStream
IterativeStream<Integer> iteration = input.iterate();
DataStream<Integer> iterationBody = iteration.map(/* this is executed many times */);
要關閉迭代並定義迭代尾部,請調用IterativeStream的closeWith(feedbackStream)方法。給closeWith函數的DataStream將反饋給迭代器頭位置。常見的模式是使用filter來過濾流反饋的數據和向前傳播的流的一部分。這些濾波器可以例如定義“終止”邏輯,其中允許元件向下遊傳播而不是反饋。
(4)延時控制Controlling Latency
在網絡傳輸數據的環境中不會對所有的record數據進行一對一的數據傳輸,所以可以通過緩存的方式將數據形成批量數據在批量發送數據。 但是當部分網絡問題,會導致該緩存數據越來越多,而導致數據延遲會越來越嚴重。要控制吞吐量和延遲,可以在執行環境(或單個運算符)上使用env.setBufferTimeout(timeoutMillis)來設置緩衝區填充的最長等待時間。 在此之後,即使緩衝區未滿,也會自動發送緩衝區。超時默認值爲100毫秒。
LocalStreamEnvironment env = StreamExecutionEnvironment.createLocalEnvironment();
env.setBufferTimeout(timeoutMillis);
env.generateSequence(1,10).map(new MyMapper()).setBufferTimeout(timeoutMillis);
要最大化吞吐量,請設置setBufferTimeout(-1),這將刪除超時,緩衝區只有在滿時纔會刷新。要最小化延遲,請將超時設置爲接近0的值(例如5或10 ms)。應避免緩衝區超時爲0,因爲它可能導致嚴重的性能下降。
(5)debugging
在分佈式集羣中運行流式程序之前,最好確保實現的算法按預期工作。因此,實施數據分析程序通常是檢查結果,調試和改進的增量過程。 Flink通過支持IDE內的本地調試,測試數據的注入和結果數據的收集,提供了顯着簡化數據分析程序開發過程的功能。本節提供了一些如何簡化Flink程序開發的提示。
A.創建本地環境操作
LocalStreamEnvironment在其創建的同一JVM進程中啓動Flink系統。如果從IDE啓動LocalEnvironment,則可以在代碼中設置斷點並輕鬆調試程序。
final StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment();
DataStream<String> lines = env.addSource(/* some source */);
// build your program
env.execute();
B.加入DataSource
Flink提供了特殊的數據源,這些數據源由Java集合支持,以方便測試。一旦程序經過測試,源和接收器可以很容易地被讀取/寫入外部系統的源和接收器替換。
final StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironment();
// Create a DataStream from a list of elements
DataStream<Integer> myInts = env.fromElements(1, 2, 3, 4, 5);
// Create a DataStream from any Java collection
List<Tuple2<String, Integer>> data = ...
DataStream<Tuple2<String, Integer>> myTuples = env.fromCollection(data);
// Create a DataStream from an Iterator
Iterator<Long> longIt = ...
DataStream<Long> myLongs = env.fromCollection(longIt, Long.class);
具體的source方法可以查看DataSource 具體的預實現類
1.2事件時間 EventTime
(1)overviewer
A.ProcessingTime 處理時間
處理時間是指執行相應操作的機器的系統時間。
當流程序在處理時間運行時,所有基於時間的操作(如時間窗口)將使用運行相應執行節點的機器的系統時鐘。每小時處理時間窗口將包括在系統時鐘指示整個小時之間到達特定執行節點的所有記錄。例如,如果應用程序在上午9:15開始運行,則第一個每小時處理時間窗口將包括在上午9:15到10:00之間處理的事件,下一個窗口將包括在上午10:00到11:00之間處理的事件,等等。
處理時間是最簡單的時間概念,不需要流和s之間的協調。它提供最佳性能和最低延遲。但是,在分佈式和異步環境中,處理時間不提供確定性,因爲它容易受到記錄到達系統的速度(例如從消息隊列)到記錄在系統內的節點之間流動的速度的影響。
B. EventTime 事件時間
事件時間是每個單獨事件在其生產設備上發生的時間。此時間通常在進入Flink之前嵌入記錄中,並且可以從每個記錄中提取該事件時間戳。在事件時間,時間的進展取決於數據,而不是任何其他時間。事件時間程序必須指定如何生成事件時間的waterMarks,這是指示事件時間進度的機制。該WaterMarks機制在下面的後面部分中描述。
在一個完整的流事件中,事件時間處理將產生完全一致和確定的結果,無論事件何時到達或其排序。但是,除非事件已知按順序到達(按時間戳),否則事件時間處理會在等待無序事件時產生一些延遲。由於只能等待一段有限的時間,因此限制了確定性事件時間應用程序的運行方式。 假設所有數據都已到達,事件時間操作將按預期運行,即使在處理無序或延遲事件或重新處理歷史數據時也會產生正確且一致的結果。
例如,每小時事件時間窗口將包含帶有落入該小時的事件時間戳的所有記錄,無論它們到達的順序如何,或者何時處理它們。 請注意,有時當事件時間程序實時處理實時數據時,它們將使用一些處理時間操作,以保證它們及時進行。
C.攝取時間 Ingestion time
攝取時間是事件進入Flink的時間。在Source操作階段,每個記錄將源的當前時間作爲時間戳,並且基於時間的操作(如時間窗口)引用該時間戳。 攝取時間在概念上位於事件時間和處理時間之間。
與處理時間相比,它消耗更高一些,但可以提供更可預測的結果。因爲攝取時間使用穩定的時間戳(在Source階段分配一次),所以對記錄的不同窗口操作將引用相同的時間戳,而在處理時間中,每個窗口操作可以將記錄分配給不同的窗口(基於本地系統時鐘和任何運輸延誤)。
與事件時間相比,攝取時間程序無法處理任何無序事件或後期數據,但程序不必指定如何生成水印。 在內部,攝取時間與事件時間非常相似,但具有自動時間戳分配和自動水印生成功能。
D. 設置時間類型
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
- TimeCharacteristic.ProcessingTime
- TimeCharacteristic.IngestionTime
- TimeCharacteristic.EventTime
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime);
// alternatively:
// env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime);
// env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
DataStream<MyEvent> stream = env.addSource(new FlinkKafkaConsumer09<MyEvent>(topic, schema, props));
stream
.keyBy( (event) -> event.getUser() )
.timeWindow(Time.hours(1))
.reduce( (a, b) -> a.add(b) )
.addSink(...);
E. 事件時間和WaterMarks機制
Flink實現了很多數據流的模型,下面對此有兩個比較好的解釋:
- Streaming 101 by Tyler Akidau
- The Dataflow Model paper
支持事件時間的流處理器需要一種方法來衡量事件時間的進度。例如,當事件時間超過一小時結束時,需要通知構建每小時窗口的窗口,以便操作可以關閉正在進行的窗口。
事件時間可以獨立於處理時間(由掛鐘測量)進行。例如,在一個程序中,當前事件時間的操作可能略微落後於處理時間(考慮到接收事件的延遲),而兩者都以相同的速度進行。另一方面,通過快速轉發已經在Kafka主題(或另一個消息隊列)中緩衝的一些歷史數據,另一個流程序可以通過幾周的事件時間進行,只需幾秒鐘的處理。
Flink中用於衡量事件時間進度的機制是waterMarks。waterMarks作爲數據流的一部分流動並帶有時間戳t。waterMarks(t)聲明事件時間已經達到該流中的時間t,這意味着不應該有來自流的具有時間戳t’<= t的元素(即具有更長或等於水印的時間戳的事件)。
waterMarks對於無序流是至關重要的,如下所示,其中事件不按時間戳排序。通常,waterMarks只是一種時間點界定,到流中的那一點,到達某個時間戳的所有事件都應該到達。一旦waterMarks到達界點操作,那麼就可以將其內部事件時鐘提前到waterMarks的值。
請注意,事件時間由新生成的流元素(或元素)繼承,這些元素來自生成它們的事件或觸發創建這些元素的waterMarks。
F.並行流中的WaterMarks
在Source函數處或之後生成WaterMarks。Source函數的每個並行子任務通常獨立地生成其WaterMarks。這些WaterMarks定義了該特定並行源的事件時間。
當WaterMarks流過流項目時,他們會在操作達到之時推進事件時間。每當操作提前到達之時,這會爲後續的時間生成新的WaterMarks 。
一些數據源消耗多個輸入流;例如,一個union,或者跟隨keyBy(…)或partition(…)函數的運算符。這樣操作當前事件時間是其輸入流的事件時間的最小值。由於其輸入流更新其事件時間,所以其他操作方式也是如此 。 下圖顯示了流經並行流的事件和WaterMarks的示例,以及跟蹤事件時間的運算符。
G. 延遲數據處理
某些元素可能違反watermark條件,這意味着即使在水印(t)發生之後,也會出現更多具有時間戳t’<= t的元素。實際上,在大多數數據設置中,某些元素可以被任意延遲,從而無法指定某個事件時間戳的所有元素將發生的時間。此外,即使delay可以被限制,通常也不希望延遲太多watermark,因爲它在事件時間窗的評估中引起太多延遲。
出於這個原因,流程序可能明確地期望一些延遲數據是在系統的事件時鐘之後到達的數據(由watermark標記時間)已經超過了後期元素的時間戳的時間。有關如何在事件時間窗口中使用延遲元素的更多信息,請參閱 Allowed Lateness。
H.source空閒
目前,對於純事件時間watermarks生成器,如果沒有要處理的元素,則watermarks不能進展。這意味着在輸入數據存在間隙的情況下,事件時間將不會進展,例如窗口操作符將不會被觸發,因此現有窗口將不能產生任何輸出數據。
爲了避免這種情況,可以使用定期watermarks分配器,它們不僅基於元素時間戳進行分配。示例解決方案可以是在不觀察新事件一段時間之後切換到使用當前處理時間作爲時間基礎的分配器。 可以使用SourceFunction.SourceContext #markAsTemporarilyIdle將源標記爲空閒。有關詳細信息,請參閱此方法的Javadoc以及StreamStatus。
(2)時間戳生成和watermarks生成
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
A.分配時間戳
爲了讓流程序知道事件時間,那麼必須需要將數據的事件時間進行賦值,來對每個數據進行時間戳賦值 。
B.在Source操作中添加時間戳和WaterMarks
如何源數據中就存在時間戳和waterMarks
@Override
public void run(SourceContext<MyType> ctx) throws Exception {
while (/* condition */) {
MyType next = getNext();
ctx.collectWithTimestamp(next, next.getEventTimestamp());
if (next.hasWatermarkTime()) {
ctx.emitWatermark(new Watermark(next.getWatermarkTime()));
}
}
}
C.Timestamp Assigners / Watermark Generators 時間戳分配器和watermarks生成器
final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
DataStream<MyEvent> stream = env.readFile(
myFormat, myFilePath, FileProcessingMode.PROCESS_CONTINUOUSLY, 100,
FilePathFilter.createDefaultFilter(), typeInfo);
DataStream<MyEvent> withTimestampsAndWatermarks = stream
.filter( event -> event.severity() == WARNING )
.assignTimestampsAndWatermarks(new MyTimestampsAndWatermarks());
withTimestampsAndWatermarks
.keyBy( (event) -> event.getGroup() )
.timeWindow(Time.seconds(10))
.reduce( (a, b) -> a.add(b) )
.addSink(...);
D.使用週期性的水印
AssignerWithPeriodicWatermarks定期分配時間戳並生成watermarks(可能取決於流元素,或純粹基於處理時間)。
生成watermarks的間隔(每n毫秒)由ExecutionConfig.setAutoWatermarkInterval(…)定義。每次調用分配器的getCurrentWatermark()方法,如果返回的watermarks非空且大於前一個水印,則會發出新的水印。 這裏我們展示了兩個使用週期性水印生成的時間戳分配器的簡單示例。請注意,Flink附帶了一個BoundedOutOfOrdernessTimestampExtractor,類似於下面顯示的BoundedOutOfOrdernessGenerator,here.
/**
* This generator generates watermarks assuming that elements arrive out of order,
* but only to a certain degree. The latest elements for a certain timestamp t will arrive
* at most n milliseconds after the earliest elements for timestamp t.
*/
public class BoundedOutOfOrdernessGenerator implements AssignerWithPeriodicWatermarks<MyEvent> {
private final long maxOutOfOrderness = 3500; // 3.5 seconds
private long currentMaxTimestamp;
@Override
public long extractTimestamp(MyEvent element, long previousElementTimestamp) {
long timestamp = element.getCreationTime();
currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp);
return timestamp;
}
@Override
public Watermark getCurrentWatermark() {
// return the watermark as current highest timestamp minus the out-of-orderness bound
return new Watermark(currentMaxTimestamp - maxOutOfOrderness);
}
}
/**
* This generator generates watermarks that are lagging behind processing time by a fixed amount.
* It assumes that elements arrive in Flink after a bounded delay.
*/
public class TimeLagWatermarkGenerator implements AssignerWithPeriodicWatermarks<MyEvent> {
private final long maxTimeLag = 5000; // 5 seconds
@Override
public long extractTimestamp(MyEvent element, long previousElementTimestamp) {
return element.getCreationTime();
}
@Override
public Watermark getCurrentWatermark() {
// return the watermark as current time minus the maximum time lag
return new Watermark(System.currentTimeMillis() - maxTimeLag);
}
}
E.kafka 分片時間戳 Timestamps per Kafka Partition
在這種情況下,您可以使用Flink的Kafka分區感知watermarks生成。使用該功能,根據Kafka分區在Kafka使用者內部生成watermarks,並且每個分區watermarks的合併方式與在流shuffle上合併水印的方式相同。
例如,如果事件時間戳嚴格按每個Kafka分區升序,則使用升序時間戳watermarks 生成器生成每分區水印將產生完美的整體watermarks。
下圖顯示瞭如何使用per-Kafka分區watermarks生成,以及在這種情況下水印如何通過流數據流傳播。
FlinkKafkaConsumer09<MyType> kafkaSource = new FlinkKafkaConsumer09<>("myTopic", schema, props);
kafkaSource.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<MyType>() {
@Override
public long extractAscendingTimestamp(MyType element) {
return element.eventTimestamp();
}
});
DataStream<MyType> stream = env.addSource(kafkaSource);
(3)預定義的timestamp提取器/watermarks提交器
四個類:
org.apache.flink.streaming.api.functions.timestamps.AscendingTimestampExtractor
org.apache.flink.streaming.api.functions.timestamps.BoundedOutOfOrdernessTimestampExtractor
org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
org.apache.flink.streaming.api.functions.AssignerWithPunctuatedWatermarks
Flink支持自定義實現方式來預定義該watermarks ,只需要實現AssignerWithPeriodicWatermarks
and AssignerWithPunctuatedWatermarks
接口即可
A. 遞增時間戳生成器Assigners with ascending timestamps
DataStream<MyEvent> stream = ...
DataStream<MyEvent> withTimestampsAndWatermarks =
stream.assignTimestampsAndWatermarks(new AscendingTimestampExtractor<MyEvent>() {
@Override
public long extractAscendingTimestamp(MyEvent element) {
return element.getCreationTime();
}
});
B.允許固定延遲數量的時間戳生成器 Assigners allowing a fixed amount of lateness
定期水印生成的另一個例子是當水印滯後於在流中看到的最大(事件 - 時間)時間戳一段固定的時間。這種情況包括預先知道流中可能遇到的最大延遲的情況,例如,在創建包含時間戳的元素的自定義源時,這些元素在固定的時間段內傳播以進行測試。對於這些情況,Flink提供了BoundedOutOfOrdernessTimestampExtractor,它將maxOutOfOrderness作爲參數,即在計算給定窗口的最終結果時,在被忽略之前允許元素遲到的最長時間。延遲對應於t-t_w的結果,其中t是元素的(事件 - 時間)時間戳,t_w是前一個水印的時間戳。如果lateness> 0,則該元素被認爲是遲的,並且在計算其對應窗口的作業結果時默認被忽略。有關使用延遲元素的更多信息,請參閱有關允許延遲 的文檔。
DataStream<MyEvent> stream = ...
DataStream<MyEvent> withTimestampsAndWatermarks =
stream.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<MyEvent>(Time.seconds(10)) {
@Override
public long extractTimestamp(MyEvent element) {
return element.getCreationTime();
}
});