Flink_DataStream

Flink_DataStream

一、基本算子的使用

2.1、Map

  • Map[DataStream -> DataStream]
  • 调用用户定义的MapFunction对DataStream数据进行处理,形成新的DataStream
  • 其中数据格式可能会发生变化,常用作对数据集内数据的清洗和转换。
	 //输入:a 
	  DataStreamSource<String> dataSource = env.socketTextStream("192.168.134.6", 9898);
       dataSource.map(new MapFunction<String, Tuple2<String,Integer>>() {
           @Override
           public Tuple2<String, Integer> map(String s) throws Exception {
               Tuple2<String, Integer> Tuple2 = new Tuple2<String, Integer>();
               Tuple2.setField(s,0);
               Tuple2.setField(1,1);
               return Tuple2;
           }
       }).print();	//(a ,1)

	// zhansan 苹果 4.5 2
        // lisi 机械键盘 800 1
        // zhansan 橘子 2.5 2
        DataStreamSource<String> dataSource = env.socketTextStream("192.168.134.6", 9899);
        dataSource.map(new MapFunction<String, Tuple3<String,String,Double>>() {
            @Override
            public Tuple3<String, String, Double> map(String s) throws Exception {
                String[] split = s.split("\\s");
                return new Tuple3<String,String,Double>(split[0],split[1],Double.parseDouble(split[2]) *Double.parseDouble(split[3]));
            }
        }).keyBy(0).sum(2).print();
        
        env.execute("test_transform_job");

		8> (zhansan,苹果,9.0)
		8> (zhansan,苹果,18.0)
		(zhansan,苹果,23.0)

富函数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.2、FlatMap

  • 处理输入一个元素产生一个或者多个元素的计算场景。
  • FlatMap[DataStream -> DataStream]
 //2、数据源source  输入:aa bb a
        DataStreamSource<String> dataSource = env.socketTextStream("192.168.134.6", 9898);
        dataSource.flatMap(new FlatMapFunction<String, Tuple2<String,Integer>>() {
            @Override
            public void flatMap(String s, Collector<Tuple2<String, Integer>> collector) throws Exception {
                String[] words = s.split("\\s");
                for (String word : words) {
                    collector.collect(new Tuple2<String, Integer>(word,1));
                }
            }
        }).print();
        
       //   输出
		    (aa,1)
			(bb,1)
			(a,1)

	//代码 等价于 new Splitter()作为flatmap的入参
	 public static class Splitter implements FlatMapFunction<String, Tuple2<String, Integer>> {
	        @Override
	        public void flatMap(String sentence, Collector<Tuple2<String, Integer>> out) throws Exception {
	            for (String word: sentence.split(" ")) {
	                out.collect(new Tuple2<String, Integer>(word, 1));
	            }
	        }
	    }

2.3、Filter的使用

  • 处理输入一个元素产生一个或者多个元素的计算场景
  • Filter[DataStream -> DataStream]
        //2、数据源source aaaa 采集,ab不采集
        DataStreamSource<String> dataSource = env.socketTextStream("192.168.134.6", 9898);
       dataSource.filter(new FilterFunction<String>() {
           @Override
           public boolean filter(String s) throws Exception {
               //aaa开头的数据进行采集
               return s.startsWith("aaa");
           }
       }).print();

在这里插入图片描述

2.4 、KeyBy 字符统计

  • 将数据集中相同的Key值的数据放置在相同的分区中,也就是对数据集执行Partition操作
 //2、数据源source
        DataStreamSource<String> dataSource = env.socketTextStream("192.168.134.6", 9898);
        dataSource.flatMap((FlatMapFunction<String, Tuple2<String, Integer>>) (s, collector) -> {
            String[] words = s.split("\\s");
            for (String word : words) {
                collector.collect(new Tuple2<>(word, 1));
            }
        }).returns(Types.TUPLE(Types.STRING, Types.INT))
                .keyBy(item -> item.f0)
                .sum(1)
                .print();
        env.execute("test_transform_job");

2.5 、Reduce

  • 对数据集滚动进行聚合处理,其中定义的ReduceFuction必须满足运算结合律和交换律。
  • 按照一个字段分组的数据流上,接受两个输入,生成一个输出(类型需保一致)
	  //Lisi Math 1 ,Lisi English 2,Lisi Chinese 3
	  dataSource.map(new MapFunction<String, Tuple3<String,String,Integer>>() {
            @Override
            public Tuple3<String, String, Integer> map(String s) throws Exception {
                String[] split = s.split("\\s");
                return new Tuple3<String,String,Integer>(split[0],split[1],Integer.parseInt(split[2])) ;
            }
        })
        .keyBy(0)
        .reduce(new ReduceFunction<Tuple3<String, String, Integer>>() {
            @Override
            public Tuple3<String, String, Integer> reduce(Tuple3<String, String, Integer> t0, Tuple3<String, String, Integer> t1) throws Exception {
                //输入的t0,t1 聚合
                return Tuple3.of(t0.f0,"总分:", t0.f2 + t1.f2);
            }
        }).print();
------------------------------------------------------------------------------------------------        
		1> (Lisi,Math,1)
		1> (Lisi,总分:,3)
		1> (Lisi,总分:,6)        

2.6、Aggregation(聚合的统称)

  • 常见的聚合操作有sum、max、min等,这些聚合操作统称为aggregation
  • sum
        //  sensor_6,1608112830l,15.4
        //  sensor_6,1608112830l,15.0
        //  sensor_1,1608112851l,32
        //  sensor_1,1608112731l,36.2
    dataSource.flatMap(new FlatMapFunction<String, Tuple3<String,String,Double>>() {
            @Override
            public void flatMap(String s, Collector<Tuple3<String, String, Double>> collector) throws Exception {
                String[] split = s.split("\\,");
                collector.collect(new Tuple3(split[0],split[1],Double.parseDouble(split[2])));
            }
        }).keyBy(0).sum(2).print();
		
		11> (sensor_6,1608112830l,15.4)
		11> (sensor_6,1608112830l,30.4)
		10> (sensor_1,1608112851l,32.0)
		10> (sensor_1,1608112851l,68.2)

  • min 分组最小值
		sensor_6,1608112830l,15.4
		sensor_6,1608112830l,15.0
		sensor_6,1608112830l,15.4
		sensor_6,1608112830l,12
		~~~~~
		.keyBy(0).min(2).print();
		11> (sensor_6,1608112830l,15.4)
		11> (sensor_6,1608112830l,15.0)
		11> (sensor_6,1608112830l,15.0)
		11> (sensor_6,1608112830l,12.0)
		

2.7、fold

  • 具有初始值的键控数据流上的“滚动”折叠。将当前元素与最后折叠的值组合并发出新值。 一个折叠函数,当应用于序列 (1,2,3,4,5) 时,发出序列 “start-1”, “start-1-2”, “start-1-2-3”, . …
	DataStream<String> result =
	  keyedStream.fold("start", new FoldFunction<Integer, String>() {
	    @Override
	    public String fold(String current, Integer value) {
	        return current + "-" + value;
	    }
	  });
		//zhansan 苹果 4.5 2 
		//zhansan 苹果 4.5 2
	dataSource.map(new MapFunction<String, Tuple3<String, String, Double>>() {
	            @Override
	            public Tuple3<String, String, Double> map(String s) throws Exception {
	                String[] split = s.split("\\s");
	                return new Tuple3<String, String, Double>(split[0], split[1], Double.parseDouble(split[2]) * Double.parseDouble(split[3]));
	            }
	        }).keyBy(0).fold(0d, new FoldFunction<Tuple3<String, String, Double>, Double>() {
	            @Override
	            public Double fold(Double init, Tuple3<String, String, Double> o) throws Exception {
	                return init=init+o.f2;
	            }
	        }).print();
			
			8> 9.0
			8> 18.0
import java.util.Properties
import org.apache.flink.api.java.tuple.Tuple
import org.apache.flink.streaming.api.scala.{KeyedStream, StreamExecutionEnvironment}
import org.apache.flink.api.scala._
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
import org.apache.flink.streaming.util.serialization.SimpleStringSchema
import org.apache.kafka.clients.consumer.ConsumerConfig
		env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(), props))
		.map(line => {
		  val user = line.split(" ")(1)
		  val cost = line.split(" ")(3).toDouble * line.split(" ")(4).toInt
		  (user, cost)
		})
		.keyBy(0)
		.fold(("",0.0))((z,t)=>{(t._1,t._2+z._2)})
		.print()
		env.execute("order counts")
		

2.8、ProcessFunction

  • ProcessFunction:
	1、用来构建事件驱动的应用以及实现自定义的业务逻辑(使用之前的window函数和
转换算子无法实现)。例如,Flink SQL就是使用Process Function实现的	
	2ProcessFunction 函数是低阶流处理算子
		可以访问流应用程序所有(非循环)基本构建块--事件 (数据流元素),状态 (容
错和一致性),定时器 (事件时间和处理时间)
	3ProcessFunction 可以被认为是一种提供了对 KeyedState 和定时器访问
的 FlatMapFunction,每在输入流中接收到一个事件,就会调用来此函数来处。
	4、对于容错的状态,ProcessFunction 可以通过 RuntimeContext 访问 KeyedState,
