Flink学习记录2:执行环境和数据源

Flink程序的组成

一个 Flink 程序,其实就是对 DataStream 的各种转换。具体来说,代码基本上都由以下几部分构成,如图所示:
⚫ 获取执行环境(execution environment)
⚫ 读取数据源(source)
⚫ 定义基于数据的转换操作(transformations)
⚫ 定义计算结果的输出位置(sink)
⚫ 触发程序执行(execute)在这里插入图片描述

1.1创建执行环境

  1. getExecutionEnvironment:最常用的创建方法
    在这里插入图片描述
  2. createLocalEnvironment
    在这里插入图片描述
  3. createRemoteEnvironment
    在这里插入图片描述

1.2执行模式:流处理和批处理

Flink默认是流处理模式,要设置为批处理(处理有界数据更高效)的话,主要有两种方式:
(1)通过命令行配置:在提交作业时,增加 execution.runtime-mode 参数,指定值为 BATCH。

bin/flink run -Dexecution.runtime-mode=BATCH 

(2) 通过代码配置

StreamExecutionEnvironment env =StreamExecutionEnvironment.getExecutionEnvironment();
env.setRuntimeMode(RuntimeExecutionMode.BATCH);

1.3触发程序执行

需要显式地调用执行环境的 execute()方法,来触发程序执行。 execute()方法将一
直等待作业完成,然后返回一个执行结果(JobExecutionResult)。

env.execute();

2.1数据源Source

Flink 代码中通用的添加 source 的方式,是调用执行环境的 addSource()方法:

DataStream<String> stream = env.addSource(...);

其中需要传入一个对象参数,传入的参数是一个“源函数”( source function),需要实现SourceFunction 接口。

2.1.1

1.创建一个实体类

实体类需要有这样几个特点:
⚫ 类是公有(public)的
⚫ 有一个无参的构造方法
⚫ 所有属性都是公有(public)的
⚫ 所有属性的类型都是可以序列化的
Flink 会把这样的类作为一种特殊的 POJO 数据类型来对待,方便数据的解析和序列化。

import java.sql.Timestamp;
public class Event {
	public String user;
	public String url;
	public Long timestamp;
	public Event() {}
	public Event(String user, String url, Long timestamp) {
		this.user = user;
		this.url = url;
		this.timestamp = timestamp;
	}
@Override
public String toString() {
	return "Event{" +
	"user='" + user + '\'' +
	", url='" + url + '\'' +
	", timestamp=" + new Timestamp(timestamp) +
	'}';
	}
}
2.从集合中读取数据fromCollection,从元素中读取fromElements
public static void main(String[] args) throws Exception {
	StreamExecutionEnvironment env =StreamExecutionEnvironment.getExecutionEnvironment();
	env.setParallelism(1);
	ArrayList<Event> clicks = new ArrayList<>();
	clicks.add(new Event("Mary","./home",1000L));
	clicks.add(new Event("Bob","./cart",2000L));
	DataStream<Event> stream = env.fromCollection(clicks);
	stream.print();
	env.execute();
}
//从元素读取:
DataStreamSource<Event> stream2 = env.fromElements(
	new Event("Mary", "./home", 1000L),
	new Event("Bob", "./cart", 2000L)
);
3.从文件读取readTextFile
//从日志文件中读取
DataStream<String> stream = env.readTextFile("clicks.csv");
4.从socket读取socketTextStream
//从localhost的7777端口端口读取文本
DataStream<String> stream = env.socketTextStream("localhost", 7777);
5.从kafka读取

在这里插入图片描述
在POM中导入依赖:

<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-connector-kafka_${scala.binary.version}</artifactId>
<version>${flink.version}</version>
</dependency>

调用通用添加数据源方法addSource

import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;

import java.util.Properties;

public class SourceKafkaTest {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "hadoop102:9092");
        properties.setProperty("group.id", "consumer-group");
        properties.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        properties.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
        properties.setProperty("auto.offset.reset", "latest");

        DataStreamSource<String> stream = env.addSource(new FlinkKafkaConsumer<String>(
                "clicks",
                new SimpleStringSchema(),
                properties
        ));

        stream.print("Kafka");
        env.execute();
    }
}
6.自定义source
import org.apache.flink.streaming.api.functions.source.SourceFunction;

import java.util.Calendar;
import java.util.Random;

public class ClickSource implements SourceFunction<Event> {
    // 声明一个布尔变量,作为控制数据生成的标识位
    private Boolean running = true;
    @Override
    public void run(SourceContext<Event> ctx) throws Exception {
        Random random = new Random();    // 在指定的数据集中随机选取数据
        String[] users = {"Mary", "Alice", "Bob", "Cary"};
        String[] urls = {"./home", "./cart", "./fav", "./prod?id=1", "./prod?id=2"};

        while (running) {
            ctx.collect(new Event(
                    users[random.nextInt(users.length)],
                    urls[random.nextInt(urls.length)],
                    Calendar.getInstance().getTimeInMillis()
            ));
            // 隔1秒生成一个点击事件,方便观测
            Thread.sleep(1000);
        }
    }
    @Override
    public void cancel() {
        running = false;
    }

}

