Flink

本文深入浅出地介绍了Flink这一高性能大数据处理引擎,包括其低延迟特性、集群搭建步骤、编程入门及DataStream算子等核心内容。具体讲解了Flink与Spark-Streaming的比较、JobManager与TaskManager的角色、不同数据源读取方式,以及shuffle、rebalance等数据分区策略,适合想要掌握Flink技术的开发者阅读。
摘要由CSDN通过智能技术生成

Flink

一、Flink简介

Flink是一种高吞吐、低延迟、高性能的分布式大数据处理引擎。相比Spark-Streaming延迟更低。

二、Flink集群搭建

JobManager:即Master
TaskManager:即Worker
安装Flink,Flink有自带的stand-alone模式,
设置参数:master为linux01;worker为linux02和linux03
每个worker可用的槽数为2

启动命令:bin/start-cluster.sh
web端口:8081

任务提交方式:

第一种方式:通过web页面进行提交任务,同时指定参数

第二种方式:使用命令行提交

/opt/apps/flinkflink-1.13.2/bin/flink run 
-m linxu01:8081 
-p 2 
-c com.doit.WorkCount_Java 
/opt/flink-java-1.0-SNAPSHOT.jar  
linux01 9111 (--hostname node-1.51doit.cn --port 8888)
参数说明:
-m指定主机名后面的端口为JobManager的REST的端口,而不是RPC的端口,RPC通信端口是6123
-p 指定是并行度
-c 指定main方法的全类名
指定jar包位置  args参数

三、编程入门

四、DataStream算子

4.1 Flink Source

在Flink中,Source主要负责数据的读取,有单并行和多并行之分,而算子都是多并行的

4.1.1 基于File的数据源

readTextFile

(1)使用TextInputFormat方式诉求文本文件,并将结果作为String返回。调用readTextFile方法,      只传入读取数据的路径,那么该方法创建的DataStream使用一个有限的数据流数据读取完成,        job就退出了。

(2)并行度跟job的并行度保持一致,默认是cpu的逻辑核数。

(3)可以设置并行度

     DataStreamSource<String> lines =            	                     env.readTextFile("E:\\work2\\mrdata\\data\\date.txt");
    // DataStreamSource<String> lines = env.readTextFile("E:\\work2\\mrdata\\data\\date.txt").setParallelism(4);

(4)readTextFile底层调用的是readFile
	String path = "/Users/xing/Desktop/a.txt";
    TextInputFormat format = new TextInputFormat(new Path(path));
    DataStreamSource<String> lines = 
    env.readFile(format, path, FileProcessingMode.PROCESS_CONTINUOUSLY, 1000);
    这里设置了持续读取文件,每间隔1秒就读一次,但有个缺陷,每次都是从头读取,不会记录偏移量

4.1.2 基于集合的数据源

fromCollection fromElements fromParallelCollection generateSequence

 * 将集合变成DataStream
 *
 * 集合类的Source都是有限数据流,即将集合内的数据读完后,就停止了
 *
 * fromCollection 创建的DataStream并行度为必须为1
 
 * fromElements 创建的DataStream并行度也是必须为1
 
 *fromParallelCollection 创建的DataStream并行度可以是多个
 
 *generateSequence 创建的DataStream并行度可以是多个


List<String> list = 
Arrays.asList("spark hadoop", "flink spark", "spark", "hive flume hadopp");
DataStreamSource<String> lines = env.fromCollection(list);


DataStreamSource<String> lines
= env.fromElements("spark", "hadoop", "flink", "hive");

4.1.3 基于Socket的数据源

socketTextStream

Socket中读取信息,元素可以用分隔符分开。即从一个端口中读取数据。

socket的并行度必须为1,所以只有一个消费者读数据

需要现在linux中使用  nc -lk 9111 开启一个9111的端口

DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);

4.1.4 基于kafka的数据源

addSource

 * 使用FlinkKafkaSourceKafka中读取数据
 *
 *并行度为 逻辑核数 
 *
 * FlinkKafkaSource创建的DataStream的多并行的,可以有多个消费者从Kafka中读取数据
 
 *前提要开启kafka 和zookeeper  可以创建多个消费者
 
   Properties properties = new Properties();
   properties
   .setProperty("bootstrap.servers","linux01:9092,linux02:9092,linux03:9092");

   properties.setProperty("group.id", "test11");
   properties.setProperty("auto.offset.reset","earliest");

   DataStream<String> lines =env
   .addSource(new FlinkKafkaConsumer<>("test1", new SimpleStringSchema(), properties));

4.1.5 自定义Source

1、实现SourceFunction接口 :单并行

单并行的Source:并行度为1
实现SourceFunction接口,重写run方法 和cancel方法
/**
   * run方法是Source对应的Task启动后,会调用该方法,用来产生数据
   * 如果是一个【有限】的数据流,run方法中的逻辑执行完后,Source就停止了,整个job也停止了
   * 如果是一个【无限】的数据流,run方法中会有while循环,不停的产生数据

   */1:自定义一个单并行的source,有限数据流
public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<String> lines = env.addSource(new mySource());

        int parallelism = lines.getParallelism();
        System.out.println("source的并行度为"+parallelism);
        lines.print();
        env.execute();


    }


    //自定义source
    private static class mySource implements SourceFunction<String> {
   /**
   * run方法是Source对应的Task启动后,会调用该方法,用来产生数据
   * 如果是一个【有限】的数据流,run方法中的逻辑执行完后,Source就停止了,整个job也停止了
   * 如果是一个【无限】的数据流,run方法中会有while循环,不停的产生数据

   */
        @Override
        public void run(SourceContext<String> ctx) throws Exception {
            List<String> words = Arrays.asList("spark", "hadoop", "flink", "hive");
            for (String word : words) {
                ctx.collect(word);
            }
        }

        @Override
        public void cancel() {

        }
    }2:自定义一个单并行的source,无限数据流
 public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<Integer> lines = env.addSource(new mySource());

        int parallelism = lines.getParallelism();

        System.out.println("source的并行度为"+parallelism);
        lines.print();

        env.execute();


    }


    //自定义source
    private static class mySource implements SourceFunction<Integer> {
      
        @Override
        public void run(SourceContext<Integer> ctx) throws Exception {
            int i =0 ;
            while(true){
                ctx.collect(i);
                i++;
                Thread.sleep(1000);
            }
        }

        @Override
        public void cancel() {

        }
    }

2、实现ParallelSourceFunction接口 :多并行

 * 实现了ParallelSourceFunction接口的Source,创建的DataStream就是多并行
 *
 * 自定义一个parallel sources (多并行的Source*
 * 这个例子是一个【无限】的数据流,run方法中会不停的产生数据(while循环)
*/

public static void main(String[] args) {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

    }

    //自定义多并行的source
    private static class MyParallelSource implements ParallelSourceFunction<Integer> {

        private boolean flag = true;
        @Override
        public void run(SourceContext<Integer> ctx) throws Exception {
            int i = 0;
            while(flag){
                ctx.collect(i);
                i++;
                Thread.sleep(1000);
            }
        }

        @Override
        public void cancel() {
            flag = false;
        }
    }

3、继承RichParallelSourceFunction :多并行,而且有生命周期方法

自定义多并行的Source,继承RichParallelSourceFunction 便能拥有生命周期方法(open  run  cancel close)

在subtask中方法的执行顺序(生命周期方法):
open(一次) -> run(一次) -> cancel(一次)-> close(一次)
  
该并行度为8,所以open run cancel close 方法都会调用8次  而在自定义MyParallelSource的构造方法只会被调用一次,因为是在Driver端创建

*/
例子:
 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<Integer> lines = env.addSource(new MyParallelSource());

        int parallelism2 = lines.getParallelism();
        System.out.println("自定义的实现SourceFunction的Source并行度为:" + parallelism2);


        lines.print();

        env.execute();
    }

    //多并行的Source,继承了RichParallelSourceFunction
    // 即调用完Source后得到的DataStream中对应的数据类型
    private static class MyParallelSource extends RichParallelSourceFunction<Integer> {

        public MyParallelSource() {
            System.out.println("constructor invoke ~~~~~~~~~~~~");
        }

        @Override
        public void open(Configuration parameters) throws Exception {
            //getRuntimeContext().getIndexOfThisSubtask() 获取当前subTask的Index
            System.out.println("subtask:" + getRuntimeContext().getIndexOfThisSubtask() + " @@@@ open 方法被调用了");

        }

        @Override
        public void close() throws Exception {
            System.out.println("subtask:" + getRuntimeContext().getIndexOfThisSubtask() + " &&&& close 方法被调用了");

        }

        private boolean flag = true;

        @Override
        public void run(SourceContext<Integer> ctx) throws Exception {
            System.out.println("subtask:" + getRuntimeContext().getIndexOfThisSubtask() + " Run方法被调用了~~~~~~~~~~~~~");
            int i = 0;
            while (flag) {
                ctx.collect(i);
                i += 1;
                Thread.sleep(2000);
            }
        }


        @Override
        public void cancel() {
            System.out.println("subtask:" + getRuntimeContext().getIndexOfThisSubtask() + " cancel方法被调用了!!!!!!!!!!!!");
            flag = false;
        }
    }