类似于其他有状态函数访问 KeyedState
		events:数据流中的元素
		state:状态,用于容错和一致性,仅用于keyed stream
		timers:定时器,支持事件时间和处理时间,仅用于keyed stream
	5Flink提供了8Process Function
		ProcessFunction:dataStream
		KeyedProcessFunction:用于KeyedStream,keyBy之后的流处理
		CoProcessFunction:用于connect连接的流
		ProcessJoinFunction:用于join流操作
		BroadcastProcessFunction:用于广播
		KeyedBroadcastProcessFunction:keyBy之后的广播
		ProcessWindowFunction:窗口增量聚合
		ProcessAllWindowFunction:全窗口聚合
	6、定时器可让应用程序对在处理时间和事件时间中的变化进行响应。每次调用 
processElement(...)函数时都可以获得一个Context对象,通过该对象可以访问元
素的事件时间(event time)时间戳以及 TimerService。可以使用TimerService为
将来的事件时间/处理时间实例注册回调。对于事件时间计时器,当当前水印被提升到
或超过计时器的时间戳时,将调用onTimer()方法,而对于处理时间计时器,当挂钟
时间达到指定时间时,将调用onTimer()方法。在调用期间,所有状态的范围再次限
定为创建定时器所用的key,从而允许定时器操作keyed state。
	如果想要在流处理过程中访问keyed state和定时器,就必须在一个keyed stream上
应用ProcessFunction函数,代码如下:
	stream.keyBy(...).process(new MyProcessFunction())	
  • 在以下示例中,KeyedProcessFunction 为每个键维护一个计数,并且会把一分钟(事件时间)内没有更新的键/值对输出:
  • 计数,键以及最后更新的时间戳会存储在 ValueState 中,ValueState 由 key 隐含定义。
    对于每条记录,KeyedProcessFunction 增加计数器并修改最后的时间戳。
  • 该函数还会在一分钟后调用回调(事件时间)。
  • 每次调用回调时,都会检查存储计数的最后修改时间与回调的事件时间时间戳,如果匹配则发送键/计数键值对(即在一分钟内没有更新)
~~~java
import org.apache.flink.api.common.state.ValueState;
import org.apache.flink.api.common.state.ValueStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.functions.ProcessFunction;
import org.apache.flink.streaming.api.functions.ProcessFunction.Context;
import org.apache.flink.streaming.api.functions.ProcessFunction.OnTimerContext;
import org.apache.flink.util.Collector;

// 数据源
DataStream<Tuple2<String, String>> stream = ...;

// 对KeyedStream应用ProcessFunction
DataStream<Tuple2<String, Long>> result = stream
    .keyBy(0)
    .process(new CountWithTimeoutFunction());

/**
 * 存储在state中的数据类型
 */
public class CountWithTimestamp {
    public String key;
    public long count;
    public long lastModified;
}

/**
 * 维护了计数和超时间隔的ProcessFunction实现
 */
public class CountWithTimeoutFunction extends KeyedProcessFunction<Tuple, Tuple2<String, String>, Tuple2<String, Long>> {
    /** 这个状态是通过 ProcessFunction 维护*/
    private ValueState<CountWithTimestamp> state;

    @Override
    public void open(Configuration parameters) throws Exception {
        state = getRuntimeContext().getState(new ValueStateDescriptor<>("myState", CountWithTimestamp.class));
    }

    @Override
    public void processElement(
            Tuple2<String, String> value,
            Context ctx,
            Collector<Tuple2<String, Long>> out) throws Exception {

        // 查看当前计数
        CountWithTimestamp current = state.value();
        if (current == null) {
            current = new CountWithTimestamp();
            current.key = value.f0;
        }

        // 更新状态中的计数
        current.count++;

        // 设置状态的时间戳为记录的事件时间时间戳
        current.lastModified = ctx.timestamp();

        // 状态回写
        state.update(current);

        // 从当前事件时间开始注册一个60s的定时器
        ctx.timerService().registerEventTimeTimer(current.lastModified + 60000);
    }

    @Override
    public void onTimer(
            long timestamp,
            OnTimerContext ctx,
            Collector<Tuple2<String, Long>> out) throws Exception {

        // 得到设置这个定时器的键对应的状态
        CountWithTimestamp result = state.value();

        // 检查定时器是过时定时器还是最新定时器
        if (timestamp == result.lastModified + 60000) {
            // emit the state on timeout
            out.collect(new Tuple2<String, Long>(result.key, result.count));
        }
    }
}

  • KeyedProcessFunction 作为 ProcessFunction 的扩展,可以在 onTimer() 方法中访问定时器的键:
	Override
	public void onTimer(long timestamp, OnTimerContext ctx, Collector<OUT> out) throws Exception {
	    K key = ctx.getCurrentKey();
	    // ...
	}
  • 定时器
  • imerService 在内部维护两种类型的定时器(处理时间和事件时间定时器)并排队执行。
  • TimerService 会删除每个键和时间戳重复的定时器,即每个键在每个时间戳上最多有一个定时器。如果为同一时间戳注册了多个定时器,则只会调用一次 onTimer() 方法。
  • Flink同步调用 onTimer() 和 processElement() 方法。因此,用户不必担心状态的并发修改。

2.9、apply

  • 只能用WindowedStream进行调用
map.keyBy(0)
.timeWindow(Time.seconds(5))
		.apply(new WindowFunction<Tuple2<String, String>, Object, Tuple, TimeWindow>() {
			@Override
			public void apply(Tuple tuple, TimeWindow window, Iterable<Tuple2<String, String>> input, Collector<Object> out) throws Exception {
				System.out.println("fds");
			}
		})

	1、keyby后可直接map直接输出,也可以timeWindow进行数据范围批量处理,不能调用apply.
	2、timeWindow返回WindowedStreamWindowedStream后的process,apply方法,是在水印时间大于等于窗口时间才会进行调用的对窗口进行计算的方式
	3、process,apply计算的方式一样,都是会得到一批key值相同的数据
	

二、数据的计算

2.1、增量的聚合统计

  • 窗口当中每加入一条数据,就进行一次统计
import org.apache.flink.api.common.functions.ReduceFunction
import org.apache.flink.streaming.api.datastream.DataStreamSink
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.windowing.time.Time
 
object FlinkTimeCount {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
    import org.apache.flink.api.scala._
 
    val socketStream: DataStream[String] = environment.socketTextStream("node01",8000)
   
   //1 1  3 2 3 2  统计各数出现的次数(5分钟的滚动窗口)
    val print: DataStreamSink[(Int, Int)] = socketStream
      .map(x => (1, x.toInt))
      .keyBy(0)
      .timeWindow(Time.seconds(5))
      .reduce(new ReduceFunction[(Int, Int)] {
        override def reduce(t: (Int, Int), t1: (Int, Int)): (Int, Int) = {
          (t._1, t._2 + t1._2)
        }
      }).print()
 
    environment.execute("startRunning")
  }
}
------------------------------------------------			
		reduce(reduceFunction)
		aggregate(aggregateFunction)
		sum(),min(),max()
				 

2.2、全量的聚合统计

  • 等到窗口截止,或者窗口内的数据全部到齐,然后再进行统计,
  • 可以用于求窗口内的数据的最大值,或者最小值,平均值等
//通过全量聚合统计,求取每3条数据的平均值
import org.apache.flink.api.java.tuple.Tuple
import org.apache.flink.streaming.api.datastream.DataStreamSink
import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment, WindowedStream}
import org.apache.flink.streaming.api.windowing.windows.{GlobalWindow, TimeWindow}
import org.apache.flink.util.Collector
 
 
object FlinkCountWindowAvg {
  def main(args: Array[String]): Unit = {
    val environment: StreamExecutionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment
 
    import org.apache.flink.api.scala._
    val socketStream: DataStream[String] = environment.socketTextStream("node01",9000)
    //统计一个窗口内的数据的平均值
    (1,1)(1,1),(2,3),(4,4)
    val socketDatas: DataStreamSink[Double] = socketStream.map(x => (1, x.toInt))
      .keyBy(0)
      .countWindow(3) //求取每3条数据的平均值
       //.timeWindow(Time.seconds(10))

      //通过process方法来统计窗口的平均值
      .process(new MyProcessWindowFunctionclass).print()
    
    //必须调用execute方法,否则程序不会执行
    environment.execute("count avg")
  }
}
 
/**ProcessWindowFunction 需要跟四个参数
  * 输入参数类型,输出参数类型,聚合的key的类型,window的下界
  */
class MyProcessWindowFunctionclass extends ProcessWindowFunction[(Int , Int) , Double , Tuple , GlobalWindow]{
  override def process(key: Tuple, context: Context, elements: Iterable[(Int, Int)], out: Collector[Double]): Unit = {
    var totalNum = 0;
    var countNum = 0;
    for(data <-  elements){
      totalNum +=1
      countNum += data._2
    }
    out.collect(countNum/totalNum)
  }
}

三、DataStream数据源DataSource

	Flink 使用 StreamExecutionEnvironment.getExecutionEnvironment创建流处理的执行环境
	Flink 使用 StreamExecutionEnvironment.addSource(source) 来为你的程序添加数据来源。
	Flink 已经提供了若干实现好了的 source functions,当然你也可以通过实现SourceFunction来
自定义非并行的source,或者实现ParallelSourceFunction 接口或者扩展RichParallelSourceFunction来自定义并行的 source。
	Flink在流处理上的source和在批处理上的source基本一致。大致有4大类:
	    1、本地集合的source(Collection-based-source)
		2、文件的source(File-based-source)
		3、socket 读取。元素可以用分隔符切分。
		4、自定义的source(Custom-source)
		5Kafka  source

