Flink学习3-API介绍

Flink学习3-API介绍

Flink系列文章

摘要

本文主要是介绍Flink的不同层次(level)API抽象,学习怎么通过API高效处理有状态性的计算无界和有界的数据流。

1 Flink多层API

Flink提供了三个不同层次的API,每种API在简洁和易表达间有自己的权衡,适用于不同的场景:
Flink-Layered-API
可以看到Flink一共有三个抽象层次的API,目测应该前两个会用的比较多,他们更加简洁但是表达性比较差。下面自底向上分别简要介绍下这三个API。

1.1 ProcessFunctions

看了上面的图我们知道ProcessFunctions最具表现力但是简洁性最差,是最底层的抽象API,他被主要用来处理包含单独事件的一个或两个输入流或者是分组到一个窗口类的事件,所以提供了对时间和状态的细粒度控制。ProcessFunctions可强制修改state、重注册未来某时触发回调函数的timer,所以可以实现复杂事件处理逻辑,这正适合很多有状态的事件驱动应用程序

因为最近作者调研主要涉及FLink流式SQL API,这里没有详看,想要了解的请参见最后参考文档中给出的连接学习。

1.2 DataStream API

  • DataStream API提供了若干常用的流/批处理操作,如窗口等。

  • 有Java和Scala的API可选,都是依赖一些底层的基本方法如map/aggregate等实现的。

下面示例展示session化一个click流然后对每个session中的点击数计数:

// 网站的点击流
DataStream<Click> clicks = ...

DataStream<Tuple2<String, Long>> result = clicks
  // 将点击数与userId匹配,每一个点击就加1
  .map(
    // 定义一个实现了MapFunction接口的方法
    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)
  // 定义30分钟间隙的session窗口
  .window(EventTimeSessionWindows.withGap(Time.minutes(30L)))
  // 对每个session点击计数,并定义为lambda函数
  .reduce((a, b) -> Tuple2.of(a.f0, a.f1 + b.f1));

详见第四章

1.3 SQL&Table API

第三章

2 库

Flink对常见的流式处理场景提供了若干内库,他们通常嵌入到API中,并非完全独立。 因此,他们可以从API的所有特性中受益,并与其他库集成:

2.1 Complex Event Processing (CEP)

该内库提供API来指定不同事件的模式,就像正则表达式或是状态机。模式识别是非常常见的事件流处理场景。

CEP库的应用包括网络入侵检测,业务流程监控和欺诈检测。

2.2 DataSet API

DataSet API是Flink的核心API,用来应对批处理应用。

2.3 Gelly

Gelly是一个可扩展的图形处理和分析库,他在DataSet API之上集成实现。

Gelly具有内置算法,如标签传播,三角枚举和页面排名,但也提供了一个简化自定义图算法实现的Graph API。

3 SQL&Table API

请点击Flink学习4-流式SQL

4 DataStream

4.1 处理时间设定

这里以ProcessingTime为例,分窗时间为1小时:

val env = StreamExecutionEnvironment.getExecutionEnvironment

env.setStreamTimeCharacteristic(TimeCharacteristic.ProcessingTime)

// 可选的:
// env.setStreamTimeCharacteristic(TimeCharacteristic.IngestionTime)
// env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)

val stream: DataStream[MyEvent] = env.addSource(new FlinkKafkaConsumer09[MyEvent](topic, schema, props))

stream
    .keyBy( _.getUser )
    .timeWindow(Time.hours(1))
    .reduce( (a, b) => a.add(b) )
    .addSink(...)

TimeCharacteristic决定了Source怎么表现(比如是否分配timestamp)以及时间窗口算子使用哪种时间作为计算标准。

如果使用EventTIme,用户要么需要使用直接为数据定义EventTIme并自己发出watermark的Source,要么在Source之后必须引入Timestamp Assigner来分配timestamp和Watermark Generator

4.2 DataStream转换

在这里插入图片描述

4.3 Connector

4.3.1 KafkaConnector

请参考Flink学习-DataStream-KafkaConnector

4.3.2 HDFSConnector(StreamingFileSink)

请参考Flink学习-DataStream-HDFSConnector(StreamingFileSink)

4.4 分区策略

Operator数据交换与分区策略
在这里插入图片描述