4.1.6 总结

file的文件源,无法做到实时,相当于是批处理,相当于是做离线

基于集合的数据源,一般只是用来做测试

基于socket的数据源,一般也是用来做测试,因为读取数据的并行度必须为1,影响效率

基于kafka的数据源一般用于生产环境中,因为多个并行度,而且可以有多个生产者

有时候需要自定义Source数据源,可以定义单并行,多并行,以及多并行带生命周期方法

4.2 Sink 算子

sink算子 相当于spark中的action算子

4.2.1 Print

 * 将数据以标准数据打印,
 * PrintSink是多并行的



    public static void main(String[] args) throws Exception{

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //整个job的并行度
        int parallelism = env.getParallelism();
        System.out.println("当前job的执行环境默认的并行度为:" + parallelism);

        //DataStreamSource是DataStream的子类
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        lines.print("doit ");

        //启动并执行
        env.execute();
    }

4.2.2 writeAsText

  WriteText 将数据写到文件中 该sink方法已经过时,了解即可

public static void main(String[] args) throws Exception{

        //创建DataStream,必须调用StreamExecutitionEnvriroment的方法
        //StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());


        //整个job的并行度
        int parallelism = env.getParallelism();
        System.out.println("当前job的执行环境默认的并行度为:" + parallelism);

        //DataStreamSource是DataStream的子类
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        lines.writeAsText("/Users/xing/Desktop/out", FileSystem.WriteMode.OVERWRITE);


        //启动并执行
        env.execute();
    }

4.2.3 自定义Sink

继承RichSinkFunction 调用的时候使用addSink

 /**
 自定义sink方法:实现跟print一样的功能

  定义一个类,继承RichSinkFunction 重写invoke方法

  invoke是来一条数据就执行一次

  open和close生命周期方法是有几个并行度就执行几次,每个task各执行一次
  
  调用该sink方法的时候是使用:addSink

*/

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "linux01:9092,linux02:9092,linux03:9092");
        properties.setProperty("group.id", "test11");
        properties.setProperty("auto.offset.reset","earliest");

        DataStream<String> lines = env.addSource(new FlinkKafkaConsumer<>("test1", new SimpleStringSchema(), properties));


        //lines.print() print底层使用的就是addSink
        //sink可以指定前缀名,可以指定并行度
        lines.addSink(new mySink()).name("我的sink").setParallelism(3);

        env.execute("test");

    }

    //自定义printSink
    private static class mySink extends RichSinkFunction<String> {
        @Override
        public void open(Configuration parameters) throws Exception {
            System.out.println("open method invoked ~~~~~~~");
        }

        @Override
        public void close() throws Exception {
            System.out.println("close method invoked ########");
        }

        @Override
        public void invoke(String value, Context context) throws Exception {
            System.out.println("invoke method invoked $$$$$$$$$$");
            int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
            System.out.println((indexOfThisSubtask+1) + ">" + value);
        }
    }

4.3 Transformation 算子

4.3.1 map

/*

  map :将数据一条一条的处理,处理一条返回一条
  多并行
  底层使用的是transform

使用lambda表达式,需要注意两个问题:
   *泛型是否会自动推导:
        里面的泛型有时候不能自动推导,所以要进行类型指定
        当参数中还有类型的时候,比如( line,  out) ->,而out是一个collect类型,其中有泛           型,此时不指定类型就会有泛型丢失,
        就需要指定数据类型(String line, Collector<Tuple2<String, Integer>> out) ->
   *是否需要使用returns
       不需要使用returns指定返回结果的数据类型的场景:
        ---->输入与输出的数据类型一致,输入一行返回一行
       需要使用returns指定返回结果的数据类型的场景:
         1)如果输入的数据类型和输出的数据类型不一致
         2)如果输入一行,返回多行,比如flatMap
  
*/
 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        DataStreamSource<String> lines = env.socketTextStream("Linux01", 8888);

        //做映射
        //调用完map算子后生成的DataStream是单并行的还是多并行的?答案:多并行的
        SingleOutputStreamOperator<String> upperDataStream = lines.map(new MapFunction<String, String>() {
            @Override
            public String map(String s) throws Exception {
                return s.toUpperCase();
            }
        }).setParallelism(2);


//使用lambda表达式的方式
// SingleOutputStreamOperator<String> upperDataStream = lines.map(s -> s.toUpperCase()).setParallelism(2);



/*
使用lambda表达式的方式-->但是输入和输出的数据类型不一致
SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = words
        .map(w -> Tuple2.of(w, 1))
        .returns(Types.TUPLE(Types.STRING, Types.INT));
 */
   
        System.out.println("map算子对应的DataStream的并行度:" + upperDataStream.getParallelism());


        upperDataStream.print();

        //启动并执行
        env.execute();
    }

使用底层方法来实现map的功能:

 传入算子名称,返回类型,具体的运算逻辑
  transform("MyMap", TypeInformation.of(String.class), new StreamMap<>(func));
// MapFunction泛型是输入和输出的数据类型
func = new MapFunction<String, String>().....

public static void main(String[] args) throws Exception{

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        DataStreamSource<String> words = env.socketTextStream("localhost", 8888);

        //MapFunction是在JobManager端被new的
        MapFunction<String, String> func = new MapFunction<String, String>() {
            @Override
            public String map(String s) throws Exception {
                return s.toUpperCase();
            }
        };

        //调用Transformation,传入算子名称,返回类型,具体的运算逻辑
        SingleOutputStreamOperator<String> upperStream = words
                .transform("MyMap", TypeInformation.of(String.class), new StreamMap<>(func));

        upperStream.print();

        //启动并执行
        env.execute();

    }

4.3.2 map 底层的StreamMap

map filter flatMap 底层都是用的processElement方法,来一条处理一条

   map算子 不断的底层实现
  1)直接使用
  2)使用transForm来实现map
  3)使用自定义的StreamMap 来实现
  4)将自定义的StreamMap完善,
  5)将自定义的StreamMap完善,加入泛型的使用

 /*
    自定义Streammap:继承一个类,实现一个接口,重写processElement方法
    
    输入的数据类型一定是String,因为读数据的时候就是按照String读过来一行
    自定义Streammapu需要实现OneInputStreamOperator接口,但这个接口有太多的抽象方法要实     现,所以再继承AbstractStreamOperator,这样就可以省去重写一些没必要的方法
    
    只重写processElement,该方法中写对数据的处理逻辑,原则上是来一条处理一条
    */

MyStreamMap1 --版本1

public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);

        SingleOutputStreamOperator<String> myStreamMap1 = lines
                .transform("myStreamMap1"
                        , TypeInformation.of(String.class)
                        , new MyStreamMapDemo1());


        myStreamMap1.print();
        env.execute();

    }

    /*
    自定义Streammap:继承一个类,实现一个接口,重写processElement方法
    
    只重写processElement,该方法中写对数据的处理逻辑,原则上是来一条处理一条
    */
    private static class MyStreamMapDemo1 extends AbstractStreamOperator<String> implements OneInputStreamOperator<String,String> {
        @Override
        public void processElement(StreamRecord<String> element) throws Exception {
            String in = element.getValue();
            String res = in + "小强";

            output.collect(element.replace(res));
        }
    }

MyStreamMap2 --版本2

自定义StreamMapMyStreamMap1基础之上,将数据的处理逻辑作为一个函数 传到StreamMappublic static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);


        MapFunction<String, String> func = new MapFunction<String, String>() {
            @Override
            public String map(String value) throws Exception {
                return value + "小强";
            }
        };

        SingleOutputStreamOperator<String> myStreamMap1 = lines
                .transform("myStreamMap1"
                        , TypeInformation.of(String.class)
                        , new MyStreamMapDemo2(func));


        myStreamMap1.print();
        env.execute();

    }

    /*
    自定义Streammap
    将处理逻辑作为函数的参数 传进来

    */
    private static class MyStreamMapDemo2 extends AbstractStreamOperator<String> implements OneInputStreamOperator<String,String> {

        private MapFunction<String, String> mapFunction;
        public MyStreamMapDemo2(MapFunction<String, String> mapFunction) {
            this.mapFunction = mapFunction;
        }

        @Override
        public void processElement(StreamRecord<String> element) throws Exception {


            output.collect(element.replace(
                    mapFunction.map(element.getValue())
            ));
        }
    }

MyStreamMap3 --版本3

