【Flink】Transformation 转换算子 『流分区 | 基本转换算子 | 聚合算子 | 合流算子 | 富函数类 | 自定义转换算子』

1. 流分区 (非算子)

  • 对于 Flink 而言,对于算子:
    1. 可以先进行流分区,再使用算子;
    2. 也可以直接使用算子(因为算子底层会根据该算子的并行度采取随机的方式进行流分区)
    * 那么为什么还使用流分区?① 灵活进行流分区 ② 让人更加容易读懂代码
  • 每个算子都可以单独设置并行度,而并行度就是代表该算子的分区数,每个分区都会单独的调用该算子。
  • 流分区并不是将一个流分成多个流,而是对流的分配-----数据流流进每个分区的数据量是多少?
  • 流分区只是进行流分配,并不会改变元素

1.1 keyBy() 分区

  1. 作用:为每个元素打上标签,然后
    ① Flink底层会自动根据标签,将标签相同的分为一组
    ② Flink底层会自动根据标签的hash值,对下游算子的分区数 / 并行度 进行取模,知道将该元素放到哪一个分区中。

    因此:

    1. 这里的标签(key) 如果是 POJO 的话,必须要重写 hashCode()方法。
    2. 相同key的的数据一定会发送到同一个分区中,因为key相同其hash值一定相同;但是同一个分区中可能会有多个key的数据,这样主要是为了提高效率,但是一旦出现同一分区中存放不同组数据,就会进行“软分区”,将组数据隔开,因此并不会影响计算的正确性。
  2. 语法:.keyBy(KeySelector接口的实现类对象)
  3. 参数:通常采用匿名内部类的方式,例如
    env
    	.fromElements(1, 2, 3, 4, 5)
    	// 过滤掉奇数元素
    	.keyBy(new KeySelector<Integer, String>() {
    		@Override
    		public String getKey(Integer value) throws Exception {
    			return value % 2 == 0 ? "偶数" : "奇数";
    		}
    	})
    	.print().setParallelism(2);
    

    注意:实现 KeySelector 接口时

    1. 接口后面的两个泛型,分别是 输入元素标签 的类型
    2. 必须实现抽象方法getKey(),并放回该元素的标签是什么
  4. 返回值类型:KeyedStream

1.2 shuffle() 分区

  1. 作用:将每个元素随机的发往分区【发生数据倾斜时,可以使用】

  2. 语法:.shuffle()

  3. 参数:无

    stream.shuffle().print("shuffle").setParallelism(4);
    
  4. 返回值类型:DataStream (最基本的数据流流类)

1.3 rebalance() 与 rescale() 分区

  1. 作用:轮询 / 平均 的将数据流元素分配到每个分区【0, 1, 2…0, 1, 2】

    两者的区别:.rebalance()轮询的时候可能跨TaskManager。而.rescale()不会,完全走的管道,不需要通过网络,所以效率更高。

  2. 语法:.rebalance().rescale()

  3. 参数:无

    stream.rebalance().print("rebalance").setParallelism(4);
    
  4. 返回值类型:DataStream (最基本的数据流流类)

1.4 broadcast() 分区

  1. 作用:数据会在不同的分区都保留一份

  2. 语法:.broadcast()

  3. 参数:无

    stream.rebalance().print("rebalance").setParallelism(4);
    
  4. 返回值类型:DataStream (最基本的数据流流类)

1.5 global() 分区

  1. 作用:将所有的输入流数据都发送到下游算子的第一个并行子任务中去。这就相当于强行让下游任务并行度变成了 1,所以使用这个操作需要非常谨慎,可能对程序造成很大的压力。

  2. 语法:.global()

  3. 参数:无

    stream.global().print("rebalance").setParallelism(4); // 下游算子设置并行度会无意义
    
  4. 返回值类型:DataStream (最基本的数据流流类)

1.6 自定义分区 (用到了再写)

2. 基本转换算子

2.1 map (一对一)

  1. 作用:对元素进行映射处理,不改变“形状”
  2. 语法:.map(MapFunction接口的实现类对象)
  3. 参数:通常采用匿名内部类的方式,例如
    // 传入匿名类,实现 MapFunction
    stream.map(new MapFunction<Event, String>() {
    	@Override
    	public String map(Event e) throws Exception {
    		return e.user;
    	}
    });
    

    注意:实现 MapFunction 接口时

    1. 接口后面的两个泛型,分别是 输入元素输出元素 的类型
    2. 必须实现抽象方法map(),返回经过映射处理后的数据
  4. 返回值类型:SingleOutputStreamOperator。他继承了最基本的数据流流类DataStream

