【2】Flink 运行模式与 Environment 介绍

221 阅读6分钟

Flink 运行模式

本地运行模式

本地运行模式即为 local 运行模式,启动一个单机实例进行运行。该模式一般是运行简单的程序、或者调试、测试等使用,在机器为单机的情况下也会经常使用。

local 模式对应使用 API:StreamExecutionEnvironment.getExecutionEnvironment() 获取到的是 LocalStreamEnvironment 对象,这个对象在下面会说明。

集群运行模式

集群运行模式即为远程运行模式,将 Job 通过 Client 提交到集群上面,并运行。

该模式支持的集群包括:

  • Standalone 模式:由 Flink 自己创建一个集群并自己管理,用的比较少;
  • Yarn 模式:将任务提交到 Yarn 集群,依赖 Yarn 的资源管理来运行,这种方式用的比较多;
  • K8S 模式:提交到 K8S 集群运行,这种方式使用的也比较多;
  • Mesos 模式:提交到 Mesos 集群运行。

我们重点介绍 Yarn 模式,目前在大数据开发框架中,由于一般会和 Hadoop 套件一起开发,所以一般自带 Yarn 集群,通过 Yarn 来管理资源和任务。

在 Yarn 模式下运行,Flink Client 需要将任务提交到 Yarn 集群运行,提交到 Yarn 也包括三种模式:

  • Session 模式:需要先启动一个 Flink 集群,然后向该集群提交作业,集群会常驻在 Yarn 中,直到手动停止,适合需要频繁提交多个小作业的场景,该方式的 main 方法在 Client 端执行,执行图在 Client 端生成;
  • Per-Job 模式:每个作业都会单独向 Yarn 申请资源,启动一个新的 Flink 集群来运行作业。作业完成后,集群会被关闭。适合运行大作业或需要资源隔离的场景,该方式的 main 方法在 Client 端执行,执行图在 Client 端生成;
  • Application 模式:在 Yarn 上启动集群,应用程序执行结束后,Flink 集群会立即关闭。该模式允许在单个应用程序中提交多个作业,该方式的 main 方法在 Jobmanager 上执行,执行图在 Jobmanager 生成;

三种模式的提交命令也不同:

  • Session 模式:./bin/flink run -m <jobmanager_address> -d <jar_path>,首先需要通过./bin/start-cluster.sh等命令启动Flink会话集群,然后再提交作业;
  • Per-Job 模式:./bin/flink run -t yarn-per-job -D<property>=<value> ... <jar_path> 或新版本中使用 ./bin/flink run -m yarn-cluster -y<property>=<value> ... <jar_path>提交任务,参数设置为 flink 启动参数等;
  • Application 模式:./bin/flink run-application -t yarn-application -D<property>=<value> ... <jar_path>,在 Flink 1.11+ 版本支持,会在 Jobmanager 中启动 jvm 来执行 main 方法。

特别说明,Per-Job 和 Application 模式的选择:

Per-Job 为每个作业启动一个集群,资源隔离性好,但是比较重。Application 模式的提交较为轻量级,速度也比较快,适合轻量级的任务提交。并且由于其支持一个 main 方法中有多个 Job,所以适合多个 Job 之间有依赖关系的任务。

Flink 运行时的 Environment


Flink 运行时 Environment 主要用于定义运行时的一些行为,比如并行度、数据源、任务的实际开始运行入口等等。

包括两大类:ExecutionEnvironment 和 StreamExecutionEnvironment。

其中,ExecutionEnvironment 主要是用于批处理,StreamExecutionEnvironment 主要是用于流式处理,二者之间没有继承关系。

它们各自有各自的子类,如下:

ExecutionEnvironment
  +---- LocalEnvironment:本地提交作业执行,即在本地 JVM 中运行作业
  +---- RemoteEnvironment:将作业提交到远程执行,即将作业提交到远程比如 yarn 上执行
  +---- ...
StreamExecutionEnvironment
  +---- LocalStreamEnvironment:本地执行流式处理任务,在本地 JVM 中运行
  +---- RemoteStreamEnvironment:将任务提交到远程集群上云霄,比如 yarn
  +---- StreamContextEnvironment:比较特殊,在上下文中模拟运行使用,比如在单测、CLI 中提交
  +---- StreamPlanEnvironment:特殊,用于生成执行计划,在 Flink UI 中展示

批处理任务 ExecutionEnvironment

找到类 org.apache.flink.api.java.ExecutionEnvironment,其中包括了定义数据源、设置并行度、设置重启策略等方法。

其中,数据源定义支持从文件读取、从集合读取等等方式读取。

在 ExecutionEnvironment 中定义了启动方法,表示启动任务。

还有一些针对任务的方法包括获取运行计划(运行图)、获取 jobName 等等操作,所有针对环境、资源配置等均可在 ExecutionEnvironment 中进行指定和配置。