DataStream可以在两个算子间传输数据,有以下两种模式:

  • 一对一
    例如上图中Sourcemap()算子之间。

    可保留元素的分区和排序信息(也就是说map()算子的1号实例可以相同顺序看到跟Source算子的1号实例生产顺序相同的元素)。

  • 重分发-类似MR Shuffle
    例如上图中的 map()keyBy/window算子 之间,以及 keyBy/windowSink 之间。

    会更改数据所在Stream分区。注意此时只能保证一个算子subtask发到一个下游算子subtask的元素顺序性。如上图keyBy/window 的 subtask[2] 接收到的 map() 的 subtask[1]的数据有序,但发送到Sink的所有数据中,无法确定不同key的聚合结果的到达顺序。

    每个算子subtask发送数据到不同的下游算子subtask,分发依据是具体的transformation(相关方法在org.apache.flink.streaming.api.datastream.DataStream):

    • keyBy
      按照key的值hash后重分区到某个下游算子实例

    • broadcast
      广播到所有下游算子实例分区

    • rebalance
      轮询分配到下游算子实例分区

    • global
      全部分配到第一个下游算子实例分区

    • shuffle
      随机均匀分配到下游算子实例分区

    • forward
      上下游并行度一致时,发送到对应的位于本地的下游算子分区

    • rescale
      轮询方式将输出的元素均匀分发到下游分区的子集。

      子集构建依赖于上游和下游算子的并行度。

      • 比如上游算子并行度2,下游为4,此时每个上游算子轮询各自分发到下游的两个算子。
      • 如果上游并行度4,下游为2,此时每两个上游算子分发到一个下游算子。
      • 如果不是倍数,则下游分发的源头数目不一致

4.5 DataStream Source构建

  • fromElements
    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.streaming.api.datastream.DataStream;
    import org.apache.flink.api.common.functions.FilterFunction;
    
    public class Example {
    
        public static void main(String[] args) throws Exception {
            final StreamExecutionEnvironment env =
                    StreamExecutionEnvironment.getExecutionEnvironment();
    
            DataStream<Person> flintstones = env.fromElements(
                    new Person("Fred", 35),
                    new Person("Wilma", 35),
                    new Person("Pebbles", 2));
    
            DataStream<Person> adults = flintstones.filter(new FilterFunction<Person>() {
                @Override
                public boolean filter(Person person) throws Exception {
                    return person.age >= 18;
                }
            });
    
            adults.print();
    
            env.execute();
        }
    
        public static class Person {
            public String name;
            public Integer age;
            public Person() {};
    
            public Person(String name, Integer age) {
                this.name = name;
                this.age = age;
            };
    
            public String toString() {
                return this.name.toString() + ": age " + this.age.toString();
            };
        }
    }
    
  • fromCollection
    List<Person> people = new ArrayList<Person>();
    
    people.add(new Person("Fred", 35));
    people.add(new Person("Wilma", 35));
    people.add(new Person("Pebbles", 2));
    
    DataStream<Person> flintstones = env.fromCollection(people);
    
  • socketTextStream
    DataStream<String> lines = env.socketTextStream("localhost", 9999)
    
  • readTextFile
    DataStream<String> lines = env.readTextFile("file:///path");
    
  • addSource(connector source)

4.6 DataStream Sink构建

  • print
    对结果流中每个元素代用toString(),将计算结果打印到TM日志中(如果是本地ide,则打印到控制台)。

    输出看起来类似于

    1> Fred: age 35
    2> Wilma: age 35
    

    1>2> 表示输出来自哪个 subtask线程。

  • addSink(connector sink)
    如StreamingFileSink、JDBC等

4.7 无状态算子

4.7.1 概述

这一节介绍的算子都是无状态的。也就是说新元素到来的计算和之前的元素毫无关系。

4.7.2 Map

输入元素的数量和输出元素数量一一对应,但可以改变元素类型。
在这里插入图片描述

4.7.3 FlatMap

输入一个元素,输出0-N个元素。可以改变元素类型。
在这里插入图片描述

4.7.4 Filter

每个元素调用该方法,返回一个boolean。为true就保留,为false就忽略。
在这里插入图片描述

4.8 有状态算子

4.8.1 概述

有状态算子,输入元素计算会依赖早前来到的元素。状态可快照保存到StateBackend。

4.8.2 keyBy

按照key的值hash后重分区到某个下游算子实例,可保证同key元素分配到相同分区。