2.2 filter (一对一)

  1. 作用:过滤元素
  2. 语法:.map(FilterFunction接口的实现类对象)
  3. 参数:通常采用匿名内部类的方式,例如
    env
    	.fromElements(1, 2, 3, 4, 5)
    	// 过滤掉奇数元素
    	.filter (new FilterFunction<Integer>() {
    		@Override
    		public boolean filter(Integer value) throws Exception {
    			return value %  == 0;
    		}
    	});
    

    注意:实现 FilterFunction接口时

    1. 接口后面的泛型,是 输入元素 的类型
    2. 必须实现抽象方法filter()。返回true留下,返回false的被过滤掉
  4. 返回值类型:SingleOutputStreamOperator。他继承了最基本的数据流流类DataStream

2.3 flatMap (一对多)

  1. 作用:将每个元素都映射为同类型的多个元素(类似HSQL的explod()炸裂)
  2. 语法:.map(FilterFunction接口的实现类对象)
  3. 参数:通常采用匿名内部类的方式,例如
    env
    	.fromElements(1, 2, 3, 4, 5)
    	// 过滤掉奇数元素
    	.flatMap(new FlatMapFunction<Integer, Integer>() {
    		@Override
    		public void flatMap(Integer value, Collector<Integer> out) throws Exception {
    			out.collect(value);
    			out.collect(value * value);
    		}
    	});
    	.print()
    	
    // 输出:1, 1, 2, 4, 3, 9, 4, 16, 5, 25
    

    注意:实现 FlatMapFunction接口时

    1. 接口后面的两个泛型,分别是 输入元素输出元素 的类型
    2. 必须实现抽象方法flatMap(),并使用out.collect()进行元素收集,没有返回值
  4. 返回值类型:SingleOutputStreamOperator。他继承了最基本的数据流流类DataStream

3. 聚合算子

3.1 sum() 算子

  1. 作用:对指定的字段做叠加求和的操作

  2. 语法:.sum(传参详细见下面)

  3. 参数:
    在这里插入图片描述

    1. 数据流的类型是 元组 ,则既能传入字段名,又能传入位置。(传入"f0"、"f1"...0, 1, ...)。例如:
      DataStreamSource<Tuple2<String, Integer>> stream = env.fromElements(
      	Tuple2.of("a", 1),
      	Tuple2.of("a", 3),
      	Tuple2.of("b", 3),
      	Tuple2.of("b", 4)
      );
      
      // 根据元组的第二个字段求和
      stream.keyBy(r -> r.f0).sum(1).print(); 
      stream.keyBy(r -> r.f0).sum("f1").print(); 
      
    2. 数据流的类型是 POJO 类,则只能传入字段名,不能传入位置。例如
      // Teacher类有字段:name、age、classid
      DataStreamSource<Teacher> stream = env.fromElements(
      	new Teacher("Mary", 30, 1),
      	new Teacher("Bob", 40, 2)
      );
      
      // 求各个班级的老师年龄和
      stream.keyBy(t -> t.classid).sum("age").print(); 
      
    3. 数据流的类型是 基本数据类型,则只能传入位置,因为没有字段名。例如
      env
      	.fromElements(1, 2, 3, 4, 5)
      	// 分别求流中所有奇数之和和偶数之和
      	.keyBy(new KeySelector<Integer, String>() {
      	
      	    @Override
      	    public String getKey(Integer value) throws Exception {
      	        return value % 2 == 0 ? "偶数" : "奇数";
      	    }
      	})
      	.sum(0)
      	.print();
      
  4. 返回值类型:SingleOutputStreamOperator

3.2 min() 算子 与 minBy() 算子

  1. 作用:都是对指定的字段求最小值。区别在于min()只计算指定字段的最小值,其他字段会保留最初第一个数据的值;而 minBy()则会返回包含字段最小值的整条数据。
    在这里插入图片描述
    在这里插入图片描述

  2. 语法:.sum(和sum()算子一样)

  3. 参数:
    在这里插入图片描述

  4. 返回值类型:SingleOutputStreamOperator

3.3 max() 算子 与 maxBy() 算子

类比 min() 算子 与 minBy() 算子

3.4 reduce() 算子

  1. 作用:把每一个新输入的数据和当前已经归约出来的值,再做一个聚合计算。
    在这里插入图片描述

    注意:

    1. 第一个元素不会进行规约,而是直接作为规约结果
    2. 可以用该算子实现sum()算子的功能。
  2. 语法:.reduce(ReduceFunction接口的实现类对象)

  3. 参数:通常采用匿名内部类的方式,例如

    .keyBy(r -> r.f0) // 使用用户名来进行分流
    .reduce(new ReduceFunction<Tuple2<String, Long>>() {
    	@Override
    	public Tuple2<String, Long> reduce(
    						Tuple2<String, Long> value1, // value1是前面已经规约后的结果
    						Tuple2<String, Long> value2) // value2是新元素
    						throws Exception {
    		return Tuple2.of(value1.f0, value1.f1 + value2.f1);
    	}
    })
    

    注意:实现 ReduceFunction 接口时

    1. 接口后面的泛型,是 输入元素 的类型
    2. 必须实现抽象方法reduce(),返回与输入元素相同的数据类型,因为归约的是相同元素。
  4. 返回值类型:SingleOutputStreamOperator。他继承了最基本的数据流流类DataStream