/*

    自定义StreamMap
    在MyStreamMap1基础之上,将数据的处理逻辑作为一个函数 传到StreamMap中
    在MyStreamMap2基础之上,全都使用泛型,实现跟源码一样的功能
*/

 public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);


        MapFunction<String, String> func = new MapFunction<String, String>() {
            @Override
            public String map(String value) throws Exception {
                return value + "小强";
            }
        };

        SingleOutputStreamOperator<String> myStreamMap1 = lines
                .transform("myStreamMap1"
                        , TypeInformation.of(String.class)
                        , new MyStreamMapDemo3<>(func));


        myStreamMap1.print();
        env.execute();

    }

    /*
    自定义Streammap

    加入泛型的使用,使程序更加灵活

    需要在类名后定义泛型MyStreamMapDemo3<I,O> I代表输入,O代表输出

    继承的是AbstractUdfStreamOperator 要传一个函数MapFunction 使用哪个算子就传哪个Function

    */
    private static class MyStreamMapDemo3<I,O> extends AbstractUdfStreamOperator<O,MapFunction<I,O>> implements OneInputStreamOperator<I,O> {
        public MyStreamMapDemo3(MapFunction<I, O> userFunction) {
            super(userFunction);
        }

        @Override
        public void processElement(StreamRecord<I> element) throws Exception {
            output.collect(element.replace(userFunction.map(element.getValue())));
        }
    }

4.3.3 reduce

/*
reduce:
聚合函数,当字段第一次出现的时候,会直接进行输出,当字段再来的时候,会进行判断该字段是否已经存在,假如存在,则将该字段的聚合后的值取出来,然后作为第一个值,将新传过来的作为第二个值,进行聚合,然后将这个字段的聚合结果进行更新然后输出。

  tp1.f1 = tp1.f1 + tp2.f1;
  return tp1;
*/

 public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<String> words = env.socketTextStream("linux01", 9111);

        SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = words.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String s) throws Exception {
                return Tuple2.of(s, 1);
            }
        });

        KeyedStream<Tuple2<String, Integer>, Tuple> keyed = wordAndOne.keyBy("f0");
        SingleOutputStreamOperator<Tuple2<String, Integer>> reduced = keyed.reduce(new ReduceFunction<Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> reduce(Tuple2<String, Integer> tp1, Tuple2<String, Integer> tp2) throws Exception {
                tp1.f1 = tp1.f1 + tp2.f1;
                return tp1;
            }
        });

        reduced.print();
        env.execute();

    }

4.3.4 flatMap

//flatMap的底层实现:


public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
        SingleOutputStreamOperator<Tuple2<String, Integer>> res = lines
                .transform("myFlatMap"
                        , TypeInformation.of(new TypeHint<Tuple2<String, Integer>>() {})  //返回的数据类型,有嵌套所以用TypeHint
                        , new My_FlatMap());  //传StreamFlatMap 这里自定义了StreamMap



        res.print();
        env.execute();
    }



    //自定义flatMap方法 实质上就是来一条处理一条,然后返回一条
    //继承一个类,实现一个类,重写processElement方法
    private static class My_FlatMap extends AbstractStreamOperator<Tuple2<String,Integer>> implements OneInputStreamOperator<String,Tuple2<String,Integer>> {
        @Override

        public void processElement(StreamRecord<String> element) throws Exception {
            //使用getValue拿到数据
            String line = element.getValue();

            //运用处理逻辑进行处理
            String[] words = line.split(" ");
            for (String word : words) {
                Tuple2<String, Integer> wordAndOne = Tuple2.of(word, 1);

//将处理的结果进行输出,使用output.collect,这里面需要传的是StreamRecord类型,所以不new StreamRecord了
//而是使用element.replace(wordAndOne) 直接将结果输出,避免了多次创建StreamRecord实例,replace会将element中的数据清空
                output.collect(element.replace(wordAndOne));
            }
        }
    }

4.3.5 keyBy

1)底层根据哈希值和分区个数来进行分区,不是上游将数据发送到下游,而是将数据写到一个channel缓   存中,当下游需要数据的时候,就会到上游拉取数据
	a)可以使用lambda方式,最方便
	b)可以使用keySelect的方式,最全,
	c)数据不是k-v类型也可以使用
	d)当使用下标的方式,需要有条件,数据需要·是tuple类型

2)对于自定义Bean类:
	最好用keySelect -->return wordCount.getWord
	使用lambda表达式也是很方便
	这个Bean不能是private修饰
	需要有空参构造器
	使用sum的时候,要写Bean中的字段名,比如:sum("count")

3)对于多级的数据:省 市 区 商品的多级分类
	使用keySelect  return "f0" + "f1"  将两个字段合到一起
	或者return Tuple 直接按照一个Tuple进行分区,将分区的字段放进元组中
	会将这两个字段的哈希值加到一起
	或者直接用lambda表达式:keyBy(tp -> tp.f0 + tp.f1)
	或者:
		keyBy(tp -> Tuple2.of(tp.f0,tp.f1)
				, TypeInfomation.of(new TypeHint<Typle2<String,String>>(){}))
		keyBy不能使用returns,这样的化必须要指定数据类型
		
	若将数据封装到Bean中,则可以写一个方法,将需要分区的字段添加到一个方法中,
		但是在该类中必须将参加分区的字段重写hash方法,不能把Array作为哦keyBy的字段
		
/**  
keyBy 
  分区的字段采用下标的时候,必须是元组类型
*/
  
  keyBy("f0");
  keyBy(0);
KeyedStream<Tuple2<String, Integer>, Tuple> keyed = wordAndOne.keyBy("f0");
KeyedStream<Tuple2<String, Integer>, Tuple> keyed = wordAndOne.keyBy(0);
 //keyBy 采用匿名内部类的方式 new keySelect
 
  KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndOne.keyBy(new KeySelector<Tuple2<String, Integer>, String>() {
            @Override
            public String getKey(Tuple2<String, Integer> value) throws Exception {
                return value.f0;
            }
        });
/**  
keyBy
  分区的字段是Bean类中的字段
*/
  
     public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<String> words = env.socketTextStream("linux01", 9111);

        SingleOutputStreamOperator<WordCount> wordAndOne = words.map(t -> new WordCount(t, 1));
        //采用lambda表达式
         KeyedStream<WordCount, String> keyed1 = wordAndOne.keyBy(t -> t.word);

        //采用Bean中的字段方式
        KeyedStream<WordCount, Tuple> keyed2 = wordAndOne.keyBy("word");

        //采用匿名内部类的方式
        KeyedStream<WordCount, String> keyed3 = wordAndOne.keyBy(new KeySelector<WordCount, String>() {
            @Override
            public String getKey(WordCount value) throws Exception {
                return value.word;
            }
        });



        //使用sum 要写Bean中的字段名,要一致
        SingleOutputStreamOperator<WordCount> sumed = keyed1.sum("count");


        sumed.print();
        env.execute();


    }
    //用来封装数据的Bean,不能是private修饰
    public static class WordCount {

        private String word;
        private Integer count;

        // 用来封装数据的POJO,如果添加了有参的构造方法,必须添加无惨的构造方法
        public WordCount() {}

        public WordCount(String word, Integer count) {
            this.word = word;
            this.count = count;
        }

        public String getWord() {
            return word;
        }

        public void setWord(String word) {
            this.word = word;
        }

        public Integer getCount() {
            return count;
        }

        public void setCount(Integer count) {
            this.count = count;
        }

        @Override
        public String toString() {
            return "WordCount{" +
                    "word='" + word + '\'' +
                    ", count=" + count +
                    '}';
        }
    }
/** keyBy
  分区的字段有多个,而这多个字段都在元组中
  -->可以用下标
  -->可以用匿名内部类
  -->可以用lambda表达式
 */
 public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
        //辽宁省,沈阳市,1000
        //辽宁省,大连市,1000
        //山东省,济南市,1000
        //山东省,烟台市,1000
        SingleOutputStreamOperator<Tuple3<String, String, Double>> tpDataStream = lines.map(new MapFunction<String, Tuple3<String, String, Double>>() {
            @Override
            public Tuple3<String, String, Double> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple3.of(fields[0], fields[1], Double.parseDouble(fields[2]));
            }
        });

        //对于元组,使用下标的方式
        KeyedStream<Tuple3<String, String, Double>, Tuple> keyed1 = tpDataStream.keyBy("f0", "f1");
        //使用下标的方式
        KeyedStream<Tuple3<String, String, Double>, Tuple> keyed2 = tpDataStream.keyBy(0, 1);
        //使用lambda的方式
        KeyedStream<Tuple3<String, String, Double>, String> keyed3 = tpDataStream.keyBy(tp3 -> tp3.f0 + tp3.f1);
        //使用匿名内部类的方式
        KeyedStream<Tuple3<String, String, Double>, String> keyed4 = tpDataStream.keyBy(new KeySelector<Tuple3<String, String, Double>, String>() {
            @Override
            public String getKey(Tuple3<String, String, Double> value) throws Exception {
                return value.f0 + value.f1;
            }
        });
        //使用匿名内部类的方式
        KeyedStream<Tuple3<String, String, Double>, Tuple2<String, String>> keyed5 = tpDataStream.keyBy(new KeySelector<Tuple3<String, String, Double>, Tuple2<String, String>>() {
            @Override
            public Tuple2<String, String> getKey(Tuple3<String, String, Double> value) throws Exception {
                return Tuple2.of(value.f0, value.f1);
            }
        });


    }