3.1、 集合source

import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.scala._
import scala.collection.immutable.{Queue, Stack}
import scala.collection.mutable
import scala.collection.mutable.{ArrayBuffer, ListBuffer}

object StreamingDemoFromCollectionSource {
  def main(args: Array[String]): Unit = {
    val senv = StreamExecutionEnvironment.getExecutionEnvironment
    //0.用element创建DataStream(fromElements)
    val ds0: DataStream[String] = senv.fromElements("spark", "flink")
    ds0.print()

    //1.用Tuple创建DataStream(fromElements)
    val ds1: DataStream[(Int, String)] = senv.fromElements((1, "spark"), (2, "flink"))
    ds1.print()

    //2.用Array创建DataStream
    val ds2: DataStream[String] = senv.fromCollection(Array("spark", "flink"))
    ds2.print()

    //3.用ArrayBuffer创建DataStream
    val ds3: DataStream[String] = senv.fromCollection(ArrayBuffer("spark", "flink"))
    ds3.print()

    //4.用List创建DataStream
    val ds4: DataStream[String] = senv.fromCollection(List("spark", "flink"))
    ds4.print()

    //5.用List创建DataStream
    val ds5: DataStream[String] = senv.fromCollection(ListBuffer("spark", "flink"))
    ds5.print()

    //6.用Vector创建DataStream
    val ds6: DataStream[String] = senv.fromCollection(Vector("spark", "flink"))
    ds6.print()

    //7.用Queue创建DataStream
    val ds7: DataStream[String] = senv.fromCollection(Queue("spark", "flink"))
    ds7.print()

    //8.用Stack创建DataStream
    val ds8: DataStream[String] = senv.fromCollection(Stack("spark", "flink"))
    ds8.print()

    //9.用Stream创建DataStream(Stream相当于lazy List,避免在中间过程中生成不必要的集合)
    val ds9: DataStream[String] = senv.fromCollection(Stream("spark", "flink"))
    ds9.print()

    //10.用Seq创建DataStream
    val ds10: DataStream[String] = senv.fromCollection(Seq("spark", "flink"))
    ds10.print()

    //11.用Set创建DataStream(不支持)
    //val ds11: DataStream[String] = senv.fromCollection(Set("spark", "flink"))
    //ds11.print()

    //12.用Iterable创建DataStream(不支持)
    //val ds12: DataStream[String] = senv.fromCollection(Iterable("spark", "flink"))
    //ds12.print()

    //13.用ArraySeq创建DataStream
    val ds13: DataStream[String] = senv.fromCollection(mutable.ArraySeq("spark", "flink"))
    ds13.print()

    //14.用ArrayStack创建DataStream
    val ds14: DataStream[String] = senv.fromCollection(mutable.ArrayStack("spark", "flink"))
    ds14.print()

    //15.用Map创建DataStream(不支持)
    //val ds15: DataStream[(Int, String)] = senv.fromCollection(Map(1 -> "spark", 2 -> "flink"))
    //ds15.print()

    //16.用Range创建DataStream
    val ds16: DataStream[Int] = senv.fromCollection(Range(1, 9))
    ds16.print()

    //17.用fromElements创建DataStream
    val ds17: DataStream[Long] = senv.generateSequence(1, 9)
    ds17.print()

    senv.execute(this.getClass.getName)
  }
}

3.2、文件source

	object DataSource_CSV {
	  def main(args: Array[String]): Unit = {
	    // 1. 获取流处理运行环境
	    val env = StreamExecutionEnvironment.getExecutionEnvironment
	    // 2. 读取文件
	    val textDataStream: DataStream[String] = env.readTextFile("hdfs://node01:8020/flink-datas/score.csv")
	    // 3. 打印数据
	    textDataStream.print()
	    // 4. 执行程序
	    env.execute()
	  }
	}

3.3、接收socket

object SocketSource {
  def main(args: Array[String]): Unit = {
    //1. 获取流处理运行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //  2. 构建socket流数据源,并指定IP地址和端口号
    // hadoop hadoop hive spark
    val socketDataStream: DataStream[String] = env.socketTextStream("node01", 9999)
    //  3. 转换,以空格拆分单词
    val mapDataSet: DataStream[String] = socketDataStream.flatMap(_.split(" "))
    //  4. 打印输出
    mapDataSet.print()
    //  5. 启动执行
    env.execute("WordCount_Stream")
  }
}

3.4、自定义source

  • 通过去实现SourceFunction或者它的子类RichSourceFunction类来自定义实现一些自定义的source
  • Kafka创建source数据源类FlinkKafkaConsumer010也是采用类似的方式
//自定义数据源, 每1秒钟随机生成一条订单信息(订单ID、用户ID、订单金额、时间戳)
	要求:
		随机生成订单ID(UUID)
		随机生成用户ID(0-2)
		随机生成订单金额(0-100)
		时间戳为当前系统时间
object StreamFlinkSqlDemo {

  // 创建一个订单样例类Order,包含四个字段(订单ID、用户ID、订单金额、时间戳)
  case class Order(id: String, userId: Int, money: Long, createTime: Long)

  def main(args: Array[String]): Unit = {
    // 1. 获取流处理运行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment

    // 2. 创建一个自定义数据源
    val orderDataStream = env.addSource(new RichSourceFunction[Order] {
      override def run(ctx: SourceFunction.SourceContext[Order]): Unit = {
				        // 使用for循环生成1000个订单
				        for (i <- 0 until 1000) {
				          // 随机生成订单ID(UUID)
				          val id = UUID.randomUUID().toString
				          
				          // 随机生成用户ID(0-2)
				          val userId = Random.nextInt(3)
				          
				          // 随机生成订单金额(0-100)
				          val money = Random.nextInt(101)
				          
				          // 时间戳为当前系统时间
				          val timestamp = System.currentTimeMillis()
				          
				          // 收集数据
				          ctx.collect(Order(id, userId, money, timestamp))
				          
				          // 每隔1秒生成一个订单
				          TimeUnit.SECONDS.sleep(1)
				        }
      	}
      override def cancel(): Unit = ()
    })
    // 3. 打印数据
    orderDataStream.print()
    // 4. 执行程序
    env.execute()
  }
}

3.5、Kafka作为数据源

import java.util.Properties
import org.apache.flink.api.common.serialization.SimpleStringSchema
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.api.scala._
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer010
import org.apache.kafka.clients.CommonClientConfigs

object DataSource_kafka {
  def main(args: Array[String]): Unit = {

    //1. 创建流处理环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
      
    //2. 指定kafka数据流的相关信息
    val kafkaCluster = "node01:9092,node02:9092,node03:9092"
    val kafkaTopicName = "kafkatopic"

    //3. 创建kafka数据流
    val properties = new Properties()
    properties.setProperty(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, kafkaCluster)
    val kafka010 = new FlinkKafkaConsumer010[String](kafkaTopicName, new SimpleStringSchema(), properties)

    //4. 添加数据源addSource(kafka010)
    val text: DataStream[String] = env.addSource(kafka010)

    //5. 打印数据
    text.print()

    //6. 执行任务
    env.execute("flink-kafka-wordcunt")
  }
}

3.6、MySQL作为数据源

  • 相关依赖
 <!-- 指定mysql-connector的依赖 -->
 <dependency>
     <groupId>mysql</groupId>
     <artifactId>mysql-connector-java</artifactId>
     <version>5.1.38</version>
 </dependency>
package com.itheima.stream

import java.sql.{Connection, DriverManager, PreparedStatement}

import org.apache.flink.api.scala._
import org.apache.flink.configuration.Configuration
import org.apache.flink.streaming.api.functions.source.RichSourceFunction
import org.apache.flink.streaming.api.functions.source.SourceFunction.SourceContext
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}

object DataSource_mysql {
  def main(args: Array[String]): Unit = {
    // 1. 创建流处理环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 2. 设置并行度
    env.setParallelism(1)
    // 3. 添加自定义MySql数据源
    val source = env.addSource(new MySql_source)
    // 4. 打印结果
    source.print()
    // 5. 执行任务
    env.execute()
  }
}

class MySql_source extends RichSourceFunction[(Int, String, String, String)] {

  override def run(ctx: SourceContext[(Int, String, String, String)]): Unit = {

    // 1. 加载MySql驱动
    Class.forName("com.mysql.jdbc.Driver")
    // 2. 链接MySql
    var connection: Connection = DriverManager.getConnection("jdbc:mysql:///test", "root", "123456")
    // 3. 创建PreparedStatement
    val sql = "select id , username , password , name from user"
    var ps: PreparedStatement = connection.prepareStatement(sql)

    // 4. 执行Sql查询
    val queryRequest = ps.executeQuery()
    // 5. 遍历结果
    while (queryRequest.next()) {
      val id = queryRequest.getInt("id")
      val username = queryRequest.getString("username")
      val password = queryRequest.getString("password")
      val name = queryRequest.getString("name")
      // 收集数据
      ctx.collect((id, username, password, name))
    }
  }
  override def cancel(): Unit = {}
}

四、DataStream的Transformation

4.1、Connect

  • Connect用来将两个DataStream组装成一个ConnectedStreams。它用了两个泛型,即不要求两个dataStream的element是同一类型。这样我们就可以把不同的数据组装成同一个结构.
