Flink Streaming专题 -1 FlinkStreaming 概述和事件时间EventTime解读

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 the TextInputFormat 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:

    1. 如果watchType设置为FileProcessingMode.PROCESS_CONTINUOUSLY,则在修改文件时,将完全重新处理其内容。这可以打破“完全一次”的语义,因为在文件末尾附加数据将导致其所有内容被重新处理。
    2. 如果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 use addSource(new FlinkKafkaConsumer08<>(...)). See connectors for more details.

(2)Data Sinks

  • writeAsText() / TextOutputFormat
  • writeAsCsv(...) / CsvOutputFormat
  • print() / printToErr() - 可以输出文件内容,如果是并行输出,将输出文件为task标识符对应的id
  • writeUsingOutputFormat() / FileOutputFormat - Method and base class for custom file outputs. Supports custom object-to-bytes conversion.
  • writeToSocket - Writes elements to a socket according to a SerializationSchema
  • 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 ,只需要实现AssignerWithPeriodicWatermarksand 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();
        }
});

Flink Streaming专题 -1 FlinkStreaming 概述和事件时间EventTime解读_第1张图片

你可能感兴趣的:(Hadoop生态,Flink)