/*
  Author:     Tao.W
  D a te:     2021/8/21
  Description:
  keyBy
  分区的字段有多个,
  将分区的字段在POJO中 --POJO即普通的自定义类
  需要将参与分区的字段重写equals和hash方法

  keyBy的时候,直接些字段的名称
*/
  public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
        //辽宁省,沈阳市,1000
        //辽宁省,大连市,1000
        //山东省,济南市,1000
        //山东省,烟台市,1000
        SingleOutputStreamOperator<ProvinceAndCity> maped = lines.map(new MapFunction<String, ProvinceAndCity>() {
            @Override
            public ProvinceAndCity map(String value) throws Exception {
                String[] arr = value.split(",");

                return new ProvinceAndCity(arr[0], arr[1]);
            }
        });

        //keyBy的时候,直接些字段的名称
        KeyedStream<ProvinceAndCity, Tuple> keyed = maped.keyBy("pro");

//        int parallelism = keyed.getParallelism();
//        System.out.println("并行度"+parallelism);
        keyed.print();
        env.execute();

    }


    //需要将参与分区的字段重写equals和hash方法
    public static class ProvinceAndCity{
        private String pro;
        private String city;




        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ProvinceAndCity that = (ProvinceAndCity) o;

            if (pro != null ? !pro.equals(that.pro) : that.pro != null) return false;
            return city != null ? city.equals(that.city) : that.city == null;
        }

        @Override
        public int hashCode() {
            int result = pro != null ? pro.hashCode() : 0;
            result = 31 * result + (city != null ? city.hashCode() : 0);
            return result;
        }

        @Override
        public String toString() {
            return "ProvinceAndCity{" +
                    "pro='" + pro + '\'' +
                    ", city='" + city + '\'' +
                    '}';
        }

        public String getPro() {
            return pro;
        }

        public void setPro(String pro) {
            this.pro = pro;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }

        public ProvinceAndCity() {
        }

        public ProvinceAndCity(String pro, String city) {
            this.pro = pro;
            this.city = city;
        }
    }
/*
  Author:     Tao.W
  D a te:     2021/8/21
  Description:
  keyBy
  分区的字段有多个,
  将分区的字段封装到POJO中 --POJO即普通的自定义类,不是Bean类,Bean类指有各种方法和私有成员变量的自定义类
  将省 市 封装到类中
  需要将参与分区的字段重写equals和hash方法
*/
 public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);
        //辽宁省,沈阳市,1000
        //辽宁省,大连市,1000
        //山东省,济南市,1000
        //山东省,烟台市,1000
        SingleOutputStreamOperator<Tuple3<String, String, Double>> tpDataStream = lines.map(new MapFunction<String, Tuple3<String, String, Double>>() {
            @Override
            public Tuple3<String, String, Double> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple3.of(fields[0], fields[1], Double.parseDouble(fields[2]));
            }
        });

        KeyedStream<Tuple3<String, String, Double>, ProvinceAndCity> keyed = tpDataStream.keyBy(tp3 -> ProvinceAndCity.of(tp3.f0, tp3.f1));
        SingleOutputStreamOperator<Tuple3<String, String, Double>> sumed = keyed.sum("f2");

        sumed.print();
        env.execute();

    }


    //需要将参与分区的字段重写equals和hash方法
    public static class ProvinceAndCity{
        private String pro;
        private String city;


        //定义一个of方法
        public static ProvinceAndCity of (String pro,String city){
            return new ProvinceAndCity(pro,city);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            ProvinceAndCity that = (ProvinceAndCity) o;

            if (pro != null ? !pro.equals(that.pro) : that.pro != null) return false;
            return city != null ? city.equals(that.city) : that.city == null;
        }

        @Override
        public int hashCode() {
            int result = pro != null ? pro.hashCode() : 0;
            result = 31 * result + (city != null ? city.hashCode() : 0);
            return result;
        }

        @Override
        public String toString() {
            return "ProvinceAndCity{" +
                    "pro='" + pro + '\'' +
                    ", city='" + city + '\'' +
                    '}';
        }

        public String getPro() {
            return pro;
        }

        public void setPro(String pro) {
            this.pro = pro;
        }

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }

        public ProvinceAndCity() {
        }

        public ProvinceAndCity(String pro, String city) {
            this.pro = pro;
            this.city = city;
        }
    }

4.3.6 Aggregate(sum max maxBy min minBy )

Aggragation:聚合(max,min,sum,maxBy,minBy,sumBy)
  
1)聚合函数底层使用的都是reduce,要想更加灵活的进行聚合操作,还是自己写reduce方法更方便
2)keyBy之后按照某个字段进行聚合操作,将同一个分区的数据进行聚合操作,
3)滚动聚合:比如每个分区都按照某个字段求一个最大值,来一条比较一条,并将结果更新
4)sum 只能对数字类型的数据进行sum操作
5)比较的时候会调用comparator方法,所以进行比较的字段必须要实现cpmparable方法,如果要对自定   义类进行聚合,则这个类需要实现comparanble接口


6)max与maxBy之间的区别:
	对于Tuple2的数据,两者没有区别
	对于有多个字段的数据,比如Tuple3(省、市、金额),按照省进行分区,然后求金额最大
:对于max---->只会返回keyBy的字段和最⼤值,如果还有其他字段,返回的是第⼀次出现的值
:对于maxBy-->不但返回keyBy的字段,还会返回最⼩值、最⼤值,如果有多个字段,还会返回最
             ⼩值、最⼤值所在数据的全部字段
对于min和minBy是一样的
  public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //辽宁省,沈阳市,1000
        //辽宁省,大连市,1000
        //辽宁省,本溪市,2000
        //山东省,济南市,2000
        //山东省,烟台市,1000
        DataStreamSource<String> lines = env.socketTextStream("linux01", 9111);

        SingleOutputStreamOperator<Tuple3<String, String, Double>> tpDataStream = lines.map(new MapFunction<String, Tuple3<String, String, Double>>() {
            @Override
            public Tuple3<String, String, Double> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple3.of(fields[0], fields[1], Double.parseDouble(fields[2]));
            }
        });

        //按照省份进行KeyBy
        KeyedStream<Tuple3<String, String, Double>, String> keyed = tpDataStream.keyBy(t -> t.f0);

        //max
 //SingleOutputStreamOperator<Tuple3<String, String, Double>> res = keyed.max(2);

//传false,则当两条数据相等的时候,返回最新的,否则会返回原来的
	SingleOutputStreamOperator<Tuple3<String, String, Double>> res = keyed.maxBy(2, false);

        res.print();

        env.execute();

    }

4.3.7 Union

--将两个数据流合并到一起:合并到一起后,方便统一进行逻辑处理
--可以union多个
**特殊情况:
1)不同的数据类型,不可union到一起
2)一个流的并行度是3,另一个流的并行度是4 ,那么合并之后的并行度是job的逻辑核数
3)自己union自己,则数据会得到双份
 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        DataStreamSource<String> lines1 = env.socketTextStream("localhost", 8888);

        DataStreamSource<String> lines2 = env.socketTextStream("localhost", 9999);

        SingleOutputStreamOperator<Integer> nums = lines2.map(Integer::parseInt);

        //要union的多个DataStream对应的数据离线必须一致才可以
        //DataStream<String> union = lines1.union(nums);

        DataStream<String> union = lines1.union(lines2);
        
        //后续可以对union进行操作

        union.print();

        env.execute();

    }

4.3.8 Connect

Connect
--可以将两个类型不一样的流connect到一起;连接到一起后生成一个新的DataStream,包装着原来的   两个流
--只能两个流connect,不可多个
--输入的两个流的类型可以不一致,但是每个流返回的数据类型是一致的
--可以让两个数据流共享状态
--貌合神离:操作的时候,假如map,则要写使用coMapFunction,重写两个map方法
 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        DataStreamSource<String> lines1 = env.socketTextStream("Linux01", 8888);
        DataStreamSource<String> lines2 = env.socketTextStream("linux01", 9999);
        SingleOutputStreamOperator<Integer> nums2 = lines2.map(Integer::parseInt).setParallelism(3);

        //String类型的流connect Integer类型的流
        ConnectedStreams<String, Integer> connected = lines1.connect(nums2);

        //进行map操作的时候,使用的是CoMapFunction 而不是MapFunction
        SingleOutputStreamOperator<String> res = connected.map(new CoMapFunction<String, Integer, String>() {

            //private FlinkState state;

            //对第一流进行操作的方法
            @Override
            public String map1(String value) throws Exception {
                //map1可以使用state
                return value.toUpperCase();
            }

            //是对第二个流继续操作的方法
            @Override
            public String map2(Integer value) throws Exception {
                //map2可以使用state
                return value + " doit";
            }
        });

        res.print();

        env.execute();

    }

4.3.9 iterator

iterator
--流式迭代计算,相当于增强的分布式for循环
--需要写三个:迭代计算的逻辑,继续迭代的条件,输出的条件
public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //10
        DataStreamSource<String> strs = env.socketTextStream("linux01", 8888);

        DataStream<Long> numbers = strs.map(Long::parseLong);

        //调用iterate方法 DataStream -> IterativeStream
        //对Nums进行迭代(不停的输入int的数字)
        IterativeStream<Long> iteration = numbers.iterate();

        //IterativeStream -> DataStream
        //对迭代出来的数据进行运算
        // 对输入的数据应用更新模型,即输入数据的处理逻辑
        DataStream<Long> iterationBody = iteration.map(new MapFunction<Long, Long>() {
            @Override
            public Long map(Long value) throws Exception {
                System.out.println("iterate input =>" + value);
                return value -= 3;
            }
        });

        //只要满足value > 0的条件,就会形成一个回路,重新的迭代,即将前面的输出作为输入,在进行一次应用更新模型,即输入数据的处理逻辑
        DataStream<Long> feedback = iterationBody.filter(new FilterFunction<Long>() {
            @Override
            public boolean filter(Long value) throws Exception {
                return value > 0;
            }
        });
        //传入迭代的条件
        iteration.closeWith(feedback);

        //不满足迭代条件的最后要输出 比如:输入8 则依次输出为:8 5 2 -1
        DataStream<Long> output = iterationBody.filter(new FilterFunction<Long>() {
            @Override
            public boolean filter(Long value) throws Exception {
                return value <= 0;
            }
        });

        //数据结果
        output.print("output value:");

        env.execute();
        
    }

4.3.9 Project

project
--投影,类似map
--只能针对 Tuple 类型的数据
--max.project(2,0) 直接用下标,这个使用更方便,类似map,不需要使用returns来指定数据类型
 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //辽宁省,沈阳市,1000
        //辽宁省,大连市,1000
        //辽宁省,本溪市,2000
        //山东省,济南市,1000
        //山东省,烟台市,1000
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<Tuple3<String, String, Double>> tpDataStream = lines.map(new MapFunction<String, Tuple3<String, String, Double>>() {
            @Override
            public Tuple3<String, String, Double> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple3.of(fields[0], fields[1], Double.parseDouble(fields[2]));
            }
        });

        //按照省份进行KeyBy
        KeyedStream<Tuple3<String, String, Double>, String> keyed = tpDataStream.keyBy(t -> t.f0);


        SingleOutputStreamOperator<Tuple3<String, String, Double>> max = keyed.max(2);

        //做隐射 用map的话,当使用lambda表达式的时候,需要使用returns
        //SingleOutputStreamOperator<Tuple2<String, Double>> res = max.map(t -> Tuple2.of(t.f0, t.f2)).returns(Types.TUPLE(Types.STRING, Types.INT));

        //用project的话,当使用lambda表达式的时候,不需要使用returns,直接使用下标
        SingleOutputStreamOperator<Tuple> res = max.project(2, 0);

        res.print();

        env.execute();


    }

五、物理分区

5.1 Hash分区

*使⽤的是KeyGroupStreamPartitioner
*常用于keyBy
*跟key和下游的分区个数有关

5.2shuffle:随机分区

在flink中shuffle是一个方法,是将数据随机进行分区,shuffle是随机计算一个分区编号

--每来一条,都给他打上一个随机的分区编号,然后对他进行重新分区,随机生成分区编号,这个跟数   据没有关系

--使用了selectChannel方法,这个方法会每来一条就执行一条,这个方法用于分区

--⽣成随机数的⽅式:random.nextInt(numberOfChannels)
  public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //上游设置并行度为1,并将字段加上分区编号进行输出
        DataStreamSource<String> words = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<String> mapped = words.map(new RichMapFunction<String, String>() {
            @Override
            public String map(String w) throws Exception {
                int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
                return w + " -> " + indexOfThisSubtask;
            }
        }).setParallelism(1);

        //对数据进行shuffle(将上游的数据随机打散,本质上是生成一个随机的分区号)
        DataStream<String> shuffled = mapped.shuffle();


        //将数据打上下游的分区编号进行输出
        shuffled.addSink(new RichSinkFunction<String>() {
            @Override
            public void invoke(String value, Context context) throws Exception {

                int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();

                System.out.println(value + " -> " + indexOfThisSubtask);
            }
        });



        env.execute();

    }

5.3 rebalance:轮询分区

--将数据轮回着进行分区,来一条分配一条
--如将数据依次分配到0区->1区->2区->0区->1区->2区...
--直接输出,默认的就是采用rebalance(当输出的上游与下游的分区数(并行度)不同的时候,就会划分task类似划分stage)
 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //并行度为1
        DataStreamSource<String> words = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<String> mapped = words.map(new RichMapFunction<String, String>() {
            @Override
            public String map(String w) throws Exception {
                int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
                return w + " -> " + indexOfThisSubtask;
            }
        }).setParallelism(1);

        //轮询:1 -> 2 -> 3 -> 0 -> 1 -> 2
        DataStream<String> rebalanced = mapped.rebalance();

        rebalanced.addSink(new RichSinkFunction<String>() {
            @Override
            public void invoke(String value, Context context) throws Exception {

                int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();

                System.out.println(value + " -> " + indexOfThisSubtask);
            }
        });



        env.execute();

    }

5.4 rescaling :轮询分区

与rebalance的·区别在于在同一个taskManager中进行轮询,减少网络传输
 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //并行度为1
        DataStreamSource<String> words = env.socketTextStream("linux01", 8888);

        SingleOutputStreamOperator<String> mapped = words.map(new RichMapFunction<String, String>() {
            @Override
            public String map(String w) throws Exception {
                int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
                return w + " -> " + indexOfThisSubtask;
            }
        }).setParallelism(1);

        //轮询:1 -> 2 -> 3 -> 0 -> 1 -> 2
        DataStream<String> rebalanced = mapped.rescale();

        rebalanced.addSink(new RichSinkFunction<String>() {
            @Override
            public void invoke(String value, Context context) throws Exception {

                int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();

                System.out.println(value + " -> " + indexOfThisSubtask);
            }
        });



        env.execute();

    }

5.5 broadCast:广播分区

将数据放到每一个channel,这样一来,下游拉取数据的时候,就实现了每个分区都能取到数据
 /* 
       会将数据广播到每个分区
        44 -> 0 -> 4
        44 -> 0 -> 1
        44 -> 0 -> 0
        44 -> 0 -> 6
        44 -> 0 -> 7
        44 -> 0 -> 3
        44 -> 0 -> 5
        44 -> 0 -> 2
        */
public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //并行度为1
        DataStreamSource<String> words = env.socketTextStream("linux01", 8888);

        SingleOutputStreamOperator<String> mapped = words.map(new RichMapFunction<String, String>() {
            @Override
            public String map(String w) throws Exception {
                int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
                return w + " -> " + indexOfThisSubtask;
            }
        }).setParallelism(1);

       /*
       会将数据广播到每个分区
        44 -> 0 -> 4
        44 -> 0 -> 1
        44 -> 0 -> 0
        44 -> 0 -> 6
        44 -> 0 -> 7
        44 -> 0 -> 3
        44 -> 0 -> 5
        44 -> 0 -> 2
        */
        DataStream<String> broadcast = mapped.broadcast();

        broadcast.addSink(new RichSinkFunction<String>() {
            @Override
            public void invoke(String value, Context context) throws Exception {

                int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();

                System.out.println(value + " -> " + indexOfThisSubtask);
            }
        });

        env.execute();

    }

5.6 custom:自定义分区器


--调用的时候调用partitionCustom方法,写入两个参数
--第1个参数:new Paritioner的实现,重写partition方法
--第2个参数:KeySelector

public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<Tuple2<String, String>> stream1 = lines.map(new RichMapFunction<String, Tuple2<String, String>>() {
            @Override
            public Tuple2<String, String> map(String value) throws Exception {
                int indexOfThisSubtask = getRuntimeContext().getIndexOfThisSubtask();
                return Tuple2.of(value, indexOfThisSubtask + "");
            }
        }).setParallelism(2);


        //使用自定义的分区器:
        DataStream<Tuple2<String, String>> stream2 = stream1.partitionCustom(
                new Partitioner<String>() {

                    /**
                     *
                     * @param key 通过KeySelector获取到分区的key
                     * @param numPartitions 下游分区的数量
                     * @return
                     */
                    @Override
                    public int partition(String key, int numPartitions) {
                        System.out.println("下游分区的数量为:" + numPartitions);
                        int index = 0;
                        if (key.startsWith("h")) {
                            index = 1;
                        } else if (key.startsWith("f")) {
                            index = 2;
                        } else if (key.startsWith("s")) {
                            index = 3;
                        }
                        return index;
                    }
                },

                //第二个参数,是选择哪个字段作为key
                t -> t.f0 //将tuple中的f0当成key
        );

        stream2.addSink(new RichSinkFunction<Tuple2<String, String>>() {
            @Override
            public void invoke(Tuple2<String, String> value, Context context) throws Exception {
                //bbb : -> 0 -> 2
                System.out.println(value.f0 + " : " + value.f1 + " -> " + getRuntimeContext().getIndexOfThisSubtask());
            }
        });


        env.execute();

    }