步骤:
	创建流式处理环境
	添加两个自定义数据源
	使用connect合并两个数据流,创建ConnectedStreams对象
	遍历ConnectedStreams对象,转换为DataStream
	打印输出,设置并行度为1
	执行任务
/**
  * 创建自定义并行度为1的source 
  * 实现从1开始产生递增数字 
  */
class MyLongSourceScala extends SourceFunction[Long] {
  var count = 1L
  var isRunning = true

  override def run(ctx: SourceContext[Long]) = {
    while (isRunning) {
      ctx.collect(count)
      count += 1
      TimeUnit.SECONDS.sleep(1)
    }
  }

  override def cancel() = {
    isRunning = false
  }
}

/**
  * 创建自定义并行度为1的source
  * 实现从1开始产生递增字符串
  */
class MyStringSourceScala extends SourceFunction[String] {
  var count = 1L
  var isRunning = true

  override def run(ctx: SourceContext[String]) = {
    while (isRunning) {
      ctx.collect("str_" + count)
      count += 1
      TimeUnit.SECONDS.sleep(1)
    }
  }

  override def cancel() = {
    isRunning = false
  }
}

object StreamingDemoConnectScala {
  def main(args: Array[String]): Unit = {
    // 1. 创建流式处理环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 2. 添加两个自定义数据源
    val text1: DataStream[Long] = env.addSource(new MyLongSourceScala)
    val text2: DataStream[String] = env.addSource(new MyStringSourceScala)
    // 3. 使用connect合并两个数据流,创建ConnectedStreams对象
    val connectedStreams: ConnectedStreams[Long, String] = text1.connect(text2)
    // 4. 遍历ConnectedStreams对象,转换为DataStream
    val result: DataStream[Any] = connectedStreams.map(line1 => {
      line1
    }, line2 => {
      line2
    })
    // 5. 打印输出,设置并行度为1
    result.print().setParallelism(1)
    // 6. 执行任务
    env.execute("StreamingDemoWithMyNoParallelSourceScala")
  }
}

4.2、split和select

  • split就是将一个DataStream分成多个流,用SplitStream来表示DataStream → SplitStream
  • select就是获取分流后对应的数据,跟split搭配使用,从SplitStream中选择一个或多个流SplitStream → DataStream
	demo:
	加载本地集合(1,2,3,4,5,6), 使用split进行数据分流,分为奇数和偶数. 并打印奇数结果
	
	/**
  * 演示Split和Select方法
  * Split: DataStream->SplitStream
  * Select: SplitStream->DataStream
  */
object SplitAndSelect {

  def main(args: Array[String]): Unit = {
    // 1. 创建批处理环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 2. 设置并行度
    env.setParallelism(1)
    // 3. 加载本地集合
    val elements: DataStream[Int] = env.fromElements(1, 2, 3, 4, 5, 6)
    // 4. 数据分流,分为奇数和偶数
    val split_data: SplitStream[Int] = elements.split(
      (num: Int) =>
        num % 2 match {
          case 0 => List("even")
          case 1 => List("odd")
        }
    )
    // 5. 获取分流后的数据
    val even: DataStream[Int] = split_data.select("even")
    val odd: DataStream[Int] = split_data.select("odd")
    val all: DataStream[Int] = split_data.select("odd", "even")

    // 6. 打印数据
    odd.print()

    // 7. 执行任务
    env.execute()
  }
}


4.3、聚合函数

  • 定义window assigner后,Window Function对指定窗口元素做计算。
  • window Function可以是ReduceFunction,AggregateFunction,FoldFunction或ProcessWindowFunction。
  • 前两个可以更有效地执行,因为Flink可以在每个窗口到达时递增地聚合它们的元素。
  • ProcessWindowFunction获取窗口中包含的所有元素的Iterable以及有关元素所属窗口的元信息

4.3.1、 ReduceFunction

	import java.util.Properties
	
	import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
	import org.apache.flink.api.scala._
	import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
	import org.apache.flink.streaming.api.windowing.time.Time
	import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
	import org.apache.flink.streaming.util.serialization.SimpleStringSchema
	import org.apache.kafka.clients.consumer.ConsumerConfig
	
	val env = StreamExecutionEnvironment.createLocalEnvironment()
	
	val props = new Properties()
	props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOS:9092,CentOS:9093,CentOS:9094")
	props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "g1")
	
	env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(), props))
	.flatMap(line => line.split(" "))
	.map((_,1))
	.keyBy(0)
	.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
	.reduce((in1,in2)=>(in1._1,in1._2+in2._2))
	.print()
	
	env.execute("word counts")

4.3.2、 AggregateFunction

  • AggregateFunction是ReduceFunction的通用版本,
  • 有三种类型:输入类型(IN),累加器类型(ACC)和输出类型(OUT)
		import java.util.Properties
		
		import org.apache.flink.api.common.functions.AggregateFunction
		import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
		import org.apache.flink.api.scala._
		import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
		import org.apache.flink.streaming.api.windowing.time.Time
		import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
		import org.apache.flink.streaming.util.serialization.SimpleStringSchema
		import org.apache.kafka.clients.consumer.ConsumerConfig
		
		val env = StreamExecutionEnvironment.createLocalEnvironment()
		
		val props = new Properties()
		props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOS:9092,CentOS:9093,CentOS:9094")
		props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "g1")
		
		env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(), props))
		.flatMap(line => line.split(" "))
		.map((_,1))
		.keyBy(0)
		.window(TumblingProcessingTimeWindows.of(Time.seconds(5)))
		.aggregate(new CustomAggregateFunction)
		.print()
		
		env.execute("word counts")

4.3.3、自定义Aggregation

	 class CustomAggregateFunction extends AggregateFunction[(String,Int),(String,Int),String]{
	  //返回累加器的初始值
	  override def createAccumulator(): (String,Int) = {
	    ("",0)
	  }
	  //累加元素
	  override def add(value: (String, Int), accumulator: (String,Int)):(String,Int) = {
	    (value._1,value._2+accumulator._2)
	  }
	  //合并累加器的值
	  override def merge(a: (String, Int), b: (String, Int)): (String, Int) = {
	    (a._1,a._2+b._2)
	  }
	  //返回最终输出结果
	  override def getResult(accumulator: (String, Int)): String = {
	    accumulator._1+" -> "+accumulator._2
	  }
	}

4.3.4、FoldFunction

		import java.util.Properties
		
		import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
		import org.apache.flink.api.scala._
		import org.apache.flink.streaming.api.windowing.assigners.SlidingProcessingTimeWindows
		import org.apache.flink.streaming.api.windowing.time.Time
		import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
		import org.apache.flink.streaming.util.serialization.SimpleStringSchema
		import org.apache.kafka.clients.consumer.ConsumerConfig
		
		val env = StreamExecutionEnvironment.createLocalEnvironment()
		
		val props = new Properties()
		props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOS:9092,CentOS:9093,CentOS:9094")
		props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "g1")
		
		env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(), props))
		.flatMap(line => line.split(" "))
		.map((_,1))
		.keyBy(0)
		.window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
		.fold(("",0))((i1,i2)=>(i2._1,i1._2+i2._2))
		.print()
		
		env.execute("word counts")

4.3.5、ProcessWindowFunction

  • ProcessWindowFunction获取包含窗口所有元素的Iterable,以及可访问时间和状态信息的Context对象,这使其能够提供比其他窗口函数更多的灵活性。这是以性能和资源消耗为代价的,因为元素不能以递增方式聚合,而是需要在内部进行缓冲,直到认为窗口已准备好进行处理。
	import java.util.Properties
	
	import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
	import org.apache.flink.api.scala._
	import org.apache.flink.streaming.api.scala.function.ProcessWindowFunction
	import org.apache.flink.streaming.api.windowing.assigners.SlidingProcessingTimeWindows
	import org.apache.flink.streaming.api.windowing.time.Time
	import org.apache.flink.streaming.api.windowing.windows.TimeWindow
	import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
	import org.apache.flink.streaming.util.serialization.SimpleStringSchema
	import org.apache.flink.util.Collector
	import org.apache.kafka.clients.consumer.ConsumerConfig
	
	val env = StreamExecutionEnvironment.createLocalEnvironment()
	
	val props = new Properties()
	props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOS:9092,CentOS:9093,CentOS:9094")
	props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "g1")
	
	env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(), props))
	.flatMap(line => line.split(" "))
	.map((_,1))
	.keyBy(_._1)
	.window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
	.process(new CustomProcessWindowFunction())
	.print()
	
	env.execute("word counts")

  • 自定义ProcessWindowFunction
class CustomProcessWindowFunction extends 
	ProcessWindowFunction[(String,Int),String,String,TimeWindow]{
	  override def process(key: String, context: Context, elements: Iterable[(String, Int)], out: Collector[String]): Unit = {
	    val tuple: (String, Int) = elements.reduce((i1,i2)=>(i1._1,i1._2+i2._2))
	    out.collect(tuple._1+" => "+tuple._2)
	  }
	}

4.4 、Union

  • 流合并,必须保证合并的流的类型保持一致
	import org.apache.flink.api.scala._
	import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
	
	val env = StreamExecutionEnvironment.createLocalEnvironment()
	val stream1: DataStream[String] = env.fromElements("a","b","c")
	val stream2: DataStream[String] = env.fromElements("b","c","d")
	stream1.union(stream2)
	.print()
	env.execute("union")