DataStream调用后,得到KeyedStream,需要使用KeyedState。
在这里插入图片描述
每个 keyBy 会通过 hash shuffle 来为数据流进行重新分区。总体来说这个开销是很大的,它涉及网络通信、序列化和反序列化。

指定字段进行keyBy:

dataStream.keyBy("someKey") // Key by field "someKey"
dataStream.keyBy(0) // Key by the first element of a Tuple

注意,以下类型不能作为key

  • 没有覆写hashCode方法的POJO类型
  • 数组类型

以上直接指定字段为key的方式有个缺点就是无法推断key的字段类型,在flink内部会作为Tuple传递,比较难处理。所以也可以用一个合适的 KeySelector:

rides
    .flatMap(new NYCEnrichment())
    .keyBy(
        new KeySelector<EnrichedRide, int>() {
            @Override
            public int getKey(EnrichedRide enrichedRide) throws Exception {
                return enrichedRide.startCell;
            }
        })

可使用lambda表达式简写

rides
    .flatMap(new NYCEnrichment())
    .keyBy(enrichedRide -> enrichedRide.startCell)

当然,也可以不从Event中抽取Key,可以直接通过指定的计算得到

keyBy(ride -> GeoUtils.mapToGridCell(ride.startLon, ride.startLat))

4.8.3 Reduce

将当前元素和最后聚合的值进行聚合,发送得到的新值。
在这里插入图片描述

4.8.4 Fold

加入了初始值的概念,将新值与之前折叠的旧值进行折叠操作,输出结果
在这里插入图片描述

4.8.5 聚合算子

注意,加了By后缀的算子含义是返回元素,不加的是返回值。
在这里插入图片描述

4.8.6 Window

4.8.6.1 Window

Window算子可在已分区的KeyedStream上定义,会根据时间予以将每个key的数据分窗。
在这里插入图片描述
在这里插入图片描述

4.8.6.2 WindowAll

直接在未按key分区的DataStream上定义,按时间分窗。大多数情况下,是只有一个算子实例来处理所有的数据聚合。
在这里插入图片描述
在这里插入图片描述

4.8.6.3 Window Apply

将函数应用于整个窗口。如果使用windowAll,则需配合传递AllWindowFunction
在这里插入图片描述

4.8.6.4 Window Reduce

在这里插入图片描述

4.8.6.5 Window Fold

在这里插入图片描述

4.8.6.6 Window聚合算子

在这里插入图片描述

4.8.6.7 Evictor

可以为窗口计算前后做一些预处理工作,如驱逐元素。
在这里插入图片描述

4.8.6.8 Trigger
  • WindowAssigner
    窗口分配器接口,如TumblingWindowAssigner,用来给每个元素分配0个或多个(TumblingWindowAssigner最多有一个)Window。

  • Pane
    在Window算子中,当某个列key可用时就用key和所属的Window来分组。而拥有同样的key和Window的所有元素就被称为一个Pane。

    一个被WindowAssigner分配到多个Window的元素可以在同个Pane中,这些Pane每个都拥有自己独立的一份Trigger实例。

    目前只有SlidingWindowAssigner实现了PanedWindowAssigner

  • Trigger
    由触发器决定何时让某个Pane触发所拥有窗口来生产输出元素。

    需要注意的是Trigger不允许内部维护状态,因为他们可被不同的key重建或重用,而应该使用TriggerContext来持久化状态
    在这里插入图片描述

4.9 多DataStream操作算子

4.9.1 Union

讲两个或更多的流合并,创建一个新的包含子流的所有元素的新流。
在这里插入图片描述

4.9.2 Window Join

将两个流join,join key由用户指定,还需要指定共用的时间窗口以及join操作函数
在这里插入图片描述

4.9.3 Interval Join

在给定时间间隔内按给定的key join来自两个流的元素。
在这里插入图片描述

4.9.4 Window CoGroup

将两个流组合,组合 key由用户指定,还需要指定共用的时间窗口以及组合操作函数。

组合的含义是将拥有相同key的数据分组到一起。
在这里插入图片描述

4.9.5 Connect

连接两个流,并保留他们的数据类型
在这里插入图片描述

4.9.6 CoMap, CoFlatMap

类似单流的Map和FlatMap,不过这里是用在ConnectedStreams
在这里插入图片描述

4.9.7 Split

将一个流拆分为2个或更多流

以下例子按value的奇数或偶数拆分为了两个流
在这里插入图片描述