5.7 分区数量于并行度之间的关系

简单来讲:默认是 分区数量 = 下游的并行度
调用物理分区方法,会系那个数据进行重新分区,而分区的数量取决于下游的并行度;
因为当下游有几个并行度,则意味着有几个类似DAG的task在同时执行,那么为了执行的更快,自然是要将数据尽可能的平均发到下游的各个分区,
所以就体现为下游有几个并行,重新分区的时候就有几个分区。
假设下游8个并行,而重新分区的时候只有两个分区,则势必其他的并行计算的task要跨task拉取数据,所以会导致效率不高

六、 Window(窗口)

6.1 Flink 时间

* EventTime 数据产⽣的的时间,以后可将数据的产生时间提取出来
* IngestionTime 数据从消息中间件读取,进⼊到Flink的时间
* ProcessingTime 即数据被Operator处理时的时间

6.2 CountWindow

按照条数进行划分窗口,可以对这个窗口内的数据进行逻辑处理

nonKeyed Window :
	即没有kayBy就划分窗口,那么这个窗口是一个单并行的算子,会成为性能瓶颈,所以一般不用
	--countWindowAll方法
	
keyed Window :
	先keyBy再划分窗口,多并行,一个分区会有多个组,当组内的数据达到指定条数的时候,这个组才会单独触发,之后会清零重新计数
	--countWindow方法

nonKeyed Window

 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);
        SingleOutputStreamOperator<Integer> nums = lines.map(Integer::parseInt);

//按照条数划分窗口
//不KeyBY就划分窗口(NonKeyWindow)
AllWindowedStream<Integer, GlobalWindow> windowAll = nums.countWindowAll(5);
//对窗口中的数据进行运算
SingleOutputStreamOperator<Integer> res = windowAll.sum(0);


        res.print();
        env.execute();
    }

keyed Window

 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //spark,5
        //spark,7
        //hive,3
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndCount = lines.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple2.of(fields[0], Integer.parseInt(fields[1]));
            }
        });

        //先keyBy
        KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndCount.keyBy(t -> t.f0);
        //在划分窗口
        WindowedStream<Tuple2<String, Integer>, String, GlobalWindow> window = keyed.countWindow(5);
        //对窗口中的数据进行运算(调用WindowFunction)
        SingleOutputStreamOperator<Tuple2<String, Integer>> sum = window.sum(1);
        sum.print();

        env.execute();
    }

6.3 Time Window

6.3.0 总结

timeWindow
--按照时间来划分窗口,
--窗口触发,只会计算本窗口内的数据,不同窗口内的数据不会进行累加
1)滚动窗口:
	基于processintTime的滚动窗口,
	*non keyBy Window单并行,一般不用
	*keyBy Window 多并行,keyed后会分区,区内有多个组,只要时间一到,每个分区内的所有        组的数据都会输出,

2)滑动窗口:
	基于processintTime的滑动窗口
	*传两个参数,一个是窗口的大小,一个是窗口滑动的时间间隔。假如一个10秒的窗口大小,每隔        20秒滑动一次
	  则数据是不断产生的,没间隔20秒,就会将一个10秒的窗口划过来,然后统计这个窗口内的数据        输出,之后等待20秒再次进行滑动
	*non keyBy Window单并行,一般不用
	*keyBy Window 多并行,区内有多个组,只要时间一到,每个分区内的所有组的数据都会输出,
		
3)会话窗口:
	基于processintTime的会话窗口
	*当数据一直产生的时候不会触发窗口,直到数据产生截至,经过指定的间隔时间,便会触发窗         口,将之前产生的数据统计计算
	*nonkeyBy 单并行
	*keyBy 每个组单独触发::假如spark一直产生,而flink输入10条停止,则间隔时间后,只有       flink会输出,而spark不会输出

6.3.1 滚动窗口

基于processintTime的滚动窗口,
*non keyBy Window单并行,一般不用
*keyBy Window 多并行,keyed后会分区,区内有多个组,只要时间一到,所有分区内的所有组的数据   都会输出,

non keyBy

 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //1
        //2
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<Integer> nums = lines.map(Integer::parseInt);

        AllWindowedStream<Integer, TimeWindow> window = nums.windowAll(ProcessingTimeSessionWindows.withGap(Time.seconds(5)));

        SingleOutputStreamOperator<Integer> sum = window.sum(0);

        sum.print();

        env.execute();
    }

keyed

public static void main(String[] args) throws Exception {


        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //spark,5
        //spark,7
        //hive,3
        DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);

        SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndCount = lines.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple2.of(fields[0], Integer.parseInt(fields[1]));
            }
        });

        //先keyBy
        KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndCount.keyBy(t -> t.f0);
        //在划分窗口
        WindowedStream<Tuple2<String, Integer>, String, TimeWindow> window = keyed.window(ProcessingTimeSessionWindows.withGap(Time.seconds(5)));

        window.sum(1).print();

        env.execute();
    }

6.3.2 滑动窗口

基于processintTime的滑动窗口
*传两个参数,一个是窗口的大小,一个是窗口滑动的时间间隔。假如一个10秒的窗口大小,每隔      20秒滑动一次
 则数据是不断产生的,没间隔20秒,就会将一个10秒的窗口划过来,然后统计这个窗口内的数据      输出,之后等待20秒再次进行滑动
 
*non keyBy Window单并行,一般不用
*keyBy Window 多并行,区内有多个组,只要时间一到,每个分区内的所有组的数据都会输出,

non keyBy Window

 public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //1
        //2
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<Integer> nums = lines.map(Integer::parseInt);

        //AllWindowedStream<Integer, TimeWindow> window = nums.timeWindowAll(Time.seconds(10), Time.seconds(5));
        AllWindowedStream<Integer, TimeWindow> window = nums.windowAll(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)));

        SingleOutputStreamOperator<Integer> sum = window.sum(0);

        sum.print();

        env.execute();
    }

keyBy Window

 public static void main(String[] args) throws Exception {


        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //spark,5
        //spark,7
        //hive,3
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndCount = lines.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple2.of(fields[0], Integer.parseInt(fields[1]));
            }
        });

        //先keyBy
        KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndCount.keyBy(t -> t.f0);
        //在划分窗口
        WindowedStream<Tuple2<String, Integer>, String, TimeWindow> window = keyed.window(SlidingProcessingTimeWindows.of(Time.seconds(10), Time.seconds(5)));

        window.sum(1).print();

        env.execute();
    }

6.3.2 会话窗口

基于processintTime的会话窗口
	*当数据一直产生的时候不会触发窗口,直到数据产生截至,经过指定的间隔时间,便会触发窗         口,将之前产生的数据统计计算
	*nonkeyBy 单并行
	*keyBy 每个组单独触发::假如spark一直产生,而flink输入10条停止,则间隔时间后,只有       flink会输出,而spark不会输出

无 keyBy

public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //1
        //2
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<Integer> nums = lines.map(Integer::parseInt);

        //AllWindowedStream<Integer, TimeWindow> window = nums.timeWindowAll(Time.seconds(10));
        AllWindowedStream<Integer, TimeWindow> window = nums.windowAll(TumblingProcessingTimeWindows.of(Time.seconds(10)));

        SingleOutputStreamOperator<Integer> sum = window.sum(0);

        sum.print();

        env.execute();
    }

有 keyBy

public static void main(String[] args) throws Exception {


        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //spark,5
        //spark,7
        //hive,3
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndCount = lines.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple2.of(fields[0], Integer.parseInt(fields[1]));
            }
        });

        //先keyBy
        KeyedStream<Tuple2<String, Integer>, String> keyed = wordAndCount.keyBy(t -> t.f0);
        //在划分窗口
        WindowedStream<Tuple2<String, Integer>, String, TimeWindow> window = keyed.window(TumblingProcessingTimeWindows.of(Time.seconds(10)));

        window.sum(1).print();

        env.execute();
    }

6.4 基于eventTime的时间窗口

数据中携带着数据的产生时间 (需要设置时间标准为enentTime)

要根据数据所携带的时间来算,而不再是系统的时间

**waterMark:水位线,flink中窗口触发的机制

6.4.1 滚动窗口

--当设置时间间隔为5000的时候,则窗口为5000的整数倍0-5000、5000-10000、10000-15000

--新老版本API差别:只是触发时间相差了1毫秒
		假如时间间隔是5000 
		则老版本到4999就会触发,计算的时候只计算到4999
		而新版本到4999不会触发,大于4999才会触发,计算的时候同样只计算到4999	

延迟时间:
*当设置延迟时间为0的时候:
	若数据输入的比较理想,按照时间顺序进到flink,则自然没问题
	若数据输入的时候乱序,假设时间间隔为5000,第一个数据进来的时间就是6000,则窗口直接触     发,至于4000、1000这些数据则会丢失,不会再进到下一个窗口
