DataStream API(二)

DataStream API(二)

书接上回,在上一篇DataStream API介绍中我们了解到Flink程序一般由五部分组成。分别是:

  1. 获得执行环境(Environment),
  2. 加载/创建初始数据(Source),
  3. 指定此数据的转换(Transform),
  4. 指定计算结果的存放位置(Sink),
  5. 触发程序执行(Execut)

Environment和Source已经介绍过了,接下来我们来了解一下Transform以及Flink支持的数据类型。话不多说,Let`s go!

Transform

map

DataStream—>DataStream:这个算子是接收一个输出一个,这么干说可能有点单薄,上图:

在这里插入图片描述

由上图我们可以看出来map是接收一条数据处理之后输出一条数据,处理前后数据的量是不变的。

DataStream<Integer> dataStream = //...
dataStream.map(new MapFunction<Integer, Integer>() {
    @Override
    public Integer map(Integer value) throws Exception {
        return 2 * value;
    }
});

这是官网给出的例子。从中我们可以看出map中接收一个MapFunction对象。MapFunction两个泛型分别是输入和输出数据类型。

flatMap

DataStream—>DataStream:这个算子可以接收一条数据,输出一条或多条数据甚至不输出数据。flatMap与map的不同点在于flatMap支持拆分数据。

dataStream.flatMap(new FlatMapFunction<String, String>() {
    @Override
    public void flatMap(String value, Collector<String> out)
        throws Exception {
        for(String word: value.split(" ")){
            out.collect(word);
        }
    }
});

与map类似,flatMap中接收一个FlatMapFunction对象。FlatMapFunction中的两个泛型分别是输入和输出数据类型。

filter

DataStream—>DataStream:是专门用于筛选数据的。它会将不满足条件的数据过滤掉。如下图所示:

在这里插入图片描述

dataStream.filter(new FilterFunction<Integer>() {
    @Override
    public boolean filter(Integer value) throws Exception {
        return value != 0;
    }
});

filter接收一个FilterFunction对象,该对象的filter方法会返回一个boolean类型的数据。如果返回的是false,这条数据就会被过滤掉。

keyBy

DataStream—>KeyedStream:逻辑地将一个流拆分成不相交的分区,每个分区包含具有相同key的元素,在内部以hash的形式实现的逻辑地将一个流拆分成不相交的分区,每个分区包含具有相同key的元素,在内部以hash的形式实现的。如下图所示:

在这里插入图片描述

keyBy有4种调用方式

//dataStream实例化为POJO对象后可以按字段分区
dataStream.keyBy("someKey");
//按指定下标的字段分区
dataStream.keyBy(0);
//以上两种方法已经不建议使用了
//通过实现一个KeySelector的接口来返回分组字段
dataStream.keyBy(new MyKey());
private static class MyKey implements KeySelector<Sensor, String> {
    public String getKey(Sensor sensor) throws Exception {
        return sensor.getId();
    }
}
//第四种调用方式还未搞明白,做个记录,后续补上

滚动聚合算子(Rolling Aggregation)

  • min()
  • max()
  • sum()
  • minBy()
  • maxBy()

这些算子可以针对KeyedStream的每一个支流做聚合。

reduce

KeyedStream → DataStream:一个分组数据流的聚合操作,合并当前的元素和上次聚合的结果,产生一个新的值,返回的流中包含每一次聚合的结果,而不是只返回最后一次聚合的最终结果。

DataStream<Sensor> dataStream = inputData.flatMap(new MySpliter())
    .keyBy(new GetMyKey())
    .reduce(new ReduceFunction<Sensor>() {
        public Sensor reduce(Sensor sensor, Sensor t1) throws Exception {
            return new 			               Sensor(sensor.getId(),sensor.getTimeStamp()+1,t1.getTemperature());
        }
    });

我们可以看到ReduceFunction的reduce方法包括两个参数,分别是上次聚合的结果和本次需要聚合的数据。

split和select

split与select是成对使用的。

split:

在这里插入图片描述

DataStream → SplitStream:根据某些特征把一个DataStream拆分成两个或者多个DataStream。

select:

在这里插入图片描述

SplitStream→DataStream:从一个SplitStream中获取一个或者多个DataStream。

举个栗子:传感器温度按照温度高低(以30度为界),拆分成两个数据流

SplitStream<Sensor> dataStream = inputData.flatMap(new MySpliter())
    .keyBy(new GetMyKey())
    .split(new OutputSelector<Sensor>() {
        public Iterable<String> select(Sensor sensor) {
            List<String> strList = new ArrayList<String>();
            if(sensor.getTemperature()>30){
                strList.add("heigh");
            }else{
                strList.add("low");
            }
            return strList;
        }
    });
dataStream.select("heigh");
dataStream.select("low");
dataStream.select("heigh","low");

值得注意的是:这两个算子是过时的。那么现在我们要实现上边提到的需求,怎么办呢?用SideOutput(侧输出流),这个咱们后边再讲。

connect和coMap、coFlatMap

connect和coMap、coFlatMap与split和select类似,也是成对出现的。

connect:

在这里插入图片描述

DataStream,DataStream → ConnectedStreams:连接两个保持他们类型的数据流,两个数据流被Connect之后,只是被放在了一个同一个流中,内部依然保持各自的数据和形式不发生任何变化,两个流相互独立。

coMap/coFlatMap

在这里插入图片描述

ConnectedStreams → DataStream:作用于ConnectedStreams上,功能与map和flatMap一样,对ConnectedStreams中的每一个Stream分别进行map和flatMap处理。

DataStream<Tuple2<String, Double>> worning = heigh.map(new MapFunction<Sensor, Tuple2<String, Double>>() {
    public Tuple2<String, Double> map(Sensor sensor) throws Exception {
        return new Tuple2<String, Double>(sensor.getId(), sensor.getTemperature());
    }
});

ConnectedStreams<Tuple2<String, Double>, Sensor> connect = worning.connect(low);

DataStream<Tuple3<String, Double, String>> map = connect.map(new CoMapFunction<Tuple2<String, Double>, Sensor, Tuple3<String, Double, String>>() {

    public Tuple3<String, Double, String> map1(Tuple2<String, Double> value) throws Exception {
        return new Tuple3<String, Double, String>(value.f0, value.f1, "worning");
    }

    public Tuple3<String, Double, String> map2(Sensor sensor) throws Exception {
        return new Tuple3<String, Double, String>(sensor.getId(), sensor.getTemperature(), "low");
    }
});

union

在这里插入图片描述

DataStream → DataStream:对两个或者两个以上的DataStream进行union操作,产生一个包含所有DataStream元素的新DataStream。

dataStream.union(otherStream1, otherStream2, ...);

union与connect的区别

  1. union之前的流类型必须是一样的;connect之前的流类型可以不一样,在coMap中再调整为统一的流类型输出。
  2. connect只能合并两条数据流;union可以合并多条数据流。

支持的数据类型

Flink流应用程序处理的是以数据对象表示的事件流。所以在Flink内部,我们需要能够处理这些对象。它们需要被序列化和反序列化,以便通过网络传送它们;或者从状态后端、检查点和保存点读取它们。为了有效地做到这一点,Flink需要明确知道应用程序所处理的数据类型。Flink使用类型信息的概念来表示数据类型,并为每个数据类型生成特定的序列化器、反序列化器和比较器。

Flink还具有一个类型提取系统,该系统分析函数的输入和返回类型,以自动获取类型信息,从而获得序列化器和反序列化器。但是,在某些情况下,例如lambda函数或泛型类型,需要显式地提供类型信息,才能使应用程序正常工作或提高其性能。

Flink支持Java和Scala中所有常见数据类型。使用最广泛的类型有以下几种:

  1. 基础数据类型
  2. Java和Scala元组(Tuples)
  3. Scala样例类(case)
  4. Java 简单对象(POJOs)
  5. 其他(Arrays,Lists,Enums,Maps等)
  6. Hadoop的Writable数据类型

注意:

  • 要使用Hadoop的Writable数据类型需要实现org.apache.hadoop.Writable接口。

  • Flink认为的Java POJOs需要满足以下条件:

    ​ 1. 该类是公有的 (public) 和独立的(没有非静态内部类)

    ​ 2. 该类拥有公有的无参构造器

    ​ 3. 类(以及所有超类)中的所有非静态、非 transient 字段都是公有的(非 final 的), 或者具有遵循 Java 4. bean 对于 getter 和 setter 命名规则的公有 getter 和 setter 方法。

Flink 的 TypeInformation 类

//创建TypeInformation类
//非泛型
TypeInformation<String> info = TypeInformation.of(String.class);
//泛型
TypeInformation<Tuple2<String, Double>> info = TypeInformation.of(new TypeHint<Tuple2<String, Double>>(){});

POJO类的序列化

PojoTypeInfo 为 POJO 中的所有字段创建序列化器。Flink 标准类型如 int、long、String 等由 Flink 序列化器处理。 对于所有其他类型,我们回退到 Kryo。

对于 Kryo 不能处理的类型,你可以要求 PojoTypeInfo 使用 Avro 对 POJO 进行序列化。

final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.getConfig().enableForceAvro();

类似的我们可以指定用Kryo进行POJO类的序列化

final ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
env.getConfig().enableForceKryo();

使用工厂类方法定义类型信息

//带注解的自定义类型
@TypeInfo(MyTupleTypeInfoFactory.class)
public class MyTuple<T0, T1> {
  public T0 myfield0;
  public T1 myfield1;
}

支持自定义类型信息的工厂:

public class MyTupleTypeInfoFactory extends TypeInfoFactory<MyTuple> {

  @Override
  public TypeInformation<MyTuple> createTypeInfo(Type t, Map<String, TypeInformation<?>> genericParameters) {
    return new MyTupleTypeInfo(genericParameters.get("T0"), genericParameters.get("T1"));
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值