4.9.8 Select

从SplitStream中选取1个或多个流
在这里插入图片描述

4.9.9 Iterate

在这里插入图片描述

4.10 异步IO

4.10.1 概述

在这里插入图片描述

同步IO,比如MapFunction中访问外部数据库,则一个请求发送到数据库后必须等待收到返回,占据函数运行大量时间。

而如果使用异步IO,则一个算子并行实例可在向外部发出请求的等待时间中继续发送其他请求、接收响应。这样可摊销多个请求的等待时间,可提高流式吞吐。

这比单独提升MapFunction算子的并行度带来的资源开销要小很多。

还可参考

4.10.2 先决条件

需要连接外部存储客户端支持异步请求。

如果没有这样的客户端,则可以尝试通过创建多个并发客户端,从而将同步客户端改造为并发客户端,这样可以通过线程池处理多个同步请求,但这个方式效率通常低于原生异步客户端。

4.10.3 异步IO API

4.10.3.1 概述

Flink 异步 IO API可配合异步请求客户端在DataStream中使用,该API 处理与数据流的集成,同时还能处理好顺序、EventTime和容错等。还需要做的:

  • 实现分派请求的AsyncFunction接口
  • 定义一个要传递给ResultFuture的回调函数,该回调函数会接受操作结果
  • 在DataStream上应用该异步IO作为转换操作

官方示例:

// 这个例子使用 Java 8 的 Future 接口(与 Flink 的 Future 相同)实现了异步请求和回调。

/**
 * 实现 'AsyncFunction' 用于发送请求和设置回调。
 */
class AsyncDatabaseRequest extends RichAsyncFunction<String, Tuple2<String, String>> {

    /** 能够利用回调函数并发发送请求的数据库客户端 */
    private transient DatabaseClient client;

	// open方法内初始化时创建我们自定义的DatabaseClient
    @Override
    public void open(Configuration parameters) throws Exception {
        client = new DatabaseClient(host, post, credentials);
    }
	
	// 记得在close时关闭自定义的DatabaseClient
    @Override
    public void close() throws Exception {
        client.close();
    }
	
	// 定义异步调用
    @Override
    public void asyncInvoke(String key, final ResultFuture<Tuple2<String, String>> resultFuture) throws Exception {

        // 使用自定义的DatabaseClient发送异步请求,接收 future 结果
        final Future<String> result = client.query(key);

        // 设置客户端完成请求后要执行的回调函数
        // 这里回调函数只是简单地把结果发给 resultFuture
        CompletableFuture.supplyAsync(new Supplier<String>() {

            @Override
            public String get() {
                try {
                    return result.get();
                } catch (InterruptedException | ExecutionException e) {
                    // 显示地处理异常。
                    return null;
                }
            }
        }).thenAccept( (String dbResult) -> {
        	// 这个resultFuture在首次调用ResultFuture.complete后就完成了,后续complete都被忽略
            resultFuture.complete(Collections.singleton(new Tuple2<>(key, dbResult)));
        });
    }
}

// 创建初始 DataStream
val stream: DataStream[String] = ...

// 应用异步 I/O 转换操作
val resultStream: DataStream[(String, String)] =
    AsyncDataStream.unorderedWait(stream, new AsyncDatabaseRequest(), 1000, TimeUnit.MILLISECONDS, 100)
4.10.3.2 参数

此外,还有两个参数控制异步行为:

  • timeout
    每个异步请求被认为失败前的超时时间,防止失败、僵死的请求。
  • capacity
    同时能进行的最大并发异步请求数量。尽管异步IO一般可带来上佳的吞吐,但这类带有异步IO的算子可能在流式应用中成为瓶颈,所以需要限制异步IO请求总数。一旦超过本限制,会触发背压。
4.10.3.3 异步IO超时处理

默认会抛出异常,导致Job重启。

也可以自己实现AsyncFunction#timeout方法来自定义处理异常。

4.10.3.4 实现原理