三、Sink

3.1、Sink到Kafka

  • 读取MySql的数据, 落地到Kafka中
  • 步骤
	创建流处理环境
	设置并行度
	添加自定义MySql数据源
	转换元组数据为字符串
	构建FlinkKafkaProducer010
	添加sink
	执行任务
	object DataSink_kafka {
	  def main(args: Array[String]): Unit = {
	    // 1. 创建流处理环境
	    val env = StreamExecutionEnvironment.getExecutionEnvironment
	    // 2. 设置并行度
	    env.setParallelism(1)
	    // 3. 添加自定义MySql数据源
	    val source: DataStream[(Int, String, String, String)] = env.addSource(new MySql_source)
	
	    // 4. 转换元组数据为字符串
	    val strDataStream: DataStream[String] = source.map(
	      line => line._1 + line._2 + line._3 + line._4
	    )
	
	    //5. 构建FlinkKafkaProducer010
	    val p: Properties = new Properties
	    p.setProperty("bootstrap.servers", "node01:9092,node02:9092,node03:9092")
	    val sink = new FlinkKafkaProducer010[String]("test2", new SimpleStringSchema(), p)
	    // 6. 添加sink
	    strDataStream.addSink(sink)
	    // 7. 执行任务
	    env.execute("flink-kafka-wordcount")
	  }
	}

3.2、Sink到MySQL

  • 加载下列本地集合,导入MySql中
List(
      (10, "dazhuang", "123456", "大壮"),
      (11, "erya", "123456", "二丫"),
      (12, "sanpang", "123456", "三胖")
    )
object DataSink_MySql {
  def main(args: Array[String]): Unit = {
    //1.创建流执行环境
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    //2.准备数据
    val value: DataStream[(Int, String, String, String)] = env.fromCollection(List(
      (10, "dazhuang", "123456", "大壮"),
      (11, "erya", "123456", "二丫"),
      (12, "sanpang", "123456", "三胖")
    ))
    // 3. 添加sink
    value.addSink(new MySql_Sink)
    //4.触发流执行
    env.execute()
  }
}

// 自定义落地MySql的Sink
class MySql_Sink extends RichSinkFunction[(Int, String, String, String)] {

  private var connection: Connection = null
  private var ps: PreparedStatement = null

  override def open(parameters: Configuration): Unit = {
    //1:加载驱动
    Class.forName("com.mysql.jdbc.Driver")
    //2:创建连接
    connection = DriverManager.getConnection("jdbc:mysql:///test", "root", "123456")
    //3:获得执行语句
    val sql = "insert into user(id , username , password , name) values(?,?,?,?);"
    ps = connection.prepareStatement(sql)
  }

  override def invoke(value: (Int, String, String, String)): Unit = {
    try {
      //4.组装数据,执行插入操作
      ps.setInt(1, value._1)
      ps.setString(2, value._2)
      ps.setString(3, value._3)
      ps.setString(4, value._4)
      ps.executeUpdate()
    } catch {
      case e: Exception => println(e.getMessage)
    }
  }

  //关闭连接操作
  override def close(): Unit = {
    if (connection != null) {
      connection.close()
    }
    if (ps != null) {
      ps.close()
    }
  }
}

3.3、Redis Sink

  • 依赖
  • 参考:https://bahir.apache.org/docs/flink/current/flink-streaming-redis/
	<dependency>
	  <groupId>org.apache.bahir</groupId>
	  <artifactId>flink-connector-redis_2.11</artifactId>
	  <version>1.0</version>
	</dependency>
	val env = StreamExecutionEnvironment.getExecutionEnvironment
	val conf = new FlinkJedisPoolConfig.Builder().setHost("CentOS").setPort(6379).build()
	
	env.socketTextStream("localhost",8888)
	.flatMap(_.split(" "))
	.map((_,1))
	.keyBy(_._1)
	.sum(1)
	.addSink(new RedisSink[(String, Int)](conf, new RedisWordMapper))
	env.execute("状态测试")

  • RedisMapper
	import org.apache.flink.streaming.connectors.redis.common.mapper.{RedisCommand, RedisCommandDescription, RedisMapper}
	
	class RedisWordMapper  extends RedisMapper[(String,Int)]{
	  override def getCommandDescription: RedisCommandDescription = {
	    new RedisCommandDescription(RedisCommand.HSET,"wordcounts")
	  }
	
	  override def getKeyFromData(t: (String, Int)): String = {
	    t._1
	  }
	
	  override def getValueFromData(t: (String, Int)): String = {
	    t._2.toString
	  }
	}

  • 集群
	FlinkJedisPoolConfig conf = new FlinkJedisPoolConfig.Builder()
	    .setNodes(new HashSet<InetSocketAddress>(Arrays.asList(new InetSocketAddress(5601)))).build();
  • 哨兵
	val conf = new FlinkJedisSentinelConfig.Builder()
	.setMasterName("master")
	.setSentinels(...)
	.build()
	
	stream.addSink(new RedisSink[(String, String)](conf, new RedisExampleMapper))

四、时间窗口

在这里插入图片描述

  • Window Assigners定义如何将元素分配给窗口。
  • Window Assigner负责将每个传入元素分配给一个或多个窗口。
  • Flink为最常见的用例提供了预定义的Window Assigners
   tumbling windows, sliding windows, session windows 和global windows。
   用户还可以通过扩展WindowAssigner类来实现自定义Window Assigners。
   所有内置WindowAssigner(global windows除外)都根据时间为窗口分配元素,
   
   Event Time是基于 processing time 或者 event time.
   基于时间的窗口具有start timestamp(包括)和end timestamp(不包括),它们一起描述窗口的大小。
   Flink在使用基于时间的窗口时使用TimeWindow,该窗口具有查询开始和结束时间戳的方法,以及返回给定窗
口的最大允许时间戳的附加方法maxTimestamp()
Event time(事件的产生时间):
	 是每个单独事件在其生产设备上发生的时间。这个时间一般是在数据Event进入Flink之前,由发射该Event的
设备分配,改时间标识了数据产生时间。使用此种机制所有的窗口的划分取决于数据本身携带的时间戳。
	缺点:需要用户指定watermarker,给系统带来更多延长,优点:可以有效的处理数据的乱序问题,更加符合窗口处理的逻辑。

Processing time(事件的处理时间):
	处理时间是指执行相应操作的机器的系统时间,所有基于时间的操作(如时间窗口)将使用运行相应操作的
机器的系统时钟。缺点是在分布式系统中,无法保证数据处理时间的确定性,因为数据的窗口划分和数据出现的
时间没有关系,取决于数据何时抵达处理节点。

Ingestion time:数据的采集时间,标识记录进入Flink系统的时间。

4.1、tumbling windows(滚动窗口)

	import java.util.Properties
	
	import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
	import org.apache.flink.api.scala._
	import org.apache.flink.streaming.api.windowing.assigners.TumblingProcessingTimeWindows
	import org.apache.flink.streaming.api.windowing.time.Time
	import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
	import org.apache.flink.streaming.util.serialization.SimpleStringSchema
	import org.apache.kafka.clients.consumer.ConsumerConfig
	
	val env = StreamExecutionEnvironment.createLocalEnvironment()
	
	val props = new Properties()
	props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOS:9092,CentOS:9093,CentOS:9094")
	props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "g1")
	
	env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(), props))
	.flatMap(line => line.split(" "))
	.map((_,1))
	.keyBy(0)
	.window(TumblingProcessingTimeWindows.of(Time.seconds(10)))
	.sum(1)
	.print()
	
	env.execute("word counts")

4.2、Sliding Window(滑动窗口)

	import java.util.Properties
	
	import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
	import org.apache.flink.api.scala._
	import org.apache.flink.streaming.api.windowing.assigners.SlidingProcessingTimeWindows
	import org.apache.flink.streaming.api.windowing.time.Time
	import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer
	import org.apache.flink.streaming.util.serialization.SimpleStringSchema
	import org.apache.kafka.clients.consumer.ConsumerConfig
	
	val env = StreamExecutionEnvironment.createLocalEnvironment()
	
	val props = new Properties()
	props.setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "CentOS:9092,CentOS:9093,CentOS:9094")
	props.setProperty(ConsumerConfig.GROUP_ID_CONFIG, "g1")
	
	env.addSource(new FlinkKafkaConsumer[String]("topic01",new SimpleStringSchema(), props))
	.flatMap(line => line.split(" "))
	.map((_,1))
	.keyBy(0)
	.window(SlidingProcessingTimeWindows.of(Time.seconds(10),Time.seconds(5)))
	.sum(1)
	.print()
	
	env.execute("word counts")

4.3、 Event Time Window

  • Flink支持Processing Time和Event Time两种机制对元素做窗口划分
  • 由于Processing Time窗口划分的机制是按照处理节点接收到的数据的时间节点为划分窗口的机制,并不能很好的去描述数据产生时刻某个窗口期数计算的结果。
  • 所以Flink提供了基于Event Time的机制去划分窗口。
  • 在基于Event Time划分窗口的优点在于系统可以实现对窗口中乱序数据的处理以及延迟窗口和迟到数据采集等功能。
  • water marker
import java.text.SimpleDateFormat