使用addSource添加自定义源

import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

public class SourceCustomTest {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        //有了自定义的source function,调用addSource方法
        DataStreamSource<Event> stream = env.addSource(new ClickSource());
        stream.print("SourceCustom");
        env.execute();
    }
}

这里要注意的是 SourceFunction 接口定义的数据源,并行度只能设置为 1,如果数据源设置为大于 1 的并行度,则会抛出异常。
需要使用实现了ParallelSourceFunction的自定义source:

import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.source.ParallelSourceFunction;

import java.util.Random;

public class SourceCustomParallelTest {
    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env.addSource(new CustomSource()).setParallelism(2).print();

        env.execute();
    }

    public static class CustomSource implements ParallelSourceFunction<Integer> {
        private boolean running = true;
        private Random random = new Random();

        @Override
        public void run(SourceContext<Integer> sourceContext) throws Exception {
            while (running) {
                sourceContext.collect(random.nextInt());
            }
        }

        @Override
        public void cancel() {
            running = false;
        }
    }
}

Flink支持的数据类型

简单来说,对于常见的 Java 和 Scala 数据类型, Flink 都是支持的。 Flink 在内部, Flink对支持不同的类型进行了划分,这些类型可以在 Types 工具类中找到:
(1)基本类型
所有 Java 基本类型及其包装类,再加上 Void、 String、 Date、 BigDecimal BigInteger。
( 2)数组类型
包括基本类型数组(PRIMITIVE_ARRAY)和对象数组(OBJECT_ARRAY)
( 3)复合数据类型
⚫ Java 元组类型(TUPLE):这是 Flink 内置的元组类型,是 Java API 的一部分。最多
25 个字段,也就是从 Tuple0~Tuple25,不支持空字段。
⚫ Scala 样例类及 Scala 元组:不支持空字段
⚫ 行类型(ROW):可以认为是具有任意个字段的元组,并支持空字段
⚫ POJO: Flink 自定义的类似于 Java bean 模式的类
( 4)辅助类型
Option、 Either、 List、 Map 等
( 5)泛型类型(GENERIC)
Flink 支持所有的 Java 类和 Scala 类。不过如果没有按照上面 POJO 类型的要求来定义,就会被 Flink 当作泛型类来处理。 Flink 会把泛型类型当作黑盒,无法获取它们内部的属性;它们也不是由 Flink 本身序列化的,而是由 Kryo 序列化的。在这些类型中,元组类型和 POJO 类型最为灵活,因为它们支持创建复杂类型。而相比之下, POJO 还支持在键(key)的定义中直接使用字段名,这会让我们的代码可读性大大增加。
所以,在项目实践中,往往会将流处理程序中的元素类型定为 Flink 的 POJO 类型。
Flink 对 POJO 类型的要求如下:
⚫ 类是公共的( public)和独立的( standalone,也就是说没有非静态的内部类);
⚫ 类有一个公共的无参构造方法
⚫ 类中的所有字段是 public 且非 final 的;或者有一个公共的 getter 和 setter 方法,这些方法需要符合 Java bean 的命名规范。
之前的 UserBehavior,就是我们创建的符合 Flink POJO 定义的数据类型。
3. 类型提示(Type Hints)
Flink 还具有一个类型提取系统,可以分析函数的输入和返回类型,自动获取类型信息,从而获得对应的序列化器和反序列化器。但是,由于 Java 中泛型擦除的存在,在某些特殊情况下(比如 Lambda 表达式中),自动提取的信息是不够精细的——只告诉 Flink 当前的元素由“船头、船身、船尾”构成,根本无法重建出“大船”的模样;这时就需要显式地提供类型信息,才能使应用程序正常工作或提高其性能。为了解决这类问题, Java API 提供了专门的“类型提示”(type hints)。
回忆一下之前的 word count 流处理程序,我们在将 String 类型的每个词转换成(word,count)二元组后,就明确地用 returns 指定了返回的类型。因为对于 map 里传入的Lambda 表达式,系统只能推断出返回的是 Tuple2 类型,而无法得到 Tuple2<String, Long>。只有显式地告诉系统当前的返回类型,才能正确地解析出完整数据。

.map(word -> Tuple2.of(word, 1L))
.returns(Types.TUPLE(Types.STRING, Types.LONG));

这是一种比较简单的场景,二元组的两个元素都是基本数据类型。那如果元组中的一个元
素又有泛型,该怎么处理呢?
Flink 专门提供了 TypeHint 类,它可以捕获泛型的类型信息,并且一直记录下来,为运行
时提供足够的信息。我们同样可以通过.returns()方法,**明确地指定转换之后的 DataStream 里元素的类型。**如:
returns(new TypeHint<Tuple2<Integer, SomeType>>(){})

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值