flink有以下几类窗口:
滚动窗口长度固定,滑动间隔等于窗口长度,窗口元素之间没有交叠。
// tumbling event-time windows
input
.keyBy()
.window(TumblingEventTimeWindows.of(Time.seconds(5)))
.()
滑动窗口长度固定,窗口长度大于窗口滑动间隔,元素存在交叠。
// sliding event-time windows
input
.keyBy()
.window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)))
.()
全局窗口会将所有key相同的元素放到一个窗口中,默认该窗口永远都不会关闭(永远都不会触发),因为该窗口没有默认的窗口触发器Trigger,因此需要用户自定义Trigger。
input
.keyBy()
.window(GlobalWindows.create())
.()
通过计算元素时间间隔,如果间隔小于session gap,则会合并到一个窗口中;如果大于时间间隔,当前窗口关闭,后续的元素属于新的窗口。与滚动窗口和滑动窗口不同的是会话窗口没有固定的窗口大小,底层本质上做的是窗口合并。
// event-time session windows with static gap
input
.keyBy()
.window(EventTimeSessionWindows.withGap(Time.minutes(10)))
.()
Flink在做窗口计算的时候支持以下语义的window:Processing time
、Event time
、Ingestion time
Processing time:使用处理节点时间,计算窗口 ,默认
Event time:使用事件产生时间,计算窗口- 精确
Ingestion time:数据进入到Flink的时间,一般是通过SourceFunction指定时间
map可以理解为映射,对每个元素进行一定的变换后,映射为另一个元素
object MapOperator {
def main(args: Array[String]): Unit = {
//获取环境变量
val env = StreamExecutionEnvironment.getExecutionEnvironment
//准备数据,类型DataStreamSource
val dataStreamSource = env.fromElements(Tuple1.apply("flink")
,Tuple1.apply("spark")
,Tuple1.apply("hadoop"))
.map("hello"+_._1)
.print()
env.execute("flink map operator")
}
}
运行结果:
3> hello hadoop
2> hello spark
1> hello flink
flatmap可以理解为将元素摊平,每个元素可以变为0个、1个、或者多个元素。
env.fromElements(Tuple1.apply("flink jobmanger taskmanager")
,Tuple1.apply("spark streaming")
,Tuple1.apply("hadoop hdfs"))
.flatMap(_._1.split(" "))
.print()
运行结果:
hadoop
hdfs
spark
streaming
flink
jobmanger
taskmanager
filter是进行筛选。
env.fromElements(Tuple1.apply("flink jobmanger taskmanager")
,Tuple1.apply("spark streaming")
,Tuple1.apply("hadoop hdfs"))
.flatMap(_._1.split(" "))
.filter(_.equals("flink"))
.print()
运行结果:
flink
逻辑上将Stream根据指定的Key进行分区,是根据key的散列值进行分区的。
注:keyed state 必须要在keyby() 之后使用
env.fromElements(Tuple1.apply("flink jobmanger taskmanager")
, Tuple1.apply("flink streaming")
, Tuple1.apply("hadoop jobmanger"))
.flatMap(_._1.split(" "))
.map(data => {
(data, 1)
})
.keyBy(0)
.reduce((t1,t2)=>{(t1._1,t1._2+t2._2)})
.print().setParallelism(1)
运行结果:
(hadoop,1)
(flink,1)
(flink,2)
(jobmanger,1)
(jobmanger,2)
(taskmanager,1)
(streaming,1)
DataStream API 支持各种聚合, 这些函数可以应用于 KeyedStream 以获得 Aggregations 聚合
常用的方法有
min、minBy、max、minBy、sum
max 和 maxBy 之间的区别在于 max 返回流中的最大值,但 maxBy 返回具有最大值的键, min 和 minBy 同理
输入:
zs 001 1200
zs 001 1500
env.socketTextStream("localhost",8888)
.map(_.split("\\s+"))
.map(ts=>(ts(0),ts(1),ts(2).toDouble))
.keyBy(1)
.min(2)
.print()
运行结果:
1> (zs,001,1200.0)
1> (zs,001,1200.0)
reduce是归并操作,它可以将KeyedStream 转变为 DataStream;对每一组内的元素进行归并操作,即第一个和第二个归并,结果再与第三个归并…
env.fromElements(Tuple1.apply("flink jobmanger taskmanager")
, Tuple1.apply("flink streaming")
, Tuple1.apply("hadoop jobmanger"))
.flatMap(_._1.split(" "))
.map(data => {
(data, 1)
})
.keyBy(0)
.reduce((t1,t2)=>{(t1._1,t1._2+t2._2)})
.print().setParallelism(1)
运行结果:
(hadoop,1)
(flink,1)
(flink,2)
(jobmanger,1)
(jobmanger,2)
(taskmanager,1)
(streaming,1)
给定一个初始值,将各个元素逐个归并计算。它将KeyedStream转变为DataStream;指定一个开始的值,对每一组内的元素进行归并操作,即第一个和第二个归并,结果再与第三个归并…
env.fromElements(Tuple1.apply("flink jobmanger taskmanager")
, Tuple1.apply("flink streaming")
, Tuple1.apply("hadoop jobmanger"))
.flatMap(_._1.split(" "))
.map(data => {
(data, 1)
})
.keyBy(0)
.fold(("",11))((t1,t2)=>{(t2._1,t1._2+t2._2)})
.print().setParallelism(1)
运行结果:
(jobmanger,11)
(jobmanger,12)
(taskmanager,11)
(streaming,11)
(hadoop,11)
(flink,11)
(flink,12)
union可以将多个流合并到一个流中,以便对合并的流进行统一处理。是对多个流的水平拼接。
参与合并的流必须是同一种类型。
dataStream1.union(dataStream2, dataStream3, ...)
根据指定的Key将两个流进行关联。
//两个流进行join操作,是inner join,关联上的才能保留下来
DataStream result = stream1.join(stream2)
//关联条件
.where(t1->t1.getField(0)).equalTo(t2->t2.getField(0))
//每5秒一个滚动窗口
.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
//关联后输出
.apply((t1,t2)->t1.getField(1)+"|"+t2.getField(1))
;
将一个流拆分为多个流。
val lines = env.socketTextStream("localhost",8888)
val splitStream: SplitStream[String] = lines.split(line => {
if (line.contains("zs")) {
List("zs") //分支名称
} else {
List("ls") //分支名称
}
})
从拆分流中选择特定流,那么就得搭配使用 Select 算子
输入:
zs 001 1200
zs 001 1500
ls 002 1000
oo2 2000
val lines = env.socketTextStream("localhost",8888)
val splitStream: SplitStream[String] = lines.split(line => {
if (line.contains("zs")) {
List("zs") //分支名称
} else {
List("ls") //分支名称
}
})
splitStream.select("zs").print("zs")
splitStream.select("ls").print("ls")
运行结果:
zs:2> zs 001 1200
zs:3> zs 001 1500
ls:4> ls 002 1000
ls:1> oo2 2000
输入:
zs 001 1200
ls 002 1500
val lines = env.socketTextStream("localhost",8888)
val outTag: OutputTag[String] = new OutputTag[String]("zs")
// processFunction: ProcessFunction[T, R]
val result: DataStream[String] = lines.process(new ProcessFunction[String, String] {
override def processElement(value: String, ctx: ProcessFunction[String, String]#Context, out: Collector[String]): Unit = {
if (value.contains("zs")) {
ctx.output(outTag, value)
} else {
out.collect(value)
}
}
})
result.print("正常输出")
//获取侧输出流中的数据
result.getSideOutput(outTag).print("侧输出")
运行结果:
正常输出:2> ls 002 1000
侧输出:3> zs 001 1200
var env=StreamExecutionEnvironment.getExecutionEnvironment
env.socketTextStream("localhost",8888)
.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(0)
.map(new RichMapFunction[(String,Int),(String,Int)] {
var vs:ValueState[Int]=_
override def open(parameters: Configuration): Unit = {
val vsd=new ValueStateDescriptor[Int]("valueCount",createTypeInformation[Int])
vs=getRuntimeContext.getState[Int](vsd)
}
override def map(value: (String, Int)): (String, Int) = {
val histroyCount = vs.value()
val currentCount=histroyCount+value._2
vs.update(currentCount)
(value._1,currentCount)
}
}).print()
val prop = new Properties()
prop.setProperty("zookeeper.connect", ZOOKEEPER_HOST)
prop.setProperty("bootstrap.server", KAFKA_BROKER)
prop.setProperty("group.id", TRANSACTION_GROUP)
prop.setProperty("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
prop.setProperty("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer")
prop.setProperty("auto.offset.reset", "latest")
val lines=env.addSource(new FlinkKafkaConsumer("topic",new SimpleStringSchema(),properties))
lines.flatMap(_.split("\\s+"))
.map((_,1))
.keyBy(t=>t._1)
.sum(1)
.print()
如果使用SimpleStringSchema,只能获取value,如果想要获取更多信息,比如 key/value/partition/offset ,用户可以通过继承KafkaDeserializationSchema类自定义反序列化对象
import org.apache.flink.api.common.typeinfo.TypeInformation
import org.apache.flink.streaming.connectors.kafka.KafkaDeserializationSchema
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.apache.flink.streaming.api.scala._
class KafkaDeserializationSchema extends KafkaDeserializationSchema[(String,String)] {
override def isEndOfStream(nextElement: (String, String)): Boolean = {
false
}
override def deserialize(record: ConsumerRecord[Array[Byte], Array[Byte]]): (String, String) = {
var key=""
if(record.key()!=null && record.key().size!=0){
key=new String(record.key())
}
val value=new String(record.value())
(key,value)
}
//tuple元素类型
override def getProducedType: TypeInformation[(String, String)] = {
createTypeInformation[(String, String)]
}
}
如果Kafka存储的数据为json格式时,可以使用系统自带的一些支持json的Schema: