DataStream的各类算子的介绍

本文详细解读了Flink中的Map, FlatMap, Filter, KeyBy, Reduce和Aggregation等算子的使用方法及其在实际场景中的应用,包括数据清洗、转换、过滤、聚合操作,适合初学者理解和实践优化.

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

读完Flink原理、实战与性能优化 这本书后,对各类算子的联合使用还不是很清楚。看到这篇文章写得比较清晰于是转载出来。
一、Map:对数据进行逐个遍历,常用作对数据集内数据的清洗和转换

input.print();    
SingleOutputStreamOperator<Tuple2<String, Integer>> map = input.map(new MapFunction<String, Tuple2<String, Integer>>() {
      @Override
      public Tuple2<String, Integer> map(String s) throws Exception {
          return new Tuple2<>(s, 1);
      }
 });
map.print();
对来的每一条数据,添加一个元素1,组成一个tuple二元组返回。

kafka输入:

代码输出:

二、FlatMap:输入一个元素,产生多个元素,将元素“炸开”

        input.print();
        SingleOutputStreamOperator<String> stringSingleOutputStreamOperator = input.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public void flatMap(String s, Collector<String> collector) throws Exception {
                String[] strs = s.split(",");
                for (String str : strs) {
                    collector.collect(str);
                }
            }
        });
         stringSingleOutputStreamOperator.print();
kafka输入:

代码输出:

三、Filter:对输入的元素进行筛选过滤

        input.print();
        SingleOutputStreamOperator<String> hello = input.filter(new FilterFunction<String>() {
            @Override
            public boolean filter(String s) throws Exception {
                return !s.equals("hello");
            }
        });
        hello.print();
kafka输入:

程序输出:

四、KeyBy:输入DataStream,输出KeyedStream.将相同key值的数据放置在相同的分区中。

        input.print();
        SingleOutputStreamOperator<String> stringSingleOutputStreamOperator = input.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public void flatMap(String s, Collector<String> collector) throws Exception {
                String[] strs = s.split(",");
                for (String str : strs) {
                    collector.collect(str);
                }
            }
        });
        SingleOutputStreamOperator<Tuple2<String, Integer>> map = stringSingleOutputStreamOperator.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String s) throws Exception {
                return new Tuple2<>(s, 1);
            }
        });
 
        KeyedStream<Tuple2<String, Integer>, Tuple> tuple2TupleKeyedStream = map.keyBy(0);
        tuple2TupleKeyedStream.print();
kafka输入:

程序输出:

keyby(0)的意思就是拿第一个字段做聚合,所以,所有的第0个字段为a的元素都被分到一个分组中,由3号子任务输出,所有的第0个字段为b的元素都被1号子任务输出。

五、Reduce:输入KeyedStream,输出DataStream.将数据进行聚合处理,需要用户自定义ReduceFunction,且必要满足结合律和交换律。对上一步的KeyBy结果继续计算,代码如下:

        input.print();
        SingleOutputStreamOperator<String> stringSingleOutputStreamOperator = input.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public void flatMap(String s, Collector<String> collector) throws Exception {
                String[] strs = s.split(",");
                for (String str : strs) {
                    collector.collect(str);
                }
            }
        });
        SingleOutputStreamOperator<Tuple2<String, Integer>> map = stringSingleOutputStreamOperator.map(new MapFunction<String, Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> map(String s) throws Exception {
                return new Tuple2<>(s, 1);
            }
        });
 
        KeyedStream<Tuple2<String, Integer>, Tuple> tuple2TupleKeyedStream = map.keyBy(0);
//        tuple2TupleKeyedStream.print();
        SingleOutputStreamOperator<Tuple2<String, Integer>> reduce = tuple2TupleKeyedStream.reduce(new ReduceFunction<Tuple2<String, Integer>>() {
            @Override
            public Tuple2<String, Integer> reduce(Tuple2<String, Integer> stringIntegerTuple2, Tuple2<String, Integer> t1) throws Exception {
                String key1 = stringIntegerTuple2.f0;
                int value1 = stringIntegerTuple2.f1;
                int value2 = t1.f1;
                return new Tuple2<String, Integer>(key1, value1 + value2);
            }
        });
        reduce.print();
kafka输入:

程序输出:

可以看到,key为a和b的元素,已经将value的值累加起来了。

六、Aggregations:输入KeyedStream,输出DataStream.其实是封装了一些聚合函数,sum、min、minBy、max、maxBy等等。我把输入元素转换为三元组,方便确定到具体元素。

1.sum:

        input.print();
        SingleOutputStreamOperator<Tuple3<String, Integer, Integer>> tuple3SingleOutputStreamOperator = input.flatMap(new FlatMapFunction<String, Tuple3<String, Integer, Integer>>() {
            @Override
            public void flatMap(String s, Collector<Tuple3<String, Integer, Integer>> collector) throws Exception {
                String[] strs = s.split(",");
                for (String str : strs) {
                    String[] kv = str.split(":");
                    collector.collect(new Tuple3<String, Integer, Integer>(kv[0], Integer.valueOf(kv[1]), (int) (Math.random() * 10)));
                }
            }
        });