import org.apache.flink.api.scala._
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.AssignerWithPunctuatedWatermarks
import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector

		val env = StreamExecutionEnvironment.createLocalEnvironment()
		env.setParallelism(1)
		env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
		
		val watermarker = env.socketTextStream("CentOS", 9999)
		.map(line => (line.split(",")(0), line.split(",")(1).toLong))
		.assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks[(String, Long)] {//抽取时间戳和水位线
		  var maxOutOfOrderness:Long=5000L
		  var currentMaxTimestamp:Long=0
		  //每当有数据的时候,系统会自动调用checkAndGetNextWatermark方法
		  override def checkAndGetNextWatermark(lastElement: (String, Long), extractedTimestamp: Long): Watermark = {
		    new Watermark(currentMaxTimestamp-maxOutOfOrderness)
		  }
		  //抽取时间戳
		  override def extractTimestamp(element: (String, Long), previousElementTimestamp: Long): Long = {
		    var sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
		
		    currentMaxTimestamp=Math.max(currentMaxTimestamp,element._2)
		    println(element+"\tcurrentMaxTimestamp:"+sf.format(currentMaxTimestamp)+"\ttimestamp:"+sf.format(element._2)+"\t watermarker:"+sf.format(currentMaxTimestamp-maxOutOfOrderness))
		    element._2
		  }
		})
		watermarker.keyBy(_._1)
		.timeWindow(Time.seconds(5))//5秒种滚动窗口
		.apply((key:String,w:TimeWindow,values:Iterable[(String,Long)],out:Collector[String])=>{
		  var sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
		  val tuples = values.toList.sortBy(_._2).map(t=> sf.format(t._2))
		  var msg =tuples.mkString(" , ") +" size:"+tuples.size+" window:["+sf.format(w.getStart)+","+sf.format(w.getEnd)+"]"
		  out.collect(msg)
		})
		.print()
		env.execute("water marker")

  • 迟到数据处理
  • 设置最大迟到 widow_end + Lateness > watermarker 触发过的窗口会重新触发
		import java.text.SimpleDateFormat
		
		import org.apache.flink.api.scala._
		import org.apache.flink.streaming.api.TimeCharacteristic
		import org.apache.flink.streaming.api.functions.AssignerWithPunctuatedWatermarks
		import org.apache.flink.streaming.api.scala.StreamExecutionEnvironment
		import org.apache.flink.streaming.api.watermark.Watermark
		import org.apache.flink.streaming.api.windowing.time.Time
		import org.apache.flink.streaming.api.windowing.windows.TimeWindow
		import org.apache.flink.util.Collector
		
		val env = StreamExecutionEnvironment.createLocalEnvironment()
		env.setParallelism(1)
		env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
		
		val watermarker = env.socketTextStream("CentOS", 9999)
		
		.map(line => (line.split(",")(0), line.split(",")(1).toLong))
		.assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks[(String, Long)] {//抽取时间戳和水位线
		  var maxOutOfOrderness:Long=5000L
		  var currentMaxTimestamp:Long=0
		  //每当有数据的时候,系统会自动调用checkAndGetNextWatermark方法
		  override def checkAndGetNextWatermark(lastElement: (String, Long), extractedTimestamp: Long): Watermark = {
		    new Watermark(currentMaxTimestamp-maxOutOfOrderness)
		  }
		  //抽取时间戳
		  override def extractTimestamp(element: (String, Long), previousElementTimestamp: Long): Long = {
		    var sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
		
		    currentMaxTimestamp=Math.max(currentMaxTimestamp,element._2)
		    println(element+"\tcurrentMaxTimestamp:"+sf.format(currentMaxTimestamp)+"\ttimestamp:"+sf.format(element._2)+"\t watermarker:"+sf.format(currentMaxTimestamp-maxOutOfOrderness))
		    element._2
		  }
		})
		watermarker.keyBy(_._1)
		.timeWindow(Time.seconds(5))//5秒种滚动窗口
		.allowedLateness(Time.seconds(2))
		.apply((key:String,w:TimeWindow,values:Iterable[(String,Long)],out:Collector[String])=>{
		  var sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
		  val tuples = values.toList.sortBy(_._2).map(t=> sf.format(t._2))
		  var msg =tuples.mkString(" , ") +" size:"+tuples.size+" window:["+sf.format(w.getStart)+","+sf.format(w.getEnd)+"]"
		  out.collect(msg)
		})
		.print()
		env.execute("water marker")

  • sideOutputLateData过期数据
		import java.text.SimpleDateFormat
		
		import org.apache.flink.api.scala._
		import org.apache.flink.streaming.api.TimeCharacteristic
		import org.apache.flink.streaming.api.functions.AssignerWithPunctuatedWatermarks
		import org.apache.flink.streaming.api.scala.{DataStream, OutputTag, StreamExecutionEnvironment}
		import org.apache.flink.streaming.api.watermark.Watermark
		import org.apache.flink.streaming.api.windowing.time.Time
		import org.apache.flink.streaming.api.windowing.windows.TimeWindow
		import org.apache.flink.util.Collector
		
		val env = StreamExecutionEnvironment.createLocalEnvironment()
		env.setParallelism(1)
		env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
		
		val watermarker = env.socketTextStream("CentOS", 9999)
		
		.map(line => (line.split(",")(0), line.split(",")(1).toLong))
		.assignTimestampsAndWatermarks(new AssignerWithPunctuatedWatermarks[(String, Long)] {//抽取时间戳和水位线
		  var maxOutOfOrderness:Long=5000L
		  var currentMaxTimestamp:Long=0
		  //每当有数据的时候,系统会自动调用checkAndGetNextWatermark方法
		  override def checkAndGetNextWatermark(lastElement: (String, Long), extractedTimestamp: Long): Watermark = {
		    new Watermark(currentMaxTimestamp-maxOutOfOrderness)
		  }
		  //抽取时间戳
		  override def extractTimestamp(element: (String, Long), previousElementTimestamp: Long): Long = {
		    var sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
		
		    currentMaxTimestamp=Math.max(currentMaxTimestamp,element._2)
		    println(element+"\tcurrentMaxTimestamp:"+sf.format(currentMaxTimestamp)+"\ttimestamp:"+sf.format(element._2)+"\t watermarker:"+sf.format(currentMaxTimestamp-maxOutOfOrderness))
		    element._2
		  }
		})
		val window = watermarker.keyBy(_._1)
		.timeWindow(Time.seconds(5)) //5秒种滚动窗口
		.allowedLateness(Time.seconds(2))
		.sideOutputLateData(new OutputTag[(String, Long)]("lateData"))
		.apply((key: String, w: TimeWindow, values: Iterable[(String, Long)], out: Collector[String]) => {
		  var sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
		  val tuples = values.toList.sortBy(_._2).map(t => sf.format(t._2))
		  var msg = tuples.mkString(" , ") + " size:" + tuples.size + " window:[" + sf.format(w.getStart) + "," + sf.format(w.getEnd) + "]"
		  out.collect(msg)
		})
		
		window
		.getSideOutput(new OutputTag[(String, Long)]("lateData"))
		.print()
		window.print()
		env.execute("water marker")

五、State

  • 针对于流处理的有状态function和operators可以存储流计算过程中的每个Event的计算状态。
  • 状态计算是构建精确操作不会或缺的板块。
  • Flink需要获知计算节点的状态,从而使用checkpoint和savepoint机制实现数据的故障恢复和容错。
  • 其中Queryable State允许外部在Flink运行过程中查询数据状态,
  • 当用户使用State操作flink提供了state backend机制用于存储状态信息,其中计算状态可以存储在Java的堆内和堆外,这取决于采取的statebackend机制。
  • 配置Statebackend不会影响应用的处理逻辑。
  • State状态分类
	eyed State:针对KeyedStream上的一些操作,基于key和其对应的状态
	Operator State:针对一个non-keyed state操作的状态,
	   例如FlinkKafkaConsumer就是一个Operators state,每个Kafka Conusmer实例管理该实例
消费Topic分区和偏移量信息等

	上述两种状态可以以managed 和 raw形式存储state,其中managed状态表示状态的管理托管给Flink来确定数
据结构(“ValueState,ListState”等)。

	Flink在运行期间自动的使用checkpoint机制持久化计算状态。Flink所有的function都支持managed形式的 ,
RawState是采取在Checkpoint的时候序列化字节持久化状态,Flink并不知道state的存储结构,仅仅是在定
义Operators的时候才会使用raw形式存储状态。Flink推荐使用managed 形式,因为这样Flink在当任务并行
度发生变化的时候状态是可以重新分发,并且有着更好的内存管理。

5.1、实时包状态的启动

-- flink 有状态的进行启动动务
-- 1、初始启动
realtime-calculate-mode-starta.sh
/opt/flink-1.11.2/bin/flink run -m yarn-cluster -yjm 1024 -ytm 2048 -ys 8 -ynm calculte-mode -c com.fmbank.bigdata.calculatemode.CalculateModeStreamingJob
/home/realtime/calculatemode/real-time-calculate-mode-1.1.4.jar

