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在简洁性和表达性之间提供了不同的权衡,并针对不同的用例。

Flink核心组件介绍 flinkk_API

ProcessFunctions(过程函数)

过程函数是最具有灵活性和表现力的函数编程接口。Flink提供ProcessFunctions来处理来自一个或两个输入流或在窗口中分组的事件的单个事件。ProcessFunctions提供对时间和状态的细粒度控制。ProcessFunction可以任意修改其状态,并注册将来触发回调函数的计时器。因此,ProcessFunctions可以实现许多有状态事件驱动的应用程序所需的复杂事件业务逻辑。

下面的示例显示了一个KeyedProcessFunction,它操作KeyedStream并匹配STARTEND事件。当接收到START事件时,该函数会记住其状态时间戳并在四小时内注册一个计时器。如果在定时器触发之前收到一个END事件,函数计算ENDSTART事件之间的持续时间,清除状态并返回。否则,四小时后计时器就会触发并清除状态。

/**
 * 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 可用于 JavaScala,并基于 map()、reduce()aggregate() 等函数。函数可以通过扩展接口或作为 JavaScala 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,SQLTable API。这两个API都是处理批处理和流处理的统一的API,也就是说以相同的语义在无界流和有界流上执行计算,并产生相同的结果。Table APISQL使用Apache Calcite进行解析、验证和查询优化。他们可以与DatastreamDataset API无缝集成。

Flink的关系api旨在简化数据分析、数据管道和ETL应用程序的定义。

下面的示例显示了会话化点击流并计算每个会话的点击次数的SQL查询。这与DataStream API示例中的用例相同。

SELECT userId, COUNT(*)
FROM clicks
GROUP BY SESSION(clicktime, INTERVAL '30' MINUTE), userId