//        tuple2SingleOutputStreamOperator.print();
        KeyedStream<Tuple3<String, Integer, Integer>, Tuple> tuple3TupleKeyedStream = tuple3SingleOutputStreamOperator.keyBy(0);
 
        tuple3TupleKeyedStream.sum(1).print("sum");
//        tuple3TupleKeyedStream.min(1).print("min");
//        tuple3TupleKeyedStream.max(1).print("max");
//        tuple3TupleKeyedStream.minBy(1).print("minBy");
//        tuple3TupleKeyedStream.maxBy(1).print("maxBy");
kafka输入:

因为我们可以确定到具体元素,所以我们可以跟一下代码,看看究竟会有什么输出。(我认为截图可以表达的更清楚)。

进入到KeyedStream.java的sum()方法。

进入到SumAggregator类里面,打断点继续跟。

显然,这个if判断为true。在该类的reduce()方法打断点跟。

value1是(a,1,5),value2是(a,2,6)。把value1拷贝,传参给fieldAccessor的set方法。fieldAccessor的实例化如下:

get方法如下:

所以get(value1)会返回1,get(value2)会返回2。adder会把两个值加起来。

刚刚说到把value1拷贝,传参给传参给fieldAccessor的set方法。所以,会把value1的第1个字段更新成add之后的值,然后返回。

所以,输出结果如下:

2.min和minBy:查阅官方资料,(Rolling aggregations on a keyed data stream. The difference between min and minBy is that min returns the minimum value, whereas minBy returns the element that has the minimum value in this field (same for max and maxBy).)。翻译(在KeyedStream上滚动聚合。min和minBy之间的区别是,min返回最小值,而minBy返回该字段中具有最小值的元素(max和maxBy也是如此)。)。拿(a,1)和(a,2)两个元素,按照官方说法,min会返回最小值1,minBy会返回最小值的元素(a,1,random),但是我的实验结果却不是这样。继续看源代码。这次不会从头跟断点了,只看关键的ComparableAggregator类中的reduce方法。

先来看min:

kafka输入:

value1是(a,1,6),value2是(a,2,3)

min不是byAggregate,所以进入else

c=1,说明value1小(要看comparator对象的实例化过程)。所以直接返回value1。返回了元素,而不是较小的值。这个和官网有出入,不知道是我操作有问题,还是我的版本有问题(我是1.7.0版本)。还请明白的大佬指教。

程序输出:

再看minBy:

kafka输入: 

这次if(byAggregate)判断为true,c=1,所以返回value1.

程序输出结果为:

 

### Flink 数据倾斜的解决方案 #### 一、调整并行度 在某些场景下,通过增加算子的并行度可以缓解数据倾斜问题。然而,并行度的提升并非总是有效的。例如,在计算各个应用程序的页面浏览量 (PV) 数据时,如果输入数据仅包含两个不同的应用程序,则无论将 `Count` 算子的并行度设置为多少,都只会分配到至多两个实例上运行[^2]。因此,对于这种特定类型的键分布,单纯依赖提高并行度无法解决问题。 为了更有效地利用资源,应分析具体业务逻辑中的数据特征,合理配置不同阶段的操作符并行度。比如,可以在聚合之前引入预聚合步骤或者重新划分分区以减少下游任务的压力。 #### 二、自定义分区器 当默认的哈希分片方式导致严重的负载不平衡时,可以通过实现自定义的 Partitioner 来改善这一状况。下面是一个简单的例子展示了如何基于某个字段值来进行定制化路由: ```java dataStream.partitionCustom(new CustomPartitioner(), "someKey"); // 或者按索引位置选取作为依据 dataStream.partitionCustom(new CustomPartitioner(), 0); ``` 这里需要注意的是,编写合理的 partition 函数至关重要,它决定了每条记录最终会被送往哪个 subtask 处理。理想情况下,该函数应该能够均匀散布各类 key 值,从而达到平衡工作负荷的目的[^3]。 #### 三、伪随机化 Key 另一种方法涉及修改原始 key 的形式以便更好地分散它们在整个集群之中。假设当前存在大量重复项集中在少数几个固定 keys 上面的话,就可以考虑将其映射成另一组数值较小且连续变化的新标识码。这样做不仅有助于打破原有的聚集模式,而且还能维持原有语义关系不变——只要后续操作无需保留精确的状态信息即可适用此技巧[^4]。 ```scala val mappedKeys = originalData.map { record => val newKey = Math.abs(record._1.hashCode() % numberOfPartitions) (newKey, record._2) } ``` 上述代码片段演示了怎样把原来的字符串型 key 转换成整数型编号的过程;其中 `%numberOfPartitions` 表达式控制着目标区间大小,确保生成后的结果适配实际可用的任务槽位数量。 --- ### 总结 综上所述,针对 Flink 平台上的数据倾斜现象可以从以下几个方面入手加以应对:一是审慎设定各个环节的并发级别;二是借助于灵活可编程性的优势开发专属版 block 分发机制;三是创造性地重构 input dataset 结构进而规避热点形成风险。当然,最佳实践往往取决于具体的生产环境条件以及待解决的实际难题所在之处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值