flink常用算子以及window

Flink 窗口种类以及常用算子

flink有以下几类窗口:

Tumbling Windows

滚动窗口长度固定,滑动间隔等于窗口长度,窗口元素之间没有交叠。

// tumbling event-time windows
input
    .keyBy(<key selector>)
    .window(TumblingEventTimeWindows.of(Time.seconds(5)))
    .<windowed transformation>(<window function>)

Sliding Windows

滑动窗口长度固定,窗口长度大于窗口滑动间隔,元素存在交叠。

// sliding event-time windows
input
    .keyBy(<key selector>)
    .window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)))
    .<windowed transformation>(<window function>)

Global Windows

全局窗口会将所有key相同的元素放到一个窗口中,默认该窗口永远都不会关闭(永远都不会触发),因为该窗口没有默认的窗口触发器Trigger,因此需要用户自定义Trigger。

input
    .keyBy(<key selector>)
    .window(GlobalWindows.create())
    .<windowed transformation>(<window function>)

Session Windows(MergerWindow)

通过计算元素时间间隔,如果间隔小于session gap,则会合并到一个窗口中;如果大于时间间隔,当前窗口关闭,后续的元素属于新的窗口。与滚动窗口和滑动窗口不同的是会话窗口没有固定的窗口大小,底层本质上做的是窗口合并。

// event-time session windows with static gap
input
    .keyBy(<key selector>)
    .window(EventTimeSessionWindows.withGap(Time.minutes(10)))
    .<windowed transformation>(<window function>)

Event Time

Flink在做窗口计算的时候支持以下语义的window:Processing timeEvent timeIngestion time

Processing time:使用处理节点时间,计算窗口 ,默认

Event time:使用事件产生时间,计算窗口- 精确

Ingestion time:数据进入到Flink的时间,一般是通过SourceFunction指定时间

常用算子

map

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

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

filter是进行筛选。

env.fromElements(Tuple1.apply("flink jobmanger taskmanager")
      ,Tuple1.apply("spark streaming")
      ,Tuple1.apply("hadoop hdfs"))
      .flatMap(_._1.split(" "))
      .filter(_.equals("flink"))
      .print()
运行结果:
flink

keyBy

逻辑上将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)

Aggregations

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

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)

fold

给定一个初始值,将各个元素逐个归并计算。它将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

union可以将多个流合并到一个流中,以便对合并的流进行统一处理。是对多个流的水平拼接。

参与合并的流必须是同一种类型。

dataStream1.union(dataStream2, dataStream3, ...)

join

根据指定的Key将两个流进行关联。

      //两个流进行join操作,是inner join,关联上的才能保留下来
        DataStream<String> 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))
                ;

split

将一个流拆分为多个流。

val lines = env.socketTextStream("localhost",8888)
    val splitStream: SplitStream[String] = lines.split(line => {
      if (line.contains("zs")) {
        List("zs") //分支名称
      } else {
        List("ls") //分支名称
      }
    })

Select

从拆分流中选择特定流,那么就得搭配使用 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

Side Out

输入:
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

ValueSate

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()

KafkaSource

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:

  • JsonNodeDeserializationSchema:要求value必须是json格式的字符串
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章