Flink 为我们提供了重分区的 8 个算子,shuffle、rebalance、rescale、broadcast、global、forward、keyBy、partitionCustom ,我们可以使用 shuffle、rebalance、rescale 三个算子,做重分区。它们的功能是:
keyBy 造成的倾斜,通常的做法是 combiner 的做法,做本地预聚合,减少 keyBy 之后的数据量。具体的思路是,通过给每条数据指定一个随机值,然后分发到不同分区,这样相同的 word 会均匀的分发到分区中,然后使用算子来做第一次聚合,最后使用 keyBy + 聚合算子做第二次聚合。实际的实现有两种。
第一种,通过 shuffle、rebalance、rescale 实现将 word 随机落到分区中,然后可以使用 flatMap 将分区中的 word 做第一次聚合,最后使用 keyBy + 聚合算子做第二次聚合。
第二种, Tuple2(word,1) 转换为 Tuple3(word, UUID%10 , 1) ,然后对 uuid%10 做 keyBy ,这样也可以实现第一次随机聚合的步骤。第二次聚合和第一种实现方式相同。
第一种实现方式的代码:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
DataStream<Tuple3<String, String, Integer>> src = env.socketTextStream("127.0.0.1", 6666)
.flatMap(new RichFlatMapFunction<String, Tuple3<String, String, Integer>>() {
@Override
public void flatMap(String s, Collector<Tuple3<String, String, Integer>> collector) throws Exception {
Arrays.stream(s.split("\\s+")).forEach(x -> {
collector.collect(new Tuple3<String, String, Integer>(x, UUID.randomUUID().toString(), 1));
});
}
}).setParallelism(1);
src.rebalance()
.flatMap(new LocalCombiner())
.keyBy(new KeySelector<Tuple2<String,Integer> , String>(){
@Override
public String getKey(Tuple2<String, Integer> record) throws Exception {
return record.f0;
}
}).sum(1).print("--------");
env.execute();
第二种实现方式:
StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
SingleOutputStreamOperator<Tuple2<String, Integer>> firstPhase = env.socketTextStream("127.0.0.1", 6666)
.flatMap(new RichFlatMapFunction<String, Tuple3<String, String, Integer>>() {
@Override
public void flatMap(String s, Collector<Tuple3<String, String, Integer>> collector) throws Exception {
Arrays.stream(s.split("\\s+")).forEach(x -> {
collector.collect(new Tuple3<String, String, Integer>(x, UUID.randomUUID().toString(), 1));
});
}
}).keyBy(new KeySelector<Tuple3<String, String, Integer>, Integer>() {
@Override
public Integer getKey(Tuple3<String, String, Integer> t3) throws Exception {
return t3.f1.hashCode() % 10;
}
}).timeWindow(Time.seconds(10))
.process(new ProcessWindowFunction<Tuple3<String, String, Integer>, Tuple2<String, Integer>, Integer, TimeWindow>() {
@Override
public void process(Integer integer, ProcessWindowFunction<Tuple3<String, String, Integer>, Tuple2<String, Integer>, Integer, TimeWindow>.Context context, Iterable<Tuple3<String, String, Integer>> iterable, Collector<Tuple2<String, Integer>> collector) throws Exception {
Map<String, Integer> wordCnt = new HashMap<>();
iterable.forEach(x -> {
wordCnt.computeIfPresent(x.f0, (k, oldValue) -> oldValue + 1);
wordCnt.computeIfAbsent(x.f0, k -> 1);
});
wordCnt.entrySet().forEach(x -> {
collector.collect(new Tuple2<String, Integer>(x.getKey(), x.getValue()));
});
}
});
firstPhase.keyBy(new KeySelector<Tuple2<String,Integer> , String>(){
@Override
public String getKey(Tuple2<String, Integer> record) throws Exception {
return record.f0;
}
}).sum(1).print("--------");
env.execute();