AsyncDataStream.(un)orderedWait 创建一个 AsyncWaitOperator,该算子用来支持异步IO,就会调用我们定义的AsyncFunction内的方法,并处理异步执行后得到的结果。
在这里插入图片描述

  • StreamElementQueue
    进行中的请求对应的未完成的Promise的队列。

    StreamElementQueue有实现, OrderedStreamElementQueue 和 UnorderedStreamElementQueue,分别用于有序和无序两种场景。

  • Promise是表示未来某个值的异步抽象。

  • Emitter
    收到异步执行结果后发送消息给下游的一个线程。

  • 执行流程

    1. 将进入该算子的元素(如E5)包装为Promise P5
    2. 将P5放入StreamElementQueue
    3. 调用用户定义的AsyncFunction#asyncInvoke方法发起异步请求,处理回调交给ResultFuture处理
      asyncInvoke方法会向用户自定义的外部服务发起一个异步的请求,并注册回调。该回调会在异步请求成功返回时调用AsyncCollector.collect方法将返回的结果交给Flink框架处理。

    实际上AsyncCollector是一个 Promise ,也就是上图的P5,在调用collect的时候会标记 Promise 为完成状态,并通知 Emitter 线程有完成的消息可以发送了,此时Emiter就会从队列中拉取Promise并发送到下游。

4.10.3.5 结果顺序

AsyncFunction带来的结果顺序是未知的,具体依赖于请求完成的先后顺序。

要控制结果发送顺序,有两种模式:

4.10.3.5.1 无序
  • 概述
    此时一旦收到结果就立刻发送,也就是说该顺序和异步请求顺序不同,使得流中元素记录在异步算子内发生变化。

    使用DataStream API时可用AsyncDataStream.unorderedWait(...)开启本模式。

  • Processing Time
    当使用processing time时,本模式的特点是最低的延迟和开销。
    在这里插入图片描述
    本模式下有两个队列。新来的元素包装为Promise后放入uncompletedQueue队列,只要得到结果返回便会被立刻放入completedQueue队列,并通知Emitter拉取消费并发送。此时因为没有水位,所以不需要watermark 与消息的顺序性。

  • EventTime
    异步IO算子可以正确处理EventTime中的水位。水位和记录保持同步,不会互相超过。只有连续两个 watermark 之间的记录是无序发出的。

    此时,在某个水位A后出现的每条记录都仅会在那个水位被发送后才会被发送。

    反过来,仅当所有一堆输入记录对应的结果数据发送后,才会发送这些输入记录之后的那个水位。
    在这里插入图片描述
    因为有 watermark,需要协调 watermark 与消息之间的顺序性,所以跟使用Processing Time时无序相比,uncompletedQueue中存放的每个元素从原先的 单个Promise 变成了 Promise的集合:

    • 新到来的普通元素被包装为Promise放入uncompletedQueue队尾的Promise集合中
    • 新到来的水位元素会被包装为Promise,并构建一个Promise集合放入,最后放入uncompletedQueue队尾,并创建新的Promise集合以放入后续普通元素
    • 这样,水位元素就成了边界

    仅当uncompletedQueue队首集合中的某个Promise返回结果后才将该Promise移入completedQueue队列,再由Emitter处理消费并发往下游。

    仅当队首Promise集合中的所有Promise都处理完毕后,才会发送紧邻的水位,随后才能继续处理下一个Promise集合内的元素。这样就保证了当且仅当某个 watermark 之前所有的消息都已经被发送了,该 watermark 才能被发送。

4.10.3.5.2 有序

保留流顺序,也就是说结果发送顺序和异步请求触发顺序(记录流入算子顺序)相同。所以会将结果记录缓存,直到他之前的记录结果都已经发送完毕或被判定为超时。

这种模式带来顺序的同时会增加大量额外延时和checkpoint开销,因为记录或结果会被放在checkpoint状态中相比于无序模式更长时间。

使用AsyncDataStream.orderedWait(...)开启本模式。
在这里插入图片描述
本模式下只有一个有序队列。将所有进入算子元素(包括水位)都包装为Promise按序放入有序队列OrderedStreamElementQueue

当某个Promise有结果返回时,只会被标记已完成,但不会立刻发送。

Emitter会等到队列队首(如上图P1)元素完成得到结果后才会触发 Emitter 拉取队首Promise元素进行发送到下游。

有序模式下使用EventTime时,水位和数据顺序被保留。此时开销和使用ProccsingTime时差别不算很大。

4.10.3.6 容错保证

异步IO算子提供完全的exactly-once容错包证。

会为in-flight异步请求缓存对应记录到checkpoint,还会当从错误中恢复时恢复或重触发请求。

具体如下,转自Flink 原理与实现:Aysnc I/O,作者阿里-伍翀:
在这里插入图片描述

