Flink DataStream API之Operators
官網位置:https://ci.apache.org/projects/flink/flink-docs-release-1.9/zh/dev/stream/operators/
Operators transform one or more DataStreams into a new DataStream. Programs can combine multiple transformations into sophisticated dataflow topologies.
算子可以將一個或多個DataStream轉化爲新的DataStream。程序可以將多種轉換組合成複雜的數據流拓撲。下面將介紹一下Flink DataStream API中一些常用的算子,注意:本文是以Scala代碼爲例,有些語法在Java裏不通用。
1 DataStream Transformations
官網以表格的形式列了一些轉換類的算子,有些可能不好理解,這裏以實際的代碼例子進行演示。
DataStream常用轉換如下圖所示:
1.1 map
類型轉換:
DataStream->DataStream
描述:
熟悉scala的同學,對於這個map應該很好理解,map操作是依次取DataStream中一個元素並生成另一個元素。
調用方式:
- 傳入一個繼承自MapFunction[T,R]的實例
def map[R: TypeInformation](mapper: MapFunction[T, R]): DataStream[R]
- 函數式調用,傳入一個參數類型爲T,返回值爲R的函數
def map[R: TypeInformation](fun: T => R): DataStream[R]
代碼演示:
先通過1到10的集合生成一個DataStream,並通過map操作,將其每個元素都乘以2,生成新的DataStream。
// create DataStream from collection
val data = env.fromCollection(1 to 10)
// 將每個元素*2
data.map(_ * 2).print("map")
輸出:
map:7> 10
map:4> 4
map:3> 2
map:2> 16
map:8> 12
map:6> 8
map:5> 6
map:1> 14
map:3> 18
map:4> 20
1.2 flatMap
類型轉換:
DataStream->DataStream
描述:
flatMap與map類似,只是它是依次取DataStream中的一個元素生成0個或多個元素。
調用方式:
它在scala裏提供了三種調用方式:
- 傳入一個繼承自FlatMapFunction[T, R]的類實例
def flatMap[R: TypeInformation](flatMapper: FlatMapFunction[T, R]): DataStream[R]
- 傳入參數爲T和Collector[R]無返回值的函數
def flatMap[R: TypeInformation](fun: (T, Collector[R]) => Unit): DataStream[R]
- 傳入參數類型爲T,返回值類型爲TraversableOnce(集合特質,常見集合都繼承該特質)的函數
def flatMap[R: TypeInformation](fun: T => TraversableOnce[R]): DataStream[R]
代碼演示:
先通過Array(1,2,3)和Array(4,5,6)兩個元素生成一個DataStream,並通過flatMap操作,將兩個數組展開
// create DataStream from elements
val data = env.fromElements(Array(1,2,3),Array(4,5,6))
// 將數據展開
data.flatMap(_.toList).print("flatMap")
結果:
flatMap:1> 4
flatMap:8> 1
flatMap:1> 5
flatMap:8> 2
flatMap:1> 6
flatMap:8> 3
1.3 filter
類型轉換:
DataStream->DataStream
描述:
按照條件過濾,得到滿足條件的元素生成新的DataStream
調用方式:
調用方式有兩種:
- 傳入一個繼承自FilterFunction[T]類實例
def filter(filter: FilterFunction[T]): DataStream[T]
- 傳入一個入參類型爲T,返回值類型爲Boolean的函數
def filter(fun: T => Boolean): DataStream[T]
代碼演示:
先通過1 到10的集合生成一個DataStream,並通過filter操作,過濾出奇數
// create DataStream from collection
val data = env.fromCollection(1 to 10)
// 輸出奇數
data.filter(_ % 2 == 1).print("filter")
輸出:
filter:3> 1
filter:5> 3
filter:7> 5
filter:1> 7
filter:3> 9
1.4 keyBy
類型轉換:
DataStream->KeyedStream
描述:
根據指定的Key選擇器,進行分區,生成KeyedStream
調用方式:
在scala裏key選擇器有四種指定方式:
- 對於tuple和array類型,我們可以指定一個或多個key的位置
def keyBy(fields: Int*): KeyedStream[T, JavaTuple]
- 指定一個或多個key的字段表達式
def keyBy(firstField: String, otherFields: String*): KeyedStream[T, JavaTuple]
- 傳入key選擇函數
def keyBy[K: TypeInformation](fun: T => K): KeyedStream[T, K]
- 實現key選擇器KeySelector,並傳入該實例
def keyBy[K: TypeInformation](fun: KeySelector[T, K]): KeyedStream[T, K]
代碼演示:
先通過集合生成一個DataStream,然後根據第一個元素進行KeyBy
// create DataStream from collection
val data = env.fromCollection(List(("device-1", 1), ("device-2", 2), ("device-1", 2)))
data.keyBy(_._1)
輸出:
keyed:3> (device-2,2)
keyed:1> (device-1,1)
keyed:1> (device-1,2)
1.5 reduce
類型轉換:
KeyedStream->DataStream
描述:
在KeyedStream上,將當前值以及上次計算結果應用到指定的reduce函數中得到新的值,依次計算
調用方式:
在scala裏reduce調用有兩種方式:
- 實現ReduceFunction[T]接口中的reduce方法,並傳入該實例
def reduce(reducer: ReduceFunction[T]): DataStream[T]
- 傳入參數類型爲T,T返回值爲T的函數
def reduce(fun: (T, T) => T): DataStream[T]
代碼演示:
將上面KeyBy之後生成的KeyedStream,應用reduce算子,進行累加
keyedStream.reduce((value1, value2) => {
(value1._1, value1._2 + value2._2)
}).print("reduce")
輸出:
reduce:1> (device-1,1)
reduce:3> (device-2,2)
reduce:1> (device-1,3)
1.6 fold(deprecated)
類型轉換:
KeyedStream->DataStream
描述:
在KeyedStream上,與reduce類似,只是多了一個初始值。
調用方式:
在scala裏fold調用有兩種方式:
- 實現FoldFunction[T,R]接口中的fold方法,並傳入該實例,另外還需要傳入初始化值。
def fold[R: TypeInformation](initialValue: R, folder: FoldFunction[T,R]):
DataStream[R]
- 柯里化傳參,第一個參數爲初始值,第二個參數爲函數表達式
def fold[R: TypeInformation](initialValue: R)(fun: (R,T) => R): DataStream[R]
代碼演示:
將上面KeyBy之後生成的KeyedStream,應用fold算子,進行字符串拼接
keyedStream.fold("start")((accumulator, value) => {
s"$accumulator-${value._1}-${value._2}"
}).print("fold")
輸出:
fold:1> start-device-1-1
fold:3> start-device-2-2
fold:1> start-device-1-1-device-1-2
1.7 aggregations
類型轉換:
KeyedStream->DataStream
描述:
在KeyedStream上,應用聚合方法,Flink提供內置的聚合方法有:sum、min、minBy、maxBy。這裏解釋一下min和minBy,max和maxBy之間的卻別。以max和maxBy爲例,max(position|field)只是返回指定的position或field的最大值,其它字段的值爲第一次出現的記錄值,而maxBy(position|field)則是返回position或field最大值所在的記錄。怎麼理解呢?假如streamData:DataStream[DeviceEvent]有兩條記錄 DeviceEvent(1,1,1)、 DeviceEvent(2,2,2),即
val streamData:DataStream[DeviceEvent]=env.fromElements(DeviceEvent(1,1,1),DeviceEvent(2,2,2))
我們通過按照第一個元素keyBy,然後按照第二個元素max
streamData.keyBy(0).max(1)
我們會得到結果:
1,1,1
1,2,1
如果我們同樣按照第一個元素keyBy,但是按照第二個元素maxBy
streamData.keyBy(0).maxBy(1)
將會得到如下結果:
1,1,1
2,2,2
min和minBy同理。
調用方式:
flink內置聚合方法在scala裏的調用方式有兩種:
- 傳入position參數
def sum(position: Int): DataStream[T]
- 傳入field參數
def sum(field: String): DataStream[T]
代碼演示:
演示這幾種聚合方法
// create DataStream from collection
val data: DataStream[(String, Int, String)] = env.fromCollection(List(("device-1", 3, "1-3"), ("device-1", 1, "1-1"), ("device-2", 2, "2-2"), ("device-1", 2, "1-2")))
val keyedStream = data.keyBy(0)
keyedStream.sum(1).print("sum")
keyedStream.min(1).print("min")
keyedStream.max(1).print("max")
keyedStream.minBy(1).print("minBy")
keyedStream.maxBy(1).print("maxBy")
輸出:
sum:3> (device-2,2,2-2)
max:3> (device-2,2,2-2)
min:1> (device-1,3,1-3)
maxBy:3> (device-2,2,2-2)
maxBy:1> (device-1,3,1-3)
minBy:1> (device-1,3,1-3)
sum:1> (device-1,3,1-3)
max:1> (device-1,3,1-3)
minBy:3> (device-2,2,2-2)
min:3> (device-2,2,2-2)
max:1> (device-1,3,1-3)
minBy:1> (device-1,1,1-1)
maxBy:1> (device-1,3,1-3)
max:1> (device-1,3,1-3)
min:1> (device-1,1,1-3)
minBy:1> (device-1,1,1-1)
sum:1> (device-1,4,1-3)
min:1> (device-1,1,1-3)
maxBy:1> (device-1,3,1-3)
sum:1> (device-1,6,1-3)
1.8 window
類型轉換:
KeyedStream->WindowedStream
描述:
在KeyedStream上的窗口裝換,Flink提供了四種窗口類型:Tumbling Windows(滾動窗口)、Sliding Windows(滑動窗口)、Session Windows(會話窗口)、Global Windows(全局窗口),滾動窗口和滑動窗口分爲基於時間的窗口和基於個數的窗口,而基於時間的窗口又分爲事件時間窗口和處理時間窗口。關於窗口的更多內容會在下一篇文章中記錄,這裏只做簡單演示。
調用方式:
window在scala裏的調用方式有五種:
- timeWindow(size: Time),傳入類型爲Time的窗口大小,Flink會根據指定的time characteristic來判斷是處理時間滾動窗口還是事件時間滾動窗口。
def timeWindow(size: Time): WindowedStream[T, K, TimeWindow]
- timeWindow(size: Time, slide: Time),傳入類型爲Time的窗口大小,以及類型爲Time的窗口滑動大小,Flink同樣會根據指定的time characteristic來判斷是處理時間滑動窗口還是事件時間滑動窗口。
def timeWindow(size: Time, slide: Time): WindowedStream[T, K, TimeWindow]
- countWindow(size: Long),傳入Long類型的窗口大小,Flink會應用爲基於個數的滾動窗口。
def countWindow(size: Long): WindowedStream[T, K, GlobalWindow]
- countWindow(size: Long, slide: Long),傳入Long類型的窗口大小,以及Long類型的窗口滑動帶下,生成基於個數的滑動窗口。
def countWindow(size: Long, slide: Long): WindowedStream[T, K, GlobalWindow]
- window,需要傳入WindowAssigner實例
def window[W <: Window](assigner: WindowAssigner[_ >: T, W]): WindowedStream[T, K, W]
代碼演示:
// create data from collection
val data = env.fromCollection(1 to 10)
data.countWindowAll(5)
1.9 windowAll
類型轉換:
DataStream->AllWindowedStream
描述:
在DataStream上的窗口裝換,與window類似,只是window應用在KeyedStream上,需要先進行KeyBy操作,windowAll可以直接應用到DataStream上。
調用方式:
window在scala裏的調用方式有五種:
- timeWindowAll(size: Time)傳入類型爲Time的窗口大小,Flink會根據指定的time characteristic來判斷是處理時間滾動窗口還是事件時間滾動窗口。
def timeWindowAll(size: Time): AllWindowedStream[T, TimeWindow]
- timeWindow(size: Time, slide: Time),傳入類型爲Time的窗口大小,以及類型爲Time的窗口滑動大小,Flink同樣會根據指定的time characteristic來判斷是處理時間滑動窗口還是事件時間滑動窗口。
def timeWindowAll(size: Time, slide: Time): AllWindowedStream[T, TimeWindow]
- countWindow(size: Long),傳入Long類型的窗口大小,Flink會應用爲基於個數的滾動窗口。
def countWindowAll(size: Long, slide: Long): AllWindowedStream[T, GlobalWindow]
- countWindow(size: Long, slide: Long),傳入Long類型的窗口大小,以及Long類型的窗口滑動帶下,生成基於個數的滑動窗口。
def countWindowAll(size: Long): AllWindowedStream[T, GlobalWindow]
- windowAll,需要傳入WindowAssigner實例
windowAll[W <: Window](assigner: WindowAssigner[_ >: T, W]): AllWindowedStream[T, W]
代碼演示:
// create data from collection
val data = env.fromCollection((1 to 10).map(x => (x % 2, x)))
// get KeyedDataStream
val keyedStream = data.keyBy(0)
val windowStream = keyedStream.countWindow(5)
輸出:
windowStream.sum(1).print("countWindow")
countWindow:6> (1,25)
countWindow:6> (0,30)
1.10 WindowedStream apply
類型轉換:
WindowedStream->DataStream
描述:
在WindowedStream上應用WindowFunction,當窗口被觸發時調用指定的WindowFunction。
調用方式:
apply在scala裏的調用方式有兩種(其它預聚合的調用方式已廢棄):
- 繼承WindowFunction實現apply方法,並傳入該實例
def apply[R: TypeInformation](
function: WindowFunction[T, R, K, W]): DataStream[R]
- 傳入匿名函數
def apply[R: TypeInformation](
function: (K, W, Iterable[T], Collector[R]) => Unit): DataStream[R]
代碼演示:
在上面生成的windowStream調用apply
val dataStream = windowedStream.apply((key, window, input, out: Collector[(Int, String, Int)]) => {
var sum = 0
input.foreach(element => {
sum += element._2
})
out.collect(key.getField(0), window.toString, sum)
})
dataStream.print("simple apply")
輸出:
simple apply:6> (1,GlobalWindow,25)
simple apply:6> (0,GlobalWindow,30)
1.11 WindowedStream reduce
類型轉換:
WindowedStream->DataStream
描述:
在WindowedStream上應用ReduceFunction,與DataStream上的ReduceFunction相同,當窗口被觸發時調用指定的ReduceFunction。
調用方式:
reduce在scala裏的調用方式有六種,其中包括幾種預聚合的方式調用:
- 繼承ReduceFunction實現reduce方法,並傳入該實例
def reduce(function: ReduceFunction[T]): DataStream[T]
- 傳入匿名函數
def reduce(function: (T, T) => T): DataStream[T]
- 傳入ReduceFunction[T]作爲預聚合器,當數據到達時調用,傳入WindowFunction作爲窗口函數,當觸發窗口計算的時候調用。
def reduce[R: TypeInformation](
preAggregator: ReduceFunction[T],
function: WindowFunction[T, R, K, W]): DataStream[R]
- 傳入匿名函數作爲預聚合器,當數據到達時調用,傳入WindowFunction作爲窗口函數,當觸發窗口計算的時候調用。
def reduce[R: TypeInformation](
preAggregator: (T, T) => T,
windowFunction: (K, W, Iterable[T], Collector[R]) => Unit): DataStream[R]
- 傳入匿名函數作爲預聚合器,當數據到達時調用,傳入ProcessWindowFunction作爲窗口函數,當觸發窗口計算的時候調用。
def reduce[R: TypeInformation](
preAggregator: (T, T) => T,
function: ProcessWindowFunction[T, R, K, W]): DataStream[R]
- 傳入ReduceFunction[T]作爲預聚合器,當數據到達時調用,傳入ProcessWindowFunction作爲窗口函數,當觸發窗口計算的時候調用。
def reduce[R: TypeInformation](
preAggregator: ReduceFunction[T],
function: ProcessWindowFunction[T, R, K, W]): DataStream[R]
代碼演示:
在上面生成的windowStream調用reduce
windowedStream.reduce(new ReduceSum).print("function reduce")
windowedStream.reduce((value1, value2) => (value2._1, value1._2 + value2._2)).print("simple reduce")
輸出:
simple reduce:6> (1,25)
function reduce:6> (1,25)
simple reduce:6> (0,30)
function reduce:6> (0,30)
1.11 WindowedStream fold(deprecated)
deprecated
1.12 WindowedStream aggregations
類型轉換:
WindowedStream->DataStream
描述:
WindowedStream上的聚合函數,與DataStream上的相同,也有sum、min、minBy、max、maxBy,參考DataStream上的aggregations。
調用方式:
參考DataStream上的aggregations。
代碼演示:
windowedStream.sum(0).print("sum")
windowedStream.min(0).print("min")
windowedStream.max(0).print("max")
windowedStream.minBy(0).print("minBy")
windowedStream.maxBy(0).print("maxBy")
輸出:
maxBy:6> (1,1)
minBy:6> (1,1)
max:6> (1,1)
sum:6> (5,1)
min:6> (1,1)
maxBy:6> (0,2)
minBy:6> (0,2)
max:6> (0,2)
sum:6> (0,2)
min:6> (0,2)
1.13 union
類型轉換:
DataStream[T],DataStream[T],*->DataStream[T]
描述:
將兩個或更多的流進行合併,要求數據類的類型是一直的。
調用方式:
該算子調用方式只有一種:
- 傳入不定長的DataStream[T]
def union(dataStreams: DataStream[T]*): DataStream[T]
代碼演示:
先通過1 到10的集合生成一個DataStream,並通過filter操作,過濾出奇數
val stream1 = env.fromCollection(1 to 10)
val stream2 = env.fromCollection(101 to 110)
stream1.union(stream2).print("union")
輸出:
union:4> 1
union:7> 101
union:4> 9
union:2> 104
union:3> 105
union:5> 107
union:5> 2
union:1> 103
union:6> 108
union:1> 6
union:6> 3
union:8> 102
union:5> 10
union:2> 7
union:4> 106
union:3> 8
union:7> 109
union:8> 110
union:7> 4
union:8> 5
1.13 join
類型轉換:
DataStream[T],DataStream[T2]->JoinedStreams[T, T2]
描述:
將兩個按照指定的條件流進行join,兩個流的數據類型可以不同,需要與窗口函數結合使用。
調用方式:
該算子調用方式只有一種:
- 傳入需要Join的DataStream[T2]
def join[T2](otherStream: DataStream[T2]): JoinedStreams[T, T2]
代碼演示:
定義兩個流,一個是從集合中創建,另一個從socket中創建。
val stream1 = env.fromCollection(List(("device-1", 1), ("device-2", 2)))
val stream2 = env.socketTextStream("localhost", 9090).map(x => {
val token = x.split(" ")
(token(0), token(1))
})
val joinedStream: JoinedStreams[(String, Int), (String, String)]#Where[String]#EqualTo = stream1.join(stream2).where(_._1).equalTo(_._1)
joinedStream.window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
.apply((first, second) => (first._1, second._2, first._2)).print("join")
使用nc -lk 9090監聽9090端口,啓動程序後,發送數據:
輸出:
union:4> 1
union:7> 101
union:4> 9
union:2> 104
union:3> 105
union:5> 107
union:5> 2
union:1> 103
union:6> 108
union:1> 6
union:6> 3
union:8> 102
union:5> 10
union:2> 7
union:4> 106
union:3> 8
union:7> 109
union:8> 110
union:7> 4
union:8> 5