Operators
操作算子将一个或多个数据流转换为新的数据流。程序可以将多个转换组合成复杂的数据流拓扑。
本节将介绍基本的转换、应用这些转换后的有效物理分区以及对Flink操作符链接的深入了解。
DataStream Transformations
1.DataStream → DataStream
- map
获取一个元素并生成一个元素。例如:一个映射函数,使输入流的值加倍。
dataStream.map { x => x * 2 }
- flatmap
获取一个元素并生成零个、一个或多个元素。例如:将句子分割成单词。
dataStream.flatMap { str => str.split(" ") }
- filter
对每个元素求布尔函数的值,并保留函数返回true的元素。例如:过滤出零值的元素。
dataStream.filter { _ != 0 }
2.DataStream* → DataStream
- union
两个或多个数据流的并集,创建包含来自所有流的所有元素的新流。注意:如果您将一个数据流与它自己相结合,您将得到结果流中的每个元素两次。
dataStream.union(otherStream1, otherStream2, ...)
3_0.DataStream,DataStream → ConnectedStreams
- connect
someStream : DataStream[Int] = ...
otherStream : DataStream[String] = ...
val connectedStreams = someStream.connect(otherStream)
3_1.ConnectedStreams → DataStream
- CoMap,CoFlatMap
类似dataStream中的map,flatmap
connectedStreams.map(
(_ : Int) => true,
(_ : String) => false
)
connectedStreams.flatMap(
(_ : Int) => true,
(_ : String) => false
)
val env = StreamExecutionEnvironment.getExecutionEnvironment
val text1 = env.socketTextStream("CentOS", 9999)
val text2 = env.socketTextStream("CentOS", 8888)
text1.connect(text2)
.flatMap((line:String)=>line.split("\\s+"),(line:String)=>line.split("\\s+"))
.map((_,1))
.keyBy(0)
.sum(1)
.print("总数")
env.execute("Stream WordCount")
4_0.DataStream → SplitStream
- split
根据某些标准将流分成两个或多个流。
val split = someDataStream.split(
(num: Int) =>
(num % 2) match {
case 0 => List("even")
case 1 => List("odd")
}
)
4_1.SplitStream → DataStream
- select
从拆分流中选择一个或多个流。
val even = split select "even"
val odd = split select "odd"
val all = split.select("even","odd")
val env = StreamExecutionEnvironment.getExecutionEnvironment
val text1 = env.socketTextStream("CentOS", 9999)
var splitStream= text1.split(line=> {
if(line.contains("error")){
List("error")
} else{
List("info")
}
})
splitStream.select("error").printToErr("错误")
splitStream.select("info").print("信息")
splitStream.select("error","info").print("All")
env.execute("Stream WordCount")
5_0.DataStream → KeyedStream
- keyBy
从逻辑上将流划分为互不连接的分区。所有具有相同键的记录都被分配到相同的分区。在内部,keyBy()是通过哈希分区实现的。有不同的方法来指定键。
这个转换返回一个KeyedStream,它是使用key state所必需的。
dataStream.keyBy("someKey") // Key by field "someKey"--按照字段名分key
dataStream.keyBy(0) // Key by the first element of a Tuple--按照元组的第一个元素分key
Note:下列情况不能作为key:
1.它是一个POJO类型,但不覆盖hashCode()方法,并且依赖于Object.hashCode()实现。
2.它是任何类型的数组。
5_1.KeyedStream → DataStream
- reduce
keyedStream上的“滚动”减少。将当前元素与最后一个减少的值组合在一起,并发出新的值。
创建部分和流的reduce函数:
keyedStream.reduce(_ + _)
val env = StreamExecutionEnvironment.getExecutionEnvironment
val lines = env.socketTextStream("CentOS", 9999)
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy("_1")
.reduce((v1,v2)=>(v1._1,v1._2+v2._2))
.print()
env.execute("Stream WordCount")
- flod
具有初始值的keyedStream上的“滚动”折叠。将当前元素与最后一个折叠值组合并发出新值。
当作用于序列(1,2,3,4,5)时,发出序列“start-1”、“start-1-2”、“start-1-2-3”、“start-1- 2-3-4”、…
val result: DataStream[String] =
keyedStream.fold("start")((str, i) => { str + "-" + i })
val env = StreamExecutionEnvironment.getExecutionEnvironment
val lines = env.socketTextStream("CentOS", 9999)
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy("_1")
.fold((null:String,0:Int))((z,v)=>(v._1,v._2+z._2))
.print()
env.execute("Stream WordCount")
- aggregation
在键控数据流上滚动聚合。min和minBy之间的区别是,min返回最小值,而minBy返回该字段中具有最小值的元素(max和maxBy也是如此)。
keyedStream.sum(0)
keyedStream.sum("key")
keyedStream.min(0)
keyedStream.min("key")
keyedStream.max(0)
keyedStream.max("key")
keyedStream.minBy(0)
keyedStream.minBy("key")
keyedStream.maxBy(0)
keyedStream.maxBy("key")
val env = StreamExecutionEnvironment.getExecutionEnvironment
//zhangsan 研发部 1000
//lisi 研发部 ww 销售部 90005000
//ww 销售部 9000
val lines = env.socketTextStream("CentOS", 9999)
lines.map(line=>line.split(" "))
.map(ts=>Emp(ts(0),ts(1),ts(2).toDouble))
.keyBy("dept")
.maxBy("salary")//Emp(lisi,研发部,5000.0)
.print()
env.execute("Stream WordCount")
如果使用max,则返回的是Emp(zhangsan,研发部,5000.0)
Physical partitioning
Flink还通过以下函数提供了对转换后的流分区的底层控制(如果需要的话)。
- 1.Custom partitioning
使用用户定义的分区程序为每个元素选择目标任务。
dataStream.partitionCustom(partitioner, "someKey")
dataStream.partitionCustom(partitioner, 0)
哈希分区:
val env = StreamExecutionEnvironment.getExecutionEnvironment
env.socketTextStream("CentOS", 9999)
.map((_,1))
.partitionCustom(new Partitioner[String] {
override def partition(key: String, numPartitions: Int): Int = {
key.hashCode & Integer.MAX_VALUE % numPartitions
}
},_._1)
.print()
.setParallelism(4)
println(env.getExecutionPlan)
env.execute("Stream WordCount")
- 2.Random partitioning
根据均匀分布随机划分元素。
dataStream.shuffle()
- 3.Rebalancing (Round-robin partitioning)
分区元素循环,为每个分区创建相等的负载。对于存在数据倾斜的情况下的性能优化非常有用。
dataStream.rebalance()
- 4.Rescaling
和Roundrobin Partitioning⼀样,Rescaling Partitioning也是⼀种通过循环的⽅式进⾏数据重平衡的分区策
略。但是不同的是,当使⽤Roundrobin Partitioning时,数据会全局性地通过⽹络介质传输到其他的节点
完成数据的重新平衡,⽽Rescaling Partitioning仅仅会对上下游继承的算⼦数据进⾏重平衡,具体的分区
主要根据上下游算⼦的并⾏度决定。例如上游算⼦的并发度为2,下游算⼦的并发度为4,就会发⽣上游
算⼦中⼀个分区的数据按照同等⽐例将数据路由在下游的固定的两个分区中,另外⼀个分区同理路由到
下游两个分区中。
dataStream.rescale()
- 5.Broadcasting
向每个分区广播元素。
dataStream.broadcast
Task chaining and resource groups
链接两个后续转换意味着将它们放在同一个线程中以获得更好的性能。如果可能的话,默认的Flink操作符是chain(例如,两个后续的map转换)。如果需要,API提供了细粒度的链接控制:
如果希望在整个作业中禁用chain,请使用StreamExecutionEnvironment.disableOperatorChaining()。对于更细粒度的控制,可以使用以下功能。注意,这些函数只能在DataStream转换之后使用,因为它们引用的是前面的转换。例如,您可以使用someStream.map(…). startnewchain(),但是您不能使用someStream.startNewChain()。
资源组是Flink中的一个插槽,如果需要,您可以手动将操作符隔离在不同的槽中。
- Start new chain
开始一个新的chain,从这个操作符开始。两个映射器将被链接,而筛选器将不会被链接到第一个映射器。
someStream.filter(...).map(...).startNewChain().map(...)
将第⼀map算⼦和filter算⼦进⾏隔离
- Disable chaining
所有操作符禁⽌和map操作符进⾏chain
someStream.map(...).disableChaining()
- Set slot sharing group
设置一个操作的槽共享组。Flink将具有相同槽共享组的操作放入相同的槽中,而将不具有槽共享组的操作保留在其他槽中。这可以用来隔离插槽。下游的操作符会⾃动继承上游资源组。默认情况下,所有的输⼊算⼦的资源组的名字是default
,因此当⽤户不对程序进⾏资源划分的情况下,⼀个job所需的资源slot,就等于最⼤并⾏度的Task。可以通过调用slotSharingGroup(“default”)
显式地将操作放入该组。
someStream.filter(...).slotSharingGroup("name")