flink应用程序
flink是一个框架,用于在无界和有界数据流上进行有状态计算。Flink在不同的抽象级别上提供了多个api,并为常见用例提供了专用的库。
流应用程序的构建块
流处理框架可以构建和执行的应用程序类型由该框架控制流、状态和时间的来定义。下面,我们将描述这些流处理应用程序的构建块,并解释 Flink 处理它们的方法
Streams
很显然,流是流处理的基础。但是流可以具有不同的特性,这些特性会影响流处理的处理方式。flink是一个通用的处理框架,可以处理任何类型的数据流。
- 有界流和无界流:流可以是无界流的或者是一个固定大小的有界流。flink具有处理无界流的复杂功能,也有专门的运算符高效的处理有界流。
- 实时流和记录流:所有的数据都是以流的方式生成的。有两种方式处理数据。在生成流时实时处理它,或将流持久化到存储系统(例如,文件系统或对象存储),然后再进行处理。Flink应用程序可以处理记录或实时流。
State
每一个重要的流处理应用程序都是有状态的,只有对单个事件应用转换的应用程序才不需要状态。每个非平凡的流应用程序都是有状态的,也就是说,只有在单个事件上应用转换的应用程序才不需要状态。任何运行基本业务逻辑的应用程序都需要记住事件或中间结果,以便在稍后的时间点(例如,当接收到下一个事件时或在特定的持续时间之后)访问它们。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o0y1xFtR-1629187481839)(https://flink.apache.org/img/function-state.png)]
应用状态是Flink中的一等公民。您可以通过查看Flink在状态处理上下文中提供的所有特性来了解这一点。
- Multiple State Primitives(多个状态原语):Flink 为不同的数据结构提供了状态原语,例如
atomic values, lists,maps
。开发人员可以根据函数的访问模式选择最有效的状态原语。 - Pluggable State Backends(可插拔的状态后端):应用的状态和检查点通过可插拔的状态后端来管理。Flink 具有不同的状态后端,可将状态存储在内存或 RocksDB(一种高效的嵌入式磁盘数据存储)中。自定义状态后端也可以很好的被集成。
- Exactly-once state consistency(恰好一次状态一致性):Flink 的检查点和恢复算法保证了应用程序出现故障时状态的一致性。因此,故障的处理是透明的,不会影响应用程序的正确性。
- Very Large State(非常大的状态):由于其异步和增量检查点算法,Flink 能够维持数 TB 大小的应用程序状态。
- Scalable Applications(可扩展的应用程序):Flink通过将状态重新分配给更多或更少的worker来支持有状态应用程序的扩展。
time
time是流式应用中另一个比较重要的组成部分。大多数事件流都是具有固定的时间语义的,因为每个事件都是在特定的时间点生成的。此外,许多常见的流计算都是基于时间的,例如窗口聚合、会话化、模式检测和基于时间的连接。流处理的一个重要方面是应用程序如何测量时间,即事件时间和处理时间的差异。
flink提供了丰富的时间特性
- Event-time Mode(事件事件模型):处理具有事件时间语义的流式应用程序基于事件的时间戳计算结果。因此,无论处理记录事件还是实时事件,事件时间处理都能得到准确和一致的结果。
- Watermark Support(水印支持):Flink在事件时间应用中使用水印来推断时间。水印也是一种灵活的机制来权衡结果的延迟和完整性。
- Late Data Handling(延迟数据处理):当以事件时间模式处理带有水印的流时,可能会发生在所有相关事件到达之前完成计算的情况。这样的事件被称为延迟事件。flink提供了多种机制来处理这种延迟事件,例如,通过侧输出重新路由它们,并更新以前计算的结果。
- Processing-time Mode(处理时间):除了事件时间模式外,Flink还支持处理时间语义,该语义执行由处理器的时间触发的计算。处理时间模式适合某些严格要求低延迟、能够容忍近似结果的应用程序。
分层API
Flink提供了三层API。每个API在简洁性和表达性之间提供了不同的权衡,并针对不同的用例。
ProcessFunctions(过程函数)
过程函数是最具有灵活性和表现力的函数编程接口。Flink提供ProcessFunctions
来处理来自一个或两个输入流或在窗口中分组的事件的单个事件。ProcessFunctions
提供对时间和状态的细粒度控制。ProcessFunction
可以任意修改其状态,并注册将来触发回调函数的计时器。因此,ProcessFunctions
可以实现许多有状态事件驱动的应用程序所需的复杂事件业务逻辑。
下面的示例显示了一个KeyedProcessFunction
,它操作KeyedStream
并匹配START
和END
事件。当接收到START
事件时,该函数会记住其状态时间戳并在四小时内注册一个计时器。如果在定时器触发之前收到一个END
事件,函数计算END
和START
事件之间的持续时间,清除状态并返回。否则,四小时后计时器就会触发并清除状态。
/**
* Matches keyed START and END events and computes the difference between
* both elements' timestamps. The first String field is the key attribute,
* the second String attribute marks START and END events.
*/
public static class StartEndDuration
extends KeyedProcessFunction<String, Tuple2<String, String>, Tuple2<String, Long>> {
private ValueState<Long> startTime;
@Override
public void open(Configuration conf) {
// obtain state handle
startTime = getRuntimeContext()
.getState(new ValueStateDescriptor<Long>("startTime", Long.class));
}
/** Called for each processed event. */
@Override
public void processElement(
Tuple2<String, String> in,
Context ctx,
Collector<Tuple2<String, Long>> out) throws Exception {
switch (in.f1) {
case "START":
// set the start time if we receive a start event.
startTime.update(ctx.timestamp());
// register a timer in four hours from the start event.
ctx.timerService()
.registerEventTimeTimer(ctx.timestamp() + 4 * 60 * 60 * 1000);
break;
case "END":
// emit the duration between start and end event
Long sTime = startTime.value();
if (sTime != null) {
out.collect(Tuple2.of(in.f0, ctx.timestamp() - sTime));
// clear the state
startTime.clear();
}
default:
// do nothing
}
}
/** Called when a timer fires. */
@Override
public void onTimer(
long timestamp,
OnTimerContext ctx,
Collector<Tuple2<String, Long>> out) {
// Timeout interval exceeded. Cleaning up the state.
startTime.clear();
}
}
该示例说明了 KeyedProcessFunction
的表达能力,但也强调了它是一个相当冗长的接口。
数据流API
DataStream API
为许多常见的流处理操作提供原语,例如窗口化、一次记录转换和通过查询外部数据存储来丰富事件。 DataStream API
可用于 Java
和 Scala
,并基于 map()、reduce()
和 aggregate()
等函数。函数可以通过扩展接口或作为 Java
或 Scala lambda
函数来定义。
以下示例展示了如何对点击流进行会话并计算每个会话的点击次数。
// a stream of website clicks
DataStream<Click> clicks = ...
DataStream<Tuple2<String, Long>> result = clicks
// project clicks to userId and add a 1 for counting
.map(
// define function by implementing the MapFunction interface.
new MapFunction<Click, Tuple2<String, Long>>() {
@Override
public Tuple2<String, Long> map(Click click) {
return Tuple2.of(click.userId, 1L);
}
})
// key by userId (field 0)
.keyBy(0)
// define session window with 30 minute gap
.window(EventTimeSessionWindows.withGap(Time.minutes(30L)))
// count clicks per session. Define function as lambda function.
.reduce((a, b) -> Tuple2.of(a.f0, a.f1 + b.f1));
SQL & Table API
SQL & Table API
flink有两个相关的API,SQL
和Table API
。这两个API都是处理批处理和流处理的统一的API,也就是说以相同的语义在无界流和有界流上执行计算,并产生相同的结果。Table API
和SQL
使用Apache Calcite
进行解析、验证和查询优化。他们可以与Datastream
和Dataset
API无缝集成。
Flink的关系api旨在简化数据分析、数据管道和ETL应用程序的定义。
下面的示例显示了会话化点击流并计算每个会话的点击次数的SQL
查询。这与DataStream API
示例中的用例相同。
SELECT userId, COUNT(*)
FROM clicks
GROUP BY SESSION(clicktime, INTERVAL '30' MINUTE), userId