Flink API - 键控流转换算子与滚动聚合算子


很多流处理程序的一个基本要求就是要能对数据进行分组,分组后的数据共享某一个相同的属性。DataStream API 提供了一个叫做 KeyedStream 的抽象,此抽象会从逻辑上对 DataStream进行分区,分区后的数据拥有同样的 Key 值,分区后的流互不相关。
针对 KeyedStream 的状态转换操作可以读取数据或者写入数据到当前事件 Key 所对应的状态中。这表明拥有同样 Key 的所有事件都可以访问同样的状态,也就是说所以这些事件可以一起处理。
要小心使用状态转换操作和基于 Key 的聚合操作。如果 Key 的值越来越多,例如:Key 是订单ID,我们必须及时清空 Key 所对应的状态,以免引起内存方面的问题。
KeyedStream 可以使用 map,flatMap 和 filter 子来处理。接下来我们会使用 keyBy 算子来将DataStream 转换成 KeyedStream,并讲解基于 key 的转换操作:滚动聚合和 reduce 算子。

键控流算子 - keyBy

keyBy 通过指定 key 来将 DataStream 转换成 KeyedStream。基于不同的 key,流中的事件将被分配到不同的分区中去。所有具有相同 key 的事件将会在接下来的操作符的同一个子任务槽中进行处理。拥有不同 key 的事件可以在同一个任务中处理。但是算子只能访问当前事件的 key 所对应的状态。
相同的 key 一定发送到同一任务槽,不同的 key 可能发送到同一任务槽处理
如图所示,把输入事件的颜色作为 key,黑色的事件输出到了一个分区,其他颜色输出到了另一个分区。
在这里插入图片描述
keyBy() 方法接收一个参数,这个参数指定了 key 或者 keys,有很多不同的方法来指定 key。
下面例子展示如何使用 keyBy 算子将时间按 user 字段分组:

public class KeyByOperator {

    // 按 user 分组

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

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        // keyBy:将相同 key 的 数据分发到同一逻辑分区

        env
                .addSource(new ClickSource())
                // 第一个泛型:流数据类型
                // 第二个泛型:key 类型
                .keyBy(new KeySelector<Event, String>() {
                    @Override
                    public String getKey(Event value) throws Exception {
                        return value.user;
                    }
                })
                .print();

        env.execute();

    }

}

基本滚动聚合算子

滚动聚合算子由 KeyedStream 调用,并生成一个聚合以后的 DataStream,例如:sum,min,max。一个滚动聚合算子会为每一个观察到的 key 保存一个聚合的值。针对每一个输入事件,算子将会更新保存的聚合结果,并发送一个带有更新后的值的事件到下游算子。滚动聚合不需要用户自定义函数,但需要接受一个参数,这个参数指定了在哪一个字段上面做聚合操作。DataStream API 提供了以下滚动聚合方法。
滚动聚合算子只能用在滚动窗口,不能用在滑动窗口。

  • sum():在输入流上对指定的字段做滚动相加操作。
  • min():在输入流上对指定的字段求最小值。
  • max():在输入流上对指定的字段求最大值。
  • minBy():在输入流上针对指定字段求最小值,并返回包含当前观察到的最小值的事件。
  • maxBy():在输入流上针对指定字段求最大值,并返回包含当前观察到的最大值的事件。

滚动聚合算子无法组合起来使用,每次计算只能使用一个单独的滚动聚合算子。
滚动聚合操作会对每一个 key 都保存一个状态。因为状态从来不会被清空,所以在使用滚动聚合算子时只能使用在含有有限个 key 的流上面。
下面例子通过对 Tuple2 格式的数据的第一个元素进行 keyBy 然后进行聚合统计:

public class AGGReduceOperator {

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

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        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("sum:");
        stream.keyBy(r -> r.f0).sum("f1").print("sum -> f1:");
        stream.keyBy(r -> r.f0).max(1).print("max:");
        stream.keyBy(r -> r.f0).max("f1").print("max -> f1:");
        stream.keyBy(r -> r.f0).min(1).print("min:");
        stream.keyBy(r -> r.f0).min("f1").print("min -> f1:");
        stream.keyBy(r -> r.f0).maxBy(1).print("maxBy:");
        stream.keyBy(r -> r.f0).maxBy("f1").print("maxBy -> f1:");
        stream.keyBy(r -> r.f0).minBy(1).print("minBy:");
        stream.keyBy(r -> r.f0).minBy("f1").print("minBy -> f1:");

        env.execute();
    }
}

res:
minBy::11> (a,1)
minBy -> f1::3> (b,3)
minBy -> f1::3> (b,3)
sum -> f1::11> (a,1)
min::3> (b,3)
min::3> (b,3)
maxBy::3> (b,3)
max -> f1::3> (b,3)
sum -> f1::11> (a,4)
maxBy::11> (a,1)
maxBy::3> (b,4)
maxBy::11> (a,3)
maxBy -> f1::11> (a,1)
max::3> (b,3)
max -> f1::11> (a,1)
minBy -> f1::11> (a,1)
min::11> (a,1)
minBy -> f1::11> (a,1)
min::11> (a,1)
minBy::11> (a,1)
max -> f1::3> (b,4)
min -> f1::3> (b,3)
min -> f1::11> (a,1)
min -> f1::3> (b,3)
maxBy -> f1::3> (b,3)
min -> f1::11> (a,1)
minBy::3> (b,3)
max -> f1::11> (a,3)
max::3> (b,4)
maxBy -> f1::11> (a,3)
max::11> (a,1)
sum -> f1::3> (b,3)
max::11> (a,3)
sum::11> (a,1)
sum -> f1::3> (b,7)
minBy::3> (b,3)
maxBy -> f1::3> (b,4)
sum::3> (b,3)
sum::11> (a,4)
sum::3> (b,7)
 

滚动聚合算子 - Reduce

reduce 算子是滚动聚合的泛化实现。它将一个 ReduceFunction 应用到了一个 KeyedStream 上面去。reduce 算子将会把每一个输入事件和当前已经 reduce 出来的值做聚合计算。reduce 操作不会改变流的事件类型。输出流数据类型和输入流数据类型是一样的。
reduce 函数可以通过实现接口 ReduceFunction 来创建一个类。ReduceFunction 接口定义了reduce() 方法,此方法接收两个输入事件,输出一个相同类型的事件。
下面的例子对相同用户的分数进行累加操作:

public class ReduceOperator {

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

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();

        env
                .fromElements(
                        Tuple2.of("Alick",98),
                        Tuple2.of("Alick",69),
                        Tuple2.of("Bob",91),
                        Tuple2.of("Tom",87),
                        Tuple2.of("Bob",69)
                )
                .keyBy(elem -> elem.f1)
                .reduce(new ReduceFunction<Tuple2<String, Integer>>() {
                    @Override
                    public Tuple2<String, Integer> reduce(Tuple2<String, Integer> value1, Tuple2<String, Integer> value2) throws Exception {
                        return Tuple2.of(value1.f0,value1.f1 + value2.f1);
                    }
                })
                .print();

        env.execute();
    }
}

res:
16> (Bob,91)
3> (Alick,98)
16> (Tom,87)
3> (Alick,69)
3> (Alick,138)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值