*当设置延迟时间为2000的时候,
	若数据输入的时候乱序,假设时间间隔为5000,第一个数据进来的时间就是6000,则不会触发窗口,等到7000才会触发窗口,那么在这2毫秒内可以给3000的数据一个机会等他进来
	所以此时到7000才会触发,但计算的还是0-5000的数据,仅是延迟触发

老的API

不keyBy 单并行
  输入数据:10001
          20001
          49991 触发
public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);

        //老版本的API,首先要设置时间标准(时间特征)
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        //提取数据中的EventTime
        //提取完EventTime生成Watermark后,对应的DataStream的数据类型和数据格式没有改变
        SingleOutputStreamOperator<String> streamAndWatermarks = lines.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<String>(Time.seconds(0)) {
            @Override
            public long extractTimestamp(String element) {
                return Long.parseLong(element.split(",")[0]);
            }
        });

        //对数据进行map去除时间字段
        SingleOutputStreamOperator<Integer> word = streamAndWatermarks.map(new MapFunction<String, Integer>() {
            @Override
            public Integer map(String value) throws Exception {
                return Integer.parseInt(value.split(",")[1]);
            }
        });

        //进行开窗 时隔5秒窗口触发
        AllWindowedStream<Integer, TimeWindow> windowed = word.windowAll(TumblingEventTimeWindows.of(Time.seconds(5)));

        //开窗后,窗口内进行聚合
        SingleOutputStreamOperator<Integer> sumed = windowed.sum(0);

        sumed.print();
        env.execute();

    }

有keyBy 多并行

//需要每个分区的数据都达到触发时间的时候才会触发窗口




public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);

        //设置并行度为4
      //  env.setParallelism(4);
        DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);

        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3 = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
            @Override
            public Tuple3<Long, String, Integer> map(String value) throws Exception {
                String[] arr = value.split(",");

                return Tuple3.of(Long.parseLong(arr[0]), arr[1], Integer.parseInt(arr[2]));
            }
        }).setParallelism(2);

        //提取时间字段,创建waterMark  这里设置了延迟时间为2秒
        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> waterMark = tp3.assignTimestampsAndWatermarks(new BoundedOutOfOrdernessTimestampExtractor<Tuple3<Long, String, Integer>>(Time.seconds(0)) {
            @Override
            public long extractTimestamp(Tuple3<Long, String, Integer> element) {
                return element.f0;
            }
        });


        //先分区。再开窗
        KeyedStream<Tuple3<Long, String, Integer>, Tuple> keyed = waterMark.keyBy(1);
        WindowedStream<Tuple3<Long, String, Integer>, Tuple, TimeWindow> windowed = keyed.window(TumblingEventTimeWindows.of(Time.seconds(5)));
        windowed.sum(2).print();

        env.execute();
    }

新的API–keyed

 public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        //窗口的长度为5秒
        //[0, 4999], [5000, 9999], [10000, 14999]
        //1000,spark,5
        //2000,spark,7
        //3000,hive,3
        DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);

        //tp3DataStream并行度为2,这个流中有水位线吗?(没有)
        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
            @Override
            public Tuple3<Long, String, Integer> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
            }
        }).setParallelism(2);

        //对多个并行的DataStream提取EventTime生成WaterMark
        //有WaterMark的DataStream并行度是多少?   2  ,因为tp3DataStream并行度为2
        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(2)).withTimestampAssigner(new SerializableTimestampAssigner<Tuple3<Long, String, Integer>>() {
                    @Override
                    public long extractTimestamp(Tuple3<Long, String, Integer> tp, long l) {
                        //Tuple3(1000,spark,5)
                        return tp.f0;
                    }
                }));

        KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);


        //在划分窗口
        WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));

        window.sum(2).print();

        env.execute();

    }

6.4.2 滑动窗口

设窗口长度为10毫秒,每5毫秒滑动一次(滑动的步长亦为5毫秒)
--每隔5毫秒,便会将长度为10毫秒的窗口进行滑动5毫秒
--窗口的起始时间一定是步长的整数倍
--假设窗口长度为10秒,2秒滑动一次
则当第一个输入的为3的时候,第一个窗口为[-6,4)、[-4,6]、[-2,8)...
  当第一个输入的为6的时候,第一个窗口为[-4,6)、[6,16]、[16,26)...
  当第一个输入的为20的时候,第一个窗口为[10,20)、[20,30)...
  当第一个输入的为13的时候,第一个窗口为[4,14),[14,24)
  ...

以新API—keyed为例

public static void main(String[] args) throws Exception {


        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        //窗口的长度为5秒
        //[0, 4999], [5000, 9999], [10000, 14999]
        //1000,spark,5
        //2000,spark,7
        //3000,hive,3
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        //tp3DataStream并行度为2,这个流中有水位线吗?(没有)
        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
            @Override
            public Tuple3<Long, String, Integer> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
            }
        }).setParallelism(1);

        //对多个并行的DataStream提取EventTime生成WaterMark
        //有WaterMark的DataStream并行度是多少?   2  ,因为tp3DataStream并行度为2
        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream.assignTimestampsAndWatermarks(WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(0)).withTimestampAssigner(new SerializableTimestampAssigner<Tuple3<Long, String, Integer>>() {
            @Override
            public long extractTimestamp(Tuple3<Long, String, Integer> tp, long l) {
                //Tuple3(1000,spark,5)
                return tp.f0;
            }
        }));

        KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);


        //在划分窗口
        WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(SlidingEventTimeWindows.of(Time.seconds(10), Time.seconds(5)));

        window.sum(2).print();

        env.execute();
    }

6.4.3 会话窗口

当数据的时间间隔超过设定的时间间隔的时候,便会触发窗口
比如:若设定时间间隔为5秒,则
1秒,
3秒,
5秒,
8秒,
13秒 此时便会触发
假如materMark是多并行的,同样需要每个分区都达到触发窗口的时候才会触发窗口
  public static void main(String[] args) throws Exception {

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        //窗口的长度为5秒
        //[0, 4999], [5000, 9999], [10000, 14999]
        //1000,spark,5
        //2000,spark,7
        //3000,hive,3
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        //tp3DataStream并行度为2,这个流中有水位线吗?(没有)
        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
            @Override
            public Tuple3<Long, String, Integer> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
            }
        }).setParallelism(1);

        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream.assignTimestampsAndWatermarks(WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(0)).withTimestampAssigner(new SerializableTimestampAssigner<Tuple3<Long, String, Integer>>() {
            @Override
            public long extractTimestamp(Tuple3<Long, String, Integer> tp, long l) {
                //Tuple3(1000,spark,5)
                return tp.f0;
            }
        }));

        KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);


        //在划分窗口
        WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(EventTimeSessionWindows.withGap(Time.seconds(5)));

        window.sum(2).print();

        env.execute();
    }

6.5 开窗后的函数

6.5.1 使用聚合函数

*在窗口内使用聚合函数
--会在窗口内进行聚合,输出结果。。max min  maxBy minBy sum
  若在窗口内进行了keyBy再聚合,则会在同一个窗口中,同一个分区同一个组的数据进行增量聚合,待到窗口触发的时候,便会将结果输出

--在窗口内进行其他的操作 
   对窗口调用apply方法,就是将窗口内的数据攒起来,然后窗口触发再执行运算逻辑
	可以对数据进行聚合操作,也可以将数据保存到hdfs中等操作,但此时是全量聚合,先将窗口内的数据攒起来,当窗口触发的时候,会将这个窗口内的数据进行计算
	

聚合函数以Reduce为例

public static void main(String[] args) throws Exception {


        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        //窗口的长度为5秒
        //[0, 4999], [5000, 9999], [10000, 14999]
        //1000,spark,5
        //2000,spark,7
        //3000,hive,3
        DataStreamSource<String> lines = env.socketTextStream("localhost", 8888);

        //tp3DataStream并行度为2,这个流中有水位线吗?(没有)
        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
            @Override
            public Tuple3<Long, String, Integer> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
            }
        }).setParallelism(1);

        //对多个并行的DataStream提取EventTime生成WaterMark
        //有WaterMark的DataStream并行度是多少?   2  ,因为tp3DataStream并行度为2
        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(0))
                        .withTimestampAssigner((tp, l) -> tp.f0)
                );

        KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);


        //在划分窗口
        WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));

        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> reduced = window.reduce(new ReduceFunction<Tuple3<Long, String, Integer>>() {
            @Override
            public Tuple3<Long, String, Integer> reduce(Tuple3<Long, String, Integer> tp1, Tuple3<Long, String, Integer> tp2) throws Exception {
                tp1.f2 = tp1.f2 + tp2.f2;
                return tp1;
            }
        });

        reduced.print();

        env.execute();
    }

6.5.2 开窗后进行其他操作