4. 分流算子 (见后面博客)

split和select 分流算子 已被 高级部分 取代,见后面博客。

5. 合流算子

5.1 connect 算子

  1. 作用:把两个流合成一个流。

    注意:

    1. 两个流元素类型可以不同
    2. 只是机械的合并在一起, 内部仍然是分离的2个流
    3. 如果多个流合并,则合并之后再合并
  2. 语法:数据流1.connect (数据流2)

  3. 参数:另外一个流

    DataStreamSource<Integer> intStream = env.fromElements(1, 2, 3, 4, 5);
    DataStreamSource<String> stringStream = env.fromElements("a", "b", "c");
    // 两个流合成一个流
    ConnectedStreams<Integer, String> cs = intStream.connect(stringStream);
    // 使用合成后的流获取合成前的流
    cs.getFirstInput().print("first");
    cs.getSecondInput().print("second");
    // 使用合成后的流实现需求
    cs.     // 第一个泛型是第一个流元素类型;第二个泛型是第二个流元素类型;第三个流是输出流元素类型
       map(new CoMapFunction<Integer, String, String>() {
           @Override
           public String map1(Integer value) throws Exception {
               return value + "修改每个元素";
           }
    
           @Override
           public String map2(String value) throws Exception {
               return value + "修改每个元素";
           }
       }).print();
    

    注意:合流之后的流再使用算子,传入的参数是Co...。比如这里的 map(new CoMapFunction<Integer, String, String>() {...}

  4. 返回值类型:ConnectedStreams。他继承了最基本的数据流流类DataStream

  5. 例子:【Flink】案例:交易流水对账功能的实现

5.2 union 算子

  1. 作用:把两个或者两个以上的流合成一个流。

    注意:

    1. 要求所有合并的流元素类型必须都相同
    2. 只是真正合并在一起, 内部也是合并的
  2. 语法:数据流1.connect (数据流2, 数据流3, 数据流4...)

  3. 参数:另外的所有流

    DataStreamSource<Integer> stream1 = env.fromElements(1, 2, 3, 4, 5);
    DataStreamSource<Integer> stream2 = env.fromElements(10, 20, 30, 40, 50);
    DataStreamSource<Integer> stream3 = env.fromElements(100, 200, 300, 400, 500);
    
    // 把多个流union在一起成为一个流, 这些流中存储的数据类型必须一样: 水乳交融
    stream1
      .union(stream2)
      .union(stream3)
      .print();
    
  4. 返回值类型:最基本的数据流流类DataStream

— connect 与 union 算子区别

  1. connect 只能将两个流合并为一个流;而union可以合并多个流
  2. connect要求待合并的流可以不同;而union是必须相同。

6. 处理算子

6.1 process 算子

  1. 作用:能够获取上下文,是一个万能算子,很多算子都能用此算子实现。
  2. 语法:.process (ProcessFunction接口的实现类对象 或者 KeyedProcessFunction接口的实现类对象)

    两者的区别在与:

    • ProcessFunction接口的实现类对象:用于对流分区前使用
    • KeyedProcessFunction接口的实现类对象:用于对流分区后使用
  3. 参数:通常采用匿名内部类的方式,例如
    // 传入匿名类,实现 processElement
    stream.process(new ProcessFunction<WaterSensor, Tuple2<String, Integer>>() {
        @Override
        public void processElement(WaterSensor value, // 待处理的元素
                                   Context ctx, // 上下文
                                   Collector<Tuple2<String, Integer>> out // 输出收集器
                                   ) throws Exception {
            out.collect(new Tuple2<>(value.getId(), value.getVc()));
        }
    });
    

    注意:实现 ProcessFunction 接口时

    1. 接口后面的两个泛型,分别是 输入元素输出元素 的类型
    2. 都必须实现抽象方法processElement(),返回处理后的数据
    // 传入匿名类,实现 processElement
    env
      .fromCollection(waterSensors)
      .keyBy(WaterSensor::getId)
      .process(new KeyedProcessFunction<String, WaterSensor, Tuple2<String, Integer>>() {
          @Override
          public void processElement(WaterSensor value, Context ctx, Collector<Tuple2<String, Integer>> out) throws Exception {
              out.collect(new Tuple2<>("key是:" + ctx.getCurrentKey(), value.getVc()));
          }
      })
      .print();
    

    注意:实现 KeyedProcessFunction 接口时

    1. 接口后面的3个泛型,第一个是标签的类型,第二个是 输入元素 的类型,第三个是 输出元素 的类型
    2. 都必须实现抽象方法processElement(),返回处理后的数据
  4. 返回值类型:SingleOutputStreamOperator。他继承了最基本的数据流流类DataStream
  5. 重点:无论传入这两个接口谁的实现类,其执行的是否正确与该算子的并行度 / 分区数 息息相关。
    1. 比如:ProcessFunction接口的实现类对象
      1. 问题:
        在这里插入图片描述
        在这里插入图片描述
      2. 解决方法:保证算子的并行度和上游算子的输出流个数相同。
    2. 比如:KeyedProcessFunction接口的实现类对象
      1. 问题
        在这里插入图片描述
        在这里插入图片描述
      2. 解决方法:使用字典map
        在这里插入图片描述
  6. 案例:多个用户多次点击页面,求总用户数,本质上就是一个去重问题。
    在这里插入图片描述
    public class MyTest {
        public static void main(String[] args) throws Exception {
            // 1. 获取 有界流 的 执行环境
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    
            // 2. 设置并行度
            env.setParallelism(2);
    
            // 3. 获取源数据
            DataStreamSource<String> stream = env.readTextFile("input/uv.txt");
    
            // 4. 数据计算
            stream
                    .map(line -> line.split(",")[0])
                    .process(new ProcessFunction<String, String>() {
                        Set set = new HashSet();
    
                        @Override
                        public void processElement(String value, ProcessFunction<String, String>.Context ctx, Collector<String> out) throws Exception {
                            if (set.add(value)) {
                                out.collect("总人数为:" + set.size());
                            }
                        }
                    })
                    .setParallelism(1)
                    .print()
                    .setParallelism(1);
    
            // 5. 执行(流数据处理必须有这一步)
            env.execute();
        }
    }
    
    结果为:
    在这里插入图片描述

7. 富函数类

  1. 除了process()算子外,每个算子传入的参数对象都有其对应的富函数类对象。比如:

    stream.map(new MapFunction<Event, String>() {
    	@Override
    	public String map(Event e) throws Exception {
    		。。。
    	}
    });
    
    // 传入对应的富函数类对象
    stream.map(new RichMapFunction<Event, String>() {
    	@Override
    	public String map(Event e) throws Exception {
    		。。。
    	}
    });
    
  2. 因为 process()算子传入的八大参数对象都继承了AbstractRichFunction,所以没有RichProcessFunction
    在这里插入图片描述

  3. 应用场景:

    1. 场景1:如果某算子要使用外部资源,则使用该算子时传入富函数类对象,提高资源利用率。
      【一个常见的应用场景就是,如果我们希望连接到一个外部数据库进行读写操作,那么将连接操作放在 map()中每个元素都会进行一次连接一次数据库操作。改进方案为:该算子在每一个分区中开始时建立连接,结束时释放连接】
      在这里插入图片描述
    2. 场景2:获取上下文
      在这里插入图片描述

8. 自定义转换算子 (用到了再写)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Flink中的算子可以分为三转换算子聚合算子和窗口算子。下面对它们进行详细介绍。 1. 转换算子 转换算子用于将一个数据转换为另一个数据,常用的转换算子有: - Map:将每个输入元素应用到一个函数上,输出一个新元素。 - FlatMap:将每个输入元素应用到一个函数上,输出零个、一个或多个新元素。 - Filter:将每个输入元素应用到一个谓词上,输出满足谓词条件的元素。 - KeyBy:根据指定的键将分组。 - Reduce:对分组后的中的元素进行归约操作。 2. 聚合算子 聚合算子用于对数据进行聚合操作,常用的聚合算子有: - Sum:对输入元素进行求和操作。 - Min:对输入元素进行求最小值操作。 - Max:对输入元素进行求最大值操作。 - Count:对输入元素进行计数操作。 3. 窗口算子 窗口算子用于将数据分割为有限大小的窗口,并对窗口中的元素进行操作,常用的窗口算子有: - Tumbling Window:将数据分成不重叠的固定大小的窗口。 - Sliding Window:将数据分成固定大小的窗口,并且这些窗口可以重叠。 - Session Window:将数据根据一定的时间间隔将数据分成不固定长度的窗口。 除了以上算子Flink还提供了一些其他的算子,例如: - Union:将两个或多个数据合并为一个数据。 - Connect和CoMap:用于将两个数据连接在一起,并在连接后对两个数据进行不同的转换操作。 - Iterate:允许在数据上进行迭代操作。 总结:Flink中的算子非常丰,可以满足各种需求,通过合理使用这些算子,可以轻松构建出高效、可扩展的实时数据处理系统。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ElegantCodingWH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值