-- 产生 JobID  59da6be897650d6aa310de54830db5c6  application  application_1657183233005_0361
2022-07-11 14:18:47,046 INFO  org.apache.hadoop.yarn.client.api.impl.YarnClientImpl        [] - Submitted application application_1657183233005_0361
2022-07-11 14:18:47,046 INFO  org.apache.flink.yarn.YarnClusterDescriptor                  [] - Waiting for the cluster to be allocated
2022-07-11 14:18:47,049 INFO  org.apache.flink.yarn.YarnClusterDescriptor                  [] - Deploying cluster, current state ACCEPTED
2022-07-11 14:19:02,481 INFO  org.apache.flink.yarn.YarnClusterDescriptor                  [] - YARN application has been deployed successfully.
2022-07-11 14:19:02,507 INFO  org.apache.flink.yarn.YarnClusterDescriptor                  [] - Found Web Interface cdhmaster01:41389 of application 'application_1657183233005_0361'.
Job has been submitted with JobID 59da6be897650d6aa310de54830db5c6

-- 2、cancal_20220711.sh 编辑取消指令
JobID 和 application 在flink-web页面 runjob里面可以获取到 
/opt/flink-1.11.2/bin/flink cancel -s hdfs://nameservice1/user/realtime/user-mode/ 59da6be897650d6aa310de54830db5c6  -yid application_1657183233005_0361

-- 执行取消指令 --生成状态检查文件 /user/realtime/user-mode/savepoint-59da6b-901c2f7f2abf
[realtime@cdhmaster01 calculatemode]$ more cancal_20220711.sh
/opt/flink-1.11.2/bin/flink cancel -s hdfs://nameservice1/user/realtime/user-mode/ 59da6be897650d6aa310de54830db5c6  -yid application_1657183233005_03
61

[realtime@cdhmaster01 calculatemode]$ sh cancal_20220711.sh
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/opt/flink-1.11.2/lib/log4j-slf4j-impl-2.12.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/opt/cloudera/parcels/CDH-5.12.2-1.cdh5.12.2.p0.4/jars/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]
DEPRECATION WARNING: Cancelling a job with savepoint is deprecated. Use "stop" instead.
Cancelling job 59da6be897650d6aa310de54830db5c6 with savepoint to hdfs://nameservice1/user/realtime/user-mode/.
2022-07-11 14:22:35,642 WARN  org.apache.flink.yarn.configuration.YarnLogConfigUtil        [] - The configuration directory ('/opt/flink-1.11.2/conf') already contains a LOG4J config file.If you want to use logback, then please delete or rename the log configuration file.
2022-07-11 14:22:35,913 INFO  org.apache.hadoop.yarn.client.RMProxy                        [] - Connecting to ResourceManager at cdhnode02/10.1.52.157:8032
2022-07-11 14:22:36,395 INFO  org.apache.flink.yarn.YarnClusterDescriptor                  [] - No path for the flink jar passed. Using the location of class org.apache.flink.yarn.YarnClusterDescriptor to locate the jar
2022-07-11 14:22:37,013 INFO  org.apache.flink.yarn.YarnClusterDescriptor                  [] - Found Web Interface cdhmaster01:41389 of application 'application_1657183233005_0361'.
Cancelled job 59da6be897650d6aa310de54830db5c6. Savepoint stored in hdfs://nameservice1/user/realtime/user-mode/savepoint-59da6b-901c2f7f2abf

-- 3、有状态的进行启动 找到 checkpoint
[realtime@cdhmaster01 calculatemode]$ more realtime-calculate-mode-start.sh
/opt/flink-1.11.2/bin/flink run --allowNonRestoredState -s hdfs://nameservice1/user/realtime/user-mode/savepoint-59da6b-901c2f7f2abf -m yarn-cluster -yjm
1024 -ytm 2048 -ys 8 -ynm calculte-mode -c com.fmbank.bigdata.calculatemode.CalculateModeStreamingJob /home/realtime/calculatemode/real-time-calculate-mod
e-1.1.4.jar

5.2、Managed Keyed State

  • ValueState: 该状态记录的是key中所包含的值,例如可以使用upate(T)更新值或者T value()获取值。
  • ListState: 可以存储一系列的元素,可以使用add(T) 或者addAll(List)添加元素,Iterable get()获取元素,或者update(List)更新元素。
  • ReducingState:保留一个值代表所有的数据的聚合结果,在创建的时候需要给定ReduceFunction实现计算逻辑,调用add(T)方法实现数据的汇总。
  • AggregatingState<IN, OUT>:保留一个值代表数据的局和结果,在创建时候需要给一个AggregateFunction实现计算逻辑,提供add(IN)方法实现数据的累计 。
  • FoldingState<T, ACC>:保留一个值代表所有数据的聚合结果。但是和ReduceState相反的是不要求聚合的类型和最终类型保持一致,在创建FoldingState的时候需要指定一个FoldFunction,调用add(T)方法实现数据汇总。FoldingState在Flink 1.4版本过时,预计可能会在后期版本废除。用户可以使用AggregatingState替换。
  • MapState<UK, UV>:存储一些列的Mapping,使用 put(UK, UV)和putAll(Map<UK, UV>)添加元素数据可以通过
  • get(UK)获取一个值,可以通过entries(), keys()and values()方法查询数据。
	以上所有的State都提供了一个clear()方法清除当前key所对应的状态值。
	需要注意的是这些状态只可以在一些带有state的接口中使用,状态不一定存储在内部,
但可能驻留在磁盘或其他位置。从状态获得的值取决于input元素的key。

六、waterMark的使用

6.1、window划分:

	1、window是flink中划分数据一个基本单位。
	2、window的划分方式是固定的,默认会根据自然时间划分window,前闭后开。
		:如window为10s,时间特征是eventTime(同样适用于Ingestion Time   和 process Time),则
		  数据1时间是00:00:01.345 ,归属于w1。
			      [00:00:10~00:00:20),w2
			      [00:00:20~00:00:30),w3
		          00:01:02.152,window=10s归属于w7	
   3、划分了window之后,触发window的计算,就可以得到这个window中的聚合结果了
   
   4、eventTime和基于processTime计算最大的不同点就是在触发window的计算时刻不相同,
   		processTime,在window的endTime等于当前时间的时候就会触发计算。
   		eventTime因为数据有可能是乱序的,所以需要watermark的协助,完成window计算的触发。
  • 水位线
	水位线就代表了当前的事件时间时钟,而且可以在数据的时间戳基础上加一些延迟来保证不丢数据,这一点对于乱序流的正确处理非常术要。
	水位线的特性:
	• 水位线是插入到数据流中的一个标记,可以认为是一个特殊的数据
	•水位线主要的内容站一个时间戳,用来表示当前小件时间的进展
	•水位线是基于数据的时间戳生成的
	•水位线的时间戳必须单调递增,以确保任务的事件时间时钟一宜向前推进
	•水位线可以通过设汽延迟,来保证正确处理乱序数据
	• 一个水位线Watermark⑴,表示在当前流中事件时间已经达到了时间戳t.这代表t之
	前的所有数据都到齐了,之后流中不会出现时间微t'< I的数据

	水位线是Fiink流处理中保证结果正确性的核心机制,它往往会踉窗口一起配合,完成对
乱序数据的正确处理。
  • 水位线生产原则
	完美的水位线是“绝对正确”的,也就是一个水位线一旦出现,就表示这个时间之前的数
据已经全部到齐、之后再也不会出现了。不过如果要保证绝对正确,就必须等足够长的时间,
这会带来更高的延迟。
	如果我们希望计算的结果能更加准确,那可以将水位线的延迟设先得更高一些,等待的时间
越长,自然也就越不容易漏掉数据。不过这样做的代价是处理的实时性降低了,我们可能为极
少数的迟到数据增加J'很多不必要的延迟.
	如果我们希望处理得更快、实时性更强,那么可以将水位线延迟设得低一典。这种情况下,
可能很多迟到数据会在水位线之后才到达,就会导致窗口遗漏数据,计算结果不准确.当然,
如果我们对准确性完全不考虑、一昧地追求处理速度,可以直接使用处理时间语义,这在理论
上可以得到以低的延迟。
	所以Flink中的水位线,其实是流处理中对低延迟和结果正确性的一个权衡机制,而且把
控制的权力交给了程序员,我们可以在代码中定义水位线的生成策略。
  • 水位线生成策略

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

6.2、 eventTime:

  • 原始的消息中提取过来的事件事件。 基于eventTime计算,需要注意两点:
	//1、首先要设置数据流的时间特征
	env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
	
	//2、提取WaterMark,

6.3、提取WaterMark

6.3.1、定时提取watermark

	对应AssignerWithPeriodicWatermarks,这种方式会定时提取更新wartermark。

6.3.2、伴随event到来就提取watermark

	对应AssignerWithPunctuatedWatermarks,每一个event到来的时候,就会提取一次Watermark。
	这样的方式当然设置watermark更为精准,但是当数据量大的时候,频繁的更新wartermark会比较影响性能。
	通常情况下采用定时提取就足够了。

6.3.3、两种周期性的 提取 watermark。

  • 需要注意的是watermark的提取工作在taskManager中完成,意味着这项工作是并行进行的的,而watermark是一个全局的概念,就是一个整个Flink作业之后一个warkermark。那么warkermark一般是怎么提取呢,这里引用官网的两个例子来说明.
  • 第一个例子中extractTimestamp方法,在每一个event到来之后就会被调用,这里其实就是为了设置watermark的值,关键代码在于Math.max(timestamp,currentMaxTimestamp),意思是在当前的水位和当前事件时间中选取一个较大值,来让watermark流动。为什么要选取最大值,因为理想状态下,消息的事件时间肯定是递增的,实际处理中,消息乱序是大概率事件,所以为了保证watermark递增,要取最大值。而getCurrentWatermarker会被定时调用,可以看到方法中减了一个常量,就这样,不断从eventTime中提取并更新watermark.
