书接上回。
3.Transform 转换算子【代码示例统一放在最后边】
3.1 map
val streamMap = stream.map { x => x * 2 }
3.2 flatMap
flatMap 的函数签名:def flatMap[A,B](as: List[A])(f: A ⇒ List[B]): List[B]
例如: flatMap(List(1,2,3))(i ⇒ List(i,i))
结果是 List(1,1,2,2,3,3),
而 List("a b", "c d").flatMap(line ⇒ line.split(" "))
结果是 List(a, b, c, d)。
val streamFlatMap = stream.flatMap{ x => x.split(" ") }
3.3 Filter
val streamFilter = stream.filter{ x => x == 1 }
3.4 KeyBy
DataStream → KeyedStream:逻辑地将一个流拆分成不相交的分区,每个分区包含具有相同 key 的元素,在内部以 hash 的形式实现的。
3.5 滚动聚合算子(Rolling Aggregation)
这些算子可以针对 KeyedStream 的每一个支流做聚合。
- sum()
- min()
- max()
- minBy()
- maxBy()
3.6 Reduce
KeyedStream → DataStream:一个分组数据流的聚合操作,合并当前的元素和上次聚合的结果,产生一个新的值,返回的流中包含每一次聚合的结果,而不是只返回最后一次聚合的最终结果。
3.7 Split和Select【注:1.13版本已竟没有该函数,推荐使用侧输出】
Split:
DataStream → SplitStream:根据某些特征把一个 DataStream 拆分成两个或者多个 DataStream。
Select:
SplitStream→DataStream:从一个 SplitStream 中获取一个或者多个DataStream。
针对同一条流多次独立调用.filter()方法进行筛选,就可以得到拆分之后的流,它将原始数据流 stream 复制三份,然后对每一份分别做筛选;这明显是不够高效。
早期的版本中,DataStream API 中提供了一个.split()方法,基本思路是按照筛选条件,给数据分类“盖戳”;然后基于这条盖戳之后的流,分别拣选想要的“戳”就可以得到拆分后的流。因为只是“盖戳”拣选,所以无法对数据进行转换,分流后的数据类型必须跟原始流保持一致。这就极大地限制了分流操作的应用场景。
推荐使用使用侧输出流。
Flink的Side Output侧输出流的作用在于将主数据分割成多个不同的侧输出流。侧输出结果流的数据类型不需要与主数据流的类型一致,不同侧输出流的类型也可以不同。
在使用侧输出的时候需要先定义一个OutputTag,来标识 Side Output,代表这个 Tag 是要收集哪种类型的数据。
3.8 Connect和CoMap
DataStream,DataStream → ConnectedStreams:连接两个保持他们类型的数据流,两个数据流被 Connect 之后,只是被放在了一个同一个流中,内部依然保持各自的数据和形式不发生任何变化,两个流相互独立。
coMap,CoFlatMap
ConnectedStreams → DataStream:作用于 ConnectedStreams 上,功能与 map和 flatMap 一样,对 ConnectedStreams 中的每一个 Stream 分别进行 map 和 flatMap处理。
3.9 Union
DataStream → DataStream:对两个或者两个以上的 DataStream 进行 union 操作,产生一个包含所有 DataStream 元素的新 DataStream。
Connect与Union区别
1.Union 之前两个流的类型必须是一样,Connect 可以不一样,在之后的 coMap中再去调整成为一样的。
2.Connect 只能操作两个流,Union 可以操作多个。
代码:
package com.zhen.flink.api import org.apache.flink.api.common.functions.ReduceFunction import org.apache.flink.streaming.api.functions.ProcessFunction import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment import org.apache.flink.streaming.api.scala._ import org.apache.flink.util.Collector /** * @Author FengZhen * @Date 6/7/22 4:25 PM * @Description TODO */ object TransformTest { lazy val HIGH_TEMP: OutputTag[SensorReading] = new OutputTag[SensorReading]("高温数据") lazy val LOW_TEMP: OutputTag[SensorReading] = new OutputTag[SensorReading]("低温数据") def main(args: Array[String]): Unit = { val env = StreamExecutionEnvironment.getExecutionEnvironment env.setParallelism(1) // 0.读取数据 val filePath = "/Users/FengZhen/Desktop/accumulate/0_project/flink_learn/src/main/resources/data/sensor.txt" val inputStream = env.readTextFile(filePath) // 1.先转换成样例数据 val dataStream: DataStream[SensorReading] = inputStream .map( data => { val arr = data.split(",") SensorReading(arr(0), arr(1).toLong, arr(2).toDouble) } ) // 2.分组聚合, 输出每个传感器当前最小值 val aggStream = dataStream .keyBy("id") //根据ID进行分组 //.min("temperature") //输出中只temperature字段有变化,其它字段无变化 .minBy("temperature") //使用minBy可以解决上述其它字段不变的问题 // aggStream.print() // 3.需要输出当前最小的温度值,以及最近的时间戳,要用reduce val resultStream = dataStream .keyBy("id") //根据ID进行分组 // .reduce( // (curState, newData) => { // //假定数据是按timestamp增续进来的 // SensorReading( // curState.id, // newData.timestamp, // curState.temperature.min(newData.temperature) // ) // } // ) .reduce(new MyReduceFunction) // resultStream.print() // 4.多流转换操作 // 4.1 分流,将传感器温度数据分成低温、高温两条流 val processStream = dataStream.process(new MySplitProcessFunction) val highStream = processStream.getSideOutput(HIGH_TEMP) val lowStream = processStream.getSideOutput(LOW_TEMP) highStream.print("high:") lowStream.print("low:") // 4.2 合流,connect val warningStream = highStream.map( data => (data.id, data.temperature) ) val connectedStreams = warningStream.connect(lowStream) // 用coMap对数据进行分别处理 val coMapResultStream = connectedStreams .map( warningData => (warningData._1, warningData._2, "warning"), lowTempData => (lowTempData.id, "healthy") ) coMapResultStream.print("coMap:") //4.3 Union合流 val unionStream = highStream.union(lowStream) unionStream.print("union") env.execute("transform test") } class MyReduceFunction extends ReduceFunction[SensorReading] { override def reduce(value1: SensorReading, value2: SensorReading): SensorReading = { SensorReading( value1.id, value2.timestamp, value1.temperature.min(value2.temperature) ) } } class MySplitProcessFunction extends ProcessFunction[SensorReading, SensorReading] { override def processElement(value: SensorReading, ctx: ProcessFunction[SensorReading, SensorReading]#Context, out: Collector[SensorReading]): Unit = { if(value.temperature > 30){ ctx.output(HIGH_TEMP, value) }else if(value.temperature <= 30){ ctx.output(LOW_TEMP, value) }else{ out.collect(value) } } } }