ExecutionEnvironment 中运行的数据源读取后是 DataSet 类型,表示批量数据源。 在实际使用过程中,ExecutionEnvironment 使用的比较少,更多的是使用 StreamExecutionEnvironment。

一个典型的批处理例子:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.ExecutionEnvironment;
import org.apache.flink.api.java.operators.DataSource;
import org.apache.flink.api.java.operators.FlatMapOperator;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.util.Collector;

/**
 * 批处理
 */
@Slf4j
public class WordCountMain {
  public static void main(String[] args) throws Exception {
    // 1.创建执行环境,批处理执行环境
    ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();

    // 2.从文件读取数据,继承自 DateSet,不是 DataStream
    DataSource<String> dataSource = env.readTextFile("/tmp/data.txt");

    // 3.提取单词,转换为二元组
    FlatMapOperator<String, Tuple2<String, Long>> pairsOperator = dataSource.flatMap(
        new FlatMapFunction<String, Tuple2<String, Long>>() {
          @Override
          public void flatMap(String line, Collector<Tuple2<String, Long>> collector) throws Exception {
            String[] strs = line.split("\\s+");
            for (String s : strs) {
              if (StringUtils.isNotBlank(s)) {
                collector.collect(Tuple2.of(s, 1L));
              }
            }
          }
        });

    pairsOperator.groupBy(0) // 按照 word 分组
        .sum(1) // 按照 Tuple2[1] 进行聚合
        .print(); // sink
  }
}

流处理任务 StreamExecutionEnvironment

与 ExecutionEnvironment 不同,StreamExecutionEnvironment 的数据源读取进来以后是 DataStream 类型,表示流式数据。

StreamExecutionEnvironment 中包括了设置 Job 监听器、设置并行度、运行模式(Batch、Streaming)、是否禁用算子链、CheckPoint 配置、State 存储配置、SavePoint 配置等。

同时在 StreamExecutionEnvironment 中也包括了从文件、集合、网络端口等读取数据的功能,这一点与 ExecutionEnvironment 类似。也支持自定义 DateSource。

在实际应用过程中,一般使用 StreamExecutionEnvironment 来完成数据处理任务,批处理方式基本不使用。StreamExecutionEnvironment 的定义在 flink-streaming-java 包中。

一段典型的使用流处理的代码如下:

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.flink.api.common.functions.FlatMapFunction;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.util.Collector;

/**
 * 无界流
 */
@Slf4j
public class StreamWordCountMain {
  public static void main(String[] args) throws Exception {
    StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    // env.disableOperatorChaining();
    // DataStreamSource<String> dataSource = env.socketTextStream("127.0.0.1", 8899); // 命令:nc -lk 8899
    DataStreamSource<String> dataSource = env.addSource(new WordDataSource());
    SingleOutputStreamOperator<Tuple2<String, Long>> flatMap =
        dataSource.flatMap(new FlatMapFunction<String, Tuple2<String, Long>>() {
          @Override
          public void flatMap(String line, Collector<Tuple2<String, Long>> collector) throws Exception {
            String[] strs = line.split("\\s+");
            for (String s : strs) {
              if (StringUtils.isNotBlank(s)) {
                collector.collect(Tuple2.of(s, 1L));
              }
            }
          }
        });
    flatMap.keyBy(k -> k.f0)
        .sum(1)
        .print();
    env.execute();
  }
}

一个自定义的数据源比如:

import lombok.extern.slf4j.Slf4j;
import org.apache.flink.streaming.api.functions.source.SourceFunction;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

@Slf4j
public class WordDataSource implements SourceFunction<String> {
  private final Random random = new Random(System.currentTimeMillis());
  private boolean isCanceled = false;

  @Override
  public void run(SourceContext<String> ctx) throws Exception {
    List<String> data = new ArrayList<>();
    BufferedReader br =
        new BufferedReader(new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("data.txt")));
    // new FileReader(new File("resources/data.txt"))
    br.lines().forEach(line -> {
      String[] seps = line.split("\\s+");
      for (String sep : seps) {
        sep = sep.trim();
        if (sep.length() > 0) data.add(sep);
      }
    });
    br.close();

    while (!isCanceled) {
      Thread.sleep(100L);
      ctx.collect(data.get(random.nextInt(data.size())));
    }
  }

  @Override
  public void cancel() {
    log.info("data source canceled.");
    isCanceled = true;
  }
}

需要注意,StreamExecutionEnvironment 是我们在编写 flink 程序的时候使用的类,而不是 flink 程序运行时候的类,在 flink 的 TaskManager 运行的时候,StreamExecutionEnvironment 会被转换为 RuntimeContext 对象,转换是通过 RuntimeEnvironment 类来转换的,RuntimeContext 中除了 StreamExecutionEnvironment 中的信息以外,还包括当前 Task 的一些信息。