/*
//对窗口调用apply方法,就是将窗口内的数据攒起来,然后窗口触发后再进行运算
     * @param key     KeyBy的条件
     * @param window  Window对象,可以获取窗口的一些信息
     * @param input   缓存在窗口中的数据(WindowState中,是集合)
     * @param out     使用collector输出数据
     
   可以再apply方法中将数据攒起来,进行其他操作

*/

    public static void main(String[] args) throws Exception {


        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());
        DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);

        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> tp3DataStream = lines.map(new MapFunction<String, Tuple3<Long, String, Integer>>() {
            @Override
            public Tuple3<Long, String, Integer> map(String line) throws Exception {
                String[] fields = line.split(",");
                return Tuple3.of(Long.parseLong(fields[0]), fields[1], Integer.parseInt(fields[2]));
            }
        }).setParallelism(1);


        SingleOutputStreamOperator<Tuple3<Long, String, Integer>> streamWithWaterMark = tp3DataStream
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy.<Tuple3<Long, String, Integer>>forBoundedOutOfOrderness(Duration.ofSeconds(0))
                                .withTimestampAssigner((tp, l) -> tp.f0)
                );

        KeyedStream<Tuple3<Long, String, Integer>, String> keyedStream = streamWithWaterMark.keyBy(t -> t.f1);


        //在划分窗口
        WindowedStream<Tuple3<Long, String, Integer>, String, TimeWindow> window = keyedStream.window(TumblingEventTimeWindows.of(Time.seconds(5)));

        //对窗口调用apply方法,就是将窗口内的数据攒起来,然后窗口触发后再进行运算
        window.apply(new WindowFunction<Tuple3<Long, String, Integer>, Tuple2<String, Integer>, String, TimeWindow>() {

            /**
             *
             * @param key     KeyBy的条件
             * @param window  Window对象,可以获取窗口的一些信息
             * @param input   缓存在窗口中的数据(WindowState中,是集合)
             * @param out     使用collector输出数据
             * @throws Exception
             */
            @Override
            public void apply(String key, TimeWindow window, Iterable<Tuple3<Long, String, Integer>> input, Collector<Tuple2<String, Integer>> out) throws Exception {
//                System.out.println("appy方法被调用了!!!!");
//                for (Tuple3<Long, String, Integer> tp : input) {
//                    out.collect(Tuple2.of(tp.f1, tp.f2));
//                }

                int count = 0;
                for (Tuple3<Long, String, Integer> tp : input) {
                    count += tp.f2;
                }
                out.collect(Tuple2.of(key, count));
            }
        }).print();


        env.execute();
    }

七、算子链

forward:在同一个机器的同一个分区,将数据传到后一个task
1)使用算子连:可以减少线程切换、减少缓冲、降低延迟、提高效率。

2)算子连可以禁用,但不会执行这个操作,明显禁用算子连很愚蠢
	-->disableOperaterChain
 
3)还可以开启调用算子,开启一条新的算子链:
   比如flatMap、filter、map本来是一条链,假如在flatMap后调用startNewChain,便会将flatMap和filter之间的算子连断开
   -->startNewChaining
   
4)还可以在某个算子后调用disAbleChaining,这样会将这个算子的前后链都断开
	-->disableChaining
/*
  Author:     Tao.W
  D a te:     2021/8/24
  Description:
  在StreamExecutionEnvironment禁用算子链
   禁用算子链非常愚蠢,几乎不使用

   当关闭算子链的时候,所有算子之间的链都会断开 会造成很多的task和subTask

   整体:socket-->flatMap-->filter-->map-->keyBY-->sum-->print
   并行度:1       8          8        8     8       8     8
        socket和flatMap       之间会进行rebalance
        flatMap--filter--map 之间不用重新分区,之间是forward
        map 和 keyBy          之间会进行hash重新分区
        keyBy --sum -- print  之间是forward

*/

public class DisableOperatorChaining {
    public static void main(String[] args) throws Exception{

        StreamExecutionEnvironment env = StreamExecutionEnvironment.createLocalEnvironmentWithWebUI(new Configuration());

        //在StreamExecutionEnvironment禁用算子链 整个job都禁用算子链
        //禁用算子链非常愚蠢,几乎不使用
        //env.disableOperatorChaining();

        DataStreamSource<String> lines = env.socketTextStream("linux01", 8888);

        SingleOutputStreamOperator<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public void flatMap(String line, Collector<String> collector) throws Exception {
                for (String word : line.split(" ")) {
                    collector.collect(word);
                }
            }
        });

        SingleOutputStreamOperator<String> filtered = words.filter(new FilterFunction<String>() {
            @Override
            public boolean filter(String word) throws Exception {
                return word.startsWith("h");
            }
        }).startNewChaining() //开启新链
          .disableChaining()  //断开算子链
          ;

        SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = filtered.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String word) throws Exception {
                return Tuple2.of(word, 1);
            }
        });

        wordAndOne.keyBy(t -> t.f0).sum(1).print();

        env.execute();
    }


}

八、taskSlot

0)
	 在flink中由三个概念: task 、subtask 、 taskslot
	 task相当于spark中的taskSet,里面都是相同的task,而task的个数即为分区的个数
	 subTask即相当于spark中的task,是最小的执行单元
	 taskSlot的个数即分区的个数,来自不同task中的subtask在slot中执行。
	 例如:
	 Task-1			Task-2			TaskSlot-1		TaskSlot-2
	 subtask-a-1	subtask-b-1		subtask-a-1		subtask-a-2
	 subtask-a-2	subtask-b-2		subtask-b-1		subtask-b-2
	 并行度为2TaskSlot-1TaskSlot-2
在spark中:
	TaskSet-1  -shuffle- TaskSet-2   executor-分区1   executor-分区2
	task-a-1              task-b-1		 task-a-1	   task-a-2
	task-a-2			  task-b-2		 task-b-1	   task-b-2
同一个task中的subtask,逻辑是相同的,只是处理的数据不同,task-a-1中的1代表读取第一个分区的数据
(1)
每个Worker都会有一个TaskManager
 每个TaskManager会有一致多个 task slot (task槽),worker会将内存资源进行等分给slot,而不会分配cpu
 每个taskSlot 会运行多个subTask
 
 task槽的个数在集群中可以进行设置,如果实在本地运行,则默认是cpu的逻辑核数


(2)
来自同一个job的不同task的不同subTask可以共享资源槽,而同一个task的subTask不能共享资源槽
 --据此:假如某个subTask计算的数据是密集型的,需要占大量的内存,那么就可以将他单独的放到一    个槽中,不影响整体的执行效率
 --slotSharingGroup,将对应的subTask打上槽的标签(名字);默认情况下,资源槽的名称为          default,若调用slotSharingGroup修改名称,则槽的名称会改变
   :假设某个算子调用slotSharingGroup,并将槽名称改为“doit”,则该算子会进到其他的槽,并将      那个槽名改为doit
   :与此同时,该算子下面的算子链所对应的槽也会改名为doit
   :但是只是想将该算子单独放到一个槽,所以需要将他之后的算子的槽的名称再改为default

 
 
 (3)
 我的集群中的逻辑核数是8,每个worker是4,所以可达到的job并行度为8,所以taskSolt有8个,即有8个task槽
 有了“来自同一个job的不同task的不同subTask可以共享资源槽”后,就可以实现有几个槽就可以有几个并行度
 
 (4)
 假设集群资源一共4核,所以4个taskSlot,每个worker2个
 现在开启一个job,设置并行度为3,其中的filter算子单独拿出来放到一个槽中,
 则此时程序无法运行,因为资源不够,并行度为3,又将filter单独放大一个槽,则现在需要6个槽才可以,但最多只有4个槽
 将job的并行度改为2,则此时需要4个槽,便可以运行了
/**
 * 设置共享资源槽
 * 当·程序中某个算子需要占大量内存的时候,
 * 比如filter或者在内存中进行数据的排序,此时可以将这个算子单独放到一个槽中让其运行
 */

public static void main(String[] args) throws Exception{

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        DataStreamSource<String> lines = env.socketTextStream(args[0], 8888);

        SingleOutputStreamOperator<String> words = lines.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public void flatMap(String line, Collector<String> collector) throws Exception {
                for (String word : line.split(" ")) {
                    collector.collect(word);
                }
            }
        });

        SingleOutputStreamOperator<String> filtered = words.filter(new FilterFunction<String>() {
            @Override
            public boolean filter(String word) throws Exception {
                return word.startsWith("h");
            }
        }).slotSharingGroup("doit"); //将DataStream对应的subtask打上标签
        //默认情况,资源槽的名称为default,如果调用slotSharingGroup设置了名从,对应的subtask的共享资源槽的名称就改变了
        //不同资源槽名称的subtask不能进入到同一个槽位中
        //slotSharingGroup该方法调用后,后面的算子的共享资源槽名称都跟随着改变(就近跟随原则)

        SingleOutputStreamOperator<Tuple2<String, Integer>> wordAndOne = filtered.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String word) throws Exception {
                return Tuple2.of(word, 1);
            }
        }).slotSharingGroup("default");

        wordAndOne.keyBy(t -> t.f0).sum(1).print();

        env.execute();
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值