原文链接:https://www.toutiao.com/i6859612664646238724/
本文主要从以下几个方面介绍Flink的流处理API——Transform
一、map
二、flatmap
三、Filter
四、KeyBy
五、滚动聚合算子(Rolling Aggregation)
六、Reduce
七、Split和Select
八、Connect和CoMap
九、Union
数据处理的过程基本可以分为三个阶段分别是,数据从来哪里,做什么业务逻辑,落地到哪里去。
这三部分在Flink中分别被称为Source、Transform和Sink
其中Source可以看这篇文章:Flink流处理API——Source
版本:
scala:2.11.12
Flink:1.7.2
pom.xml依赖部分(log日志的依赖一定要加上,否则当Flink从Kafka0.8中读取数据报Failed to instantiate SLF4J LoggerFactory Reported exception)
<dependencies>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-scala_2.11</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-streaming-scala_2.11</artifactId>
<version>1.7.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.flink</groupId>
<artifactId>flink-clients_2.11</artifactId>
<version>1.7.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.22</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.22</version>
</dependency>
</dependencies>
数据源:(Source)部分
package xxx
import org.apache.flink.api.common.functions.FilterFunction
import org.apache.flink.streaming.api.scala._
// 样例类,传感器ID,时间戳,温度
case class SensorReading(id: String, timestamo: Long, temperature: Double){
override def toString: String = {
id+":"+ timestamo.toString + "," + temperature
}
}
object TransformAPIDemo {
def main(args: Array[String]): Unit = {
val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
val value: DataStream[String] = environment.readTextFile("src\\main\\resources\\sensor.txt")
// 这里Transform操作
environment.execute()
}
}
接下来以对value的操作进行说明各个Transform算子的作用
一、map
map是左转换操作,将DataStream[A]的数据转换成DataStream[B]的数据。
// map
val maped: DataStream[SensorReading] = value.map(line => {
val fildes: Array[String] = line.split(",") // 这里的split是scala的split方法
SensorReading(fildes(0).trim, fildes(1).trim.toLong, fildes(2).trim.toDouble)
})
二、flatmap
flatmap和map的功能类似,都是将DataStream[A]的数据转换成DataStream[B]的数据。所不用的是,flatmap打散的效果更彻底,会将集合中的数据全部拿出后打算,不保留集合原状。
List("a b", "c d").flatMap(line ⇒ line.split(" "))
结果是 List(a, b, c, d)。
三、Filter
fliter将是DataStream[A]的数据进行过滤,该算子需要传入满足过滤的条件:
// fliterval
fliter: DataStream[SensorReading] = maped.filter(sensor => {
sensor.id.endsWith("0")
})
四、KeyBy
将DataStream 的数据转换成KeyedStream的数据:
逻辑地将一个流拆分成不相交的分区,每个分区包含具有相同 key 的元素, 在内部以 hash 的形式实现的。
KeyBy是后续一些聚合算子、开窗算子的前提。
五、滚动聚合算子(Rolling Aggregation)
聚合算子可以针对 KeyedStream 的每一个支流做聚合。也就是前面经过KeyBy操作后的数据流。
常用的聚合算子有:
sum()
min()
max()
minBy()
maxBy()
例如,求每个传感器的最低温度如下:
// kayBy、sum(括号的参数表示按照第几个字段进行分流、聚合等)
val sumed: DataStream[SensorReading] = maped.keyBy(0).min(2)
需要注意的是,每来一条数据都会计算一次min值(聚合一次),并返回。
经过聚合后,KeyedStream又回到DataStream类型。但是这几个聚合算子往往不能满足实际业务需求,就需要功能更强大的reduce聚合算子。
六、Reduce
KeyedStream → DataStream:一个分组数据流的聚合操作,合并当前的元素和上次聚合的结果,产生一个新的值,返回的流中包含每一次聚合的结果,而不是只返回最后一次聚合的最终结果。
用reduce同样可以实现每个传感器最低温度
// keyBy、reduce (DataStream要先经过KeyBy形成KeyedStream后才能调用reduce算子)
val reduced: DataStream[SensorReading] = maped.keyBy(0).reduce((x, y) => { // x:之前聚合的结果, y:本次数据
SensorReading(x.id, y.timestamo, x.temperature.min(y.temperature))
})
七、Split和Select
Split算子是将DataStream类型的数转化为SplitStream类型的数据
根据某些特征把一个 DataStream 拆分成两个或者多个 DataStream。
Select算子是将SplitStream类型的数据在转化为DataStream类型的数据:
从一个 SplitStream 中获取一个或者多个 DataStream。
Split和Select往往配合使用
// split, select
// 在split传入分割条件,设置tag,在select中选择tag
val splited: SplitStream[SensorReading] = maped.split(data => {
if (data.temperature > 30) Seq("High")
else Seq("Low")
})
val highStream: DataStream[SensorReading] = splited.select("High")
val lowStream: DataStream[SensorReading] = splited.select("Low")
val allStream: DataStream[SensorReading] = splited.select("High", "Low")
八、Connect和CoMap
DataStream,DataStream → ConnectedStreams:连接两个保持他们类型的数据流,两个数据流被 Connect 之后,只是被放在了一个同一个流中,内部依然保持各自的数据和形式不发生任何变化,两个流相互独立。
ConnectedStreams → DataStream:作用于 ConnectedStreams 上,功能与 map和 flatMap 一样,对 ConnectedStreams 中的每一个 Stream 分别进行 map 和 flatMap处理。
// 让highStream2变成元祖类型,而lowStream是SensorReading类型
val highStream2: DataStream[(String, Double)] = highStream.map(sensor => {
(sensor.id, sensor.temperature)
})
// connect,CoMap (支持不同类型的合并)
val highAndLowStream: ConnectedStreams[(String,Double), SensorReading] = highStream2.connect(lowStream)
val coMap: DataStream[Product] = highAndLowStream.map(
highData => (highData, "high"),
lowData => (lowData)
)
coMap中需要对不同的数据流指定不同的操作流程。
九、Union
DataStream → DataStream:对两个或者两个以上的 DataStream 进行 union 操作,产生一个包含所有 DataStream 元素的新 DataStream。
// union
val highUnionLowStream: DataStream[SensorReading] = highStream.union(lowStream)
Connect 与 Union 区别:
1. Union 之前两个流的类型必须是一样, Connect 可以不一样,在之后的 coMap中再去调整成为一样的。
2. Connect 只能操作两个流, Union 可以操作多个。