4.10.3.7 代码实现小贴士

推荐使用DirectExecutor,因为一般来说回调函数会做尽量少的工作,DirectExecutor 避免了额外的线程切换带来的开销。回调函数通常仅把结果发送给 ResultFuture,其实就是将他们添加到输出缓存。

从这里开始,包括发送记录和与 chenkpoint 交互在内的繁重逻辑都将在专有的线程池中进行处理。

DirectExecutor可使用org.apache.flink.runtime.concurrent.Executors.directExecutor()com.google.common.util.concurrent.MoreExecutors.directExecutor()实现。

4.10.3.8 警告
  • Flink 不会以多线程的方式调用AsyncFunction
    AsyncFunction只有一个实例,它被流中相应分区内的每个记录顺序地调用。除非 asyncInvoke(...)方法快速返回并且依赖于客户端回调,否则无法实现正确的异步 I/O。

    比如说,以下方式会导致阻塞的asyncInvoke,从而使异步行为无效:

    • 使用同步的数据库客户端
      它的查询方法调用在返回结果之前一直被阻塞。
    • asyncInvoke(...) 方法内阻塞等待异步客户端返回的 future 类型对象
  • 目前,出于一致性的原因,AsyncFunction算子(AsyncWaitOperator)必须位于算子链的头部

4.11 更多例子

5 DataSet

批处理API。用到的时候补充。

6 传参数

6.1 概述

这里说的参数是指在提交Flink任务时要传递到Flink程序里的参数,比如指定路径、集群地址、系统参数等。

可以使用传统的Commons CLIargparse4j来解析参数,也可以使用Flink自带的ParameterTool来操作。

6.2 ParameterTool

6.2.1 ParameterTool读取放入

6.2.1.1 从.properties文件读取

三种方式如下:

String propertiesFilePath = "/home/sam/flink/myjob.properties";
ParameterTool parameter = ParameterTool.fromPropertiesFile(propertiesFilePath);

File propertiesFile = new File(propertiesFilePath);
ParameterTool parameter = ParameterTool.fromPropertiesFile(propertiesFile);

InputStream propertiesFileInputStream = new FileInputStream(file);
ParameterTool parameter = ParameterTool.fromPropertiesFile(propertiesFileInputStream);
6.2.1.2 从命令行读取

可以读取类似以下格式的命令行参数:

--input hdfs:///mydata --elements 42

代码如下:

public static void main(String[] args) {
  ParameterTool parameter = ParameterTool.fromArgs(args);
}
6.2.1.3 从系统属性读取

可以读取JVM系统参数如-Dinput=hdfs:///mydata

此时代码如下:

ParameterTool parameter = ParameterTool.fromSystemProperties();

6.2.2 ParameterTool的使用

6.2.2.1 ParameterTool直接使用
ParameterTool parameters = // ...
parameter.getRequired("input");
parameter.get("output", "myDefaultValue");
// 以下为获取并行度例子
int parallelism = parameters.get("mapParallelism", 2);
DataSet<Tuple2<String, Integer>> counts = text.flatMap(new Tokenizer()).setParallelism(parallelism);

parameter.getLong("expectedCount", -1L);
parameter.getNumberOfParameters()

ParameterTool是可序列化的,所以可以将它传给function使用:

ParameterTool parameters = ParameterTool.fromArgs(args);
DataSet<Tuple2<String, Integer>> counts = text.flatMap(new Tokenizer(parameters));
6.2.2.2 ParameterTool全局注册

Parameters被注册为ExecutionConfig中的全局Job Parameter,可以从JobManager web接口和用户定义的所有function中访问。

注册全局参数代码如下:

ParameterTool parameters = ParameterTool.fromArgs(args);

// set up the execution environment
final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
// register global parameter
env.getConfig().setGlobalJobParameters(parameters);

然后就可以在RichFunction中访问:

public static final class Tokenizer extends RichFlatMapFunction<String, Tuple2<String, Integer>> {

    @Override
    public void flatMap(String value, Collector<Tuple2<String, Integer>> out) {
	ParameterTool parameters = (ParameterTool)
	    getRuntimeContext().getExecutionConfig().getGlobalJobParameters();
	parameters.getRequired("input");
	// .. do more ..

总结

这篇文章主要讲了一些Flink编程中用到的基本概念和API,为了更加深入理解,还要多学习下Example才行,请点击这里

更多文档

参考文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值