public class BoundedOutOfOrdernessGenerator implements AssignerWithPeriodicWatermarks<MyEvent> {
    private final long maxOutOfOrderness = 3500; // 3.5 seconds
    private long currentMaxTimestamp;
    @Override
    public long extractTimestamp(MyEvent element, long previousElementTimestamp) {//上一个元素时间戳
        long timestamp = element.getCreationTime();
        currentMaxTimestamp = Math.max(timestamp, currentMaxTimestamp);
        return timestamp;
    }
    @Override
    public Watermark getCurrentWatermark() {
        //水印作为当前最高时间戳减去无序界限
        return new Watermark(currentMaxTimestamp - maxOutOfOrderness);
    }
}
  • 第二个例子,直接取系统当前时间减去一个常量,作为新的watermark
public class TimeLagWatermarkGenerator implements AssignerWithPeriodicWatermarks<MyEvent> {
 
	private final long maxTimeLag = 5000; // 5 seconds
	@Override//
	public long extractTimestamp(MyEvent element, long previousElementTimestamp) {//上一个元素时间戳
		return element.getCreationTime();
	}
	@Override
	public Watermark getCurrentWatermark() {
		// 将水印作为当前时间减去最大时间延迟返回
		return new Watermark(System.currentTimeMillis() - maxTimeLag);
	}
}
  • 应用
import org.apache.commons.lang3.time.FastDateFormat
import org.apache.flink.api.java.tuple.Tuple
import org.apache.flink.streaming.api.TimeCharacteristic
import org.apache.flink.streaming.api.functions.AssignerWithPeriodicWatermarks
import org.apache.flink.streaming.api.scala.function.WindowFunction
import org.apache.flink.streaming.api.scala.{DataStream, StreamExecutionEnvironment}
import org.apache.flink.streaming.api.watermark.Watermark
import org.apache.flink.streaming.api.windowing.assigners.TumblingEventTimeWindows
import org.apache.flink.streaming.api.windowing.time.Time
import org.apache.flink.streaming.api.windowing.windows.TimeWindow
import org.apache.flink.util.Collector
 
import scala.collection.mutable.ArrayBuffer
 
/**
  * 水印测试
  */
object WaterMarkFunc01 {
  // 线程安全的时间格式化对象
  val sdf: FastDateFormat = FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss:SSS")
  def main(args: Array[String]): Unit = {
    val hostName = "s102"
    val port = 9000
    val delimiter = '\n'
    val env = StreamExecutionEnvironment.getExecutionEnvironment
    // 将EventTime设置为流数据时间类型
    env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)
    env.setParallelism(1)
     输入数据格式:name:时间戳
      // flink:1559223685000

    val streams: DataStream[String] = env.socketTextStream(hostName, port, delimiter)
    import org.apache.flink.api.scala._
    val data = streams.map(data => {
      // 输入数据格式:name:时间戳
      // flink:1559223685000
      try {
        val items = data.split(":")
        (items(0), items(1).toLong)
      } catch {
        case _: Exception => println("输入数据不符合格式:" + data)
          ("0", 0L)
      }
    }).filter(data => !data._1.equals("0") && data._2 != 0L)
 
    //为数据流中的元素分配时间戳,并定期创建水印以监控事件时间进度
    val waterStream: DataStream[(String, Long)] = data
    .assignTimestampsAndWatermarks(new AssignerWithPeriodicWatermarks[(String, Long)] {
      // 事件时间
      var currentMaxTimestamp = 0L
      val maxOutOfOrderness = 3000L
      var lastEmittedWatermark: Long = Long.MinValue
 
      // Returns the current watermark
      override def getCurrentWatermark: Watermark = {
        // 允许延迟三秒
        val potentialWM = currentMaxTimestamp - maxOutOfOrderness
        // 保证水印能依次递增
        if (potentialWM >= lastEmittedWatermark) {
          lastEmittedWatermark = potentialWM
        }
        new Watermark(lastEmittedWatermark)
      }
 
      // Assigns a timestamp to an element, in milliseconds since the Epoch
      override def extractTimestamp(element: (String, Long), previousElementTimestamp: Long): Long = {
        // 将元素的时间字段值作为该数据的timestamp
        val time = element._2
        if (time > currentMaxTimestamp) {
          currentMaxTimestamp = time
        }
        val outData = String.format("key: %s    EventTime: %s    waterMark:  %s",
          element._1,
          sdf.format(time),
          sdf.format(getCurrentWatermark.getTimestamp))
        println(outData)
        time
      }
    })
    val result: DataStream[String] = waterStream.keyBy(0)// 根据name值进行分组
      .window(TumblingEventTimeWindows.of(Time.seconds(5L)))// 5s跨度的基于事件时间的翻滚窗口
      .apply(new WindowFunction[(String, Long), String, Tuple, TimeWindow] {
        override def apply(key: Tuple, window: TimeWindow, input: Iterable[(String, Long)], out: Collector[String]): Unit = {
        
          val timeArr = ArrayBuffer[String]()
          val iterator = input.iterator
          while (iterator.hasNext) {
            val tup2 = iterator.next()
            timeArr.append(sdf.format(tup2._2))
          }
          
          val outData = String.format("key: %s    data: %s    startTime:  %s    endTime:  %s",
            key.toString,
            timeArr.mkString("-"),
            sdf.format(window.getStart),
            sdf.format(window.getEnd))
            
          out.collect(outData)
          
        }
      })
    result.print("window计算结果:")
    env.execute(this.getClass.getName)
  }
}

在这里插入图片描述

七、处理函数

7.1、ProcessFunction

  • 最基本的处理函数,基于DataStream直接调用.process。时作为参数传入。
    在这里插入图片描述

7.2、Keyed ProcessFunction

  • 对流按键分区后的处理函数,基于KeyedStream调用.process。时作为参数传入。要想使用定时器,比如基于KeyedStream。
  • 可以注册定时器,ProcessFunction不能
    在这里插入图片描述
    在这里插入图片描述

7.3、ProcessWindowFunction

  • 开窗之后的处理函数,也是全窗口函,数的代表。it J- WindowedStrcam调用.process。时作为参数传入.

7.4、ProcessAllWindowFunction

  • 同样是开窗之后的处理函数,基于AllWindowedStream调用.process。时作为参数传入.

7.5、CoProcessFunclion

  • 合并(connect)两条流之后的处理函数,基丁• ConnectedStreams调用.process。时作为参数传入。关于流的连接合并操作,我们会在后续章节详细介绍。

7.6、ProcessJoinFunclion

  • 间隔连接(interval join)两条流之后的处理函数,基于IntervalJoined调用.process。时作为
    参数传入。

7.7、BroadcastProcessFunction

  • 广播连接流处理函数,基于BroadcaslConnectcdStrcam调用.process时作为参数传入。这里的“广播连接流"BroadcastConnecledStream,是个未keyBy的普通DataSlream与一个广播流(BroadcastSlream)做连接(conned)之后的产物。

7.8、 KeyedBroadcastProcessFunction

  • 按键分区的广播连接流处理函数,同样是基于BroadcastConnectedStream谓用.process时作为参数传入。与BroadcastProcessFunclion不同的是,这时的广播连接流,是一个KeyedStreain与广播流(BroadcastSlrcam)做连接之后的产物。

八、流的分离与合流

8.1、分流OutPutTagde使用

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

8.2、合流union类型相同的多条流

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.3、Connect连接类型不同的两条流

  • 为了处理更加灵活,连接操作允许流的数据类型不同。但我们知道一个DalaStream中的数 据 只 能 有 唯 一 的 类 型 ,所 以 连 接 得 到 的 并 不 是 DataStream ,而 是 一 个 “ 连 接 流 ”(ConncctedStream)连接流可以看成是两条流形式上的“统一”,被放在了一个同一个流中:事实上内部仍保持各自的数据形式不变,彼此之间是相互独立的。要想得到新的DataStreain.还需要进一步定义一个“同处理”(co-proccss)转换操作,用来说明对于不同来源、不同类型的数据,怎样分别进行处理转换、得到统一的输出类型.所以整体上来,两条流的连接就像是“一国两制”,两条流可以保持各自的数据类型、处理方式也可以不同,不过最终还是会统一到同一个DataStream中,如图所示。
    *在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 定义状态
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 定时器
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

8.4、Join连接

  • 可以发现,根据某个key合并两条流,与关系型数据库中表的join操作非常相近。事实上,Flink中两条流的conned操作、就可以通过keyBy指定犍进行分组后合并,实现了类似于SQL中的join操作;另外connect支持处惬函数,可以使用自定义状态和TimcrScrvicc灵活实现各种需求,其实已经能够处理双流join的大多数场景.
  • 不过处理函数是底层接口,所以尽管connccl能做的事情多,但在一兆具体应用场景下还是显得太过抽象了。比如,如果我们希望统计固定时间内两条流数据的匹配情况,那就需要设准定时器、自定义触发逻辑来实现——其实这完全可以用窗口(window) 来衣示。为了更方便地实现基于时间的合流操作,Flink的DataStrcina API提供了内置的join算子.

8.4.1、窗口联结

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值