Flink实践场景-通过DataStream KeyedProcessFunction统计每小时的出租车司机的收入

一、场景说明以及依赖请查看上一篇介绍

 Flink实践场景-通过DataStream Api统计每小时的出租车司机的收入-CSDN博客

二、KeyedProcessFunction介绍

在 Flink 中,KeyedProcessFunction 是一种特殊类型的处理函数,它允许你对数据流中的每个键(key)分别进行处理。这种处理函数特别适用于需要对每个键维护独立状态或定时器的场景。

 按照上述说法,本人之前的错误理解为应当不需要配置watermark和事件时间,直接再keyedProcessFunction配置定时器即可,但实际运行时出现无法触发计算的场景,后查询原理后才明白之前理解错误:

eyedProcessFunction 通常与事件时间(event time)和 watermarks 一起使用,因为它们允许你基于事件时间来处理数据,而不是仅仅基于数据到达的时间(即处理时间)。Watermarks 是 Flink 中用于处理乱序事件和迟到数据的机制,它们是事件时间处理的核心。

如果你不配置 watermark,KeyedProcessFunction 可能无法输出结果,原因如下:

  1. 事件时间处理依赖于 Watermarks:在事件时间语义下,Flink 依赖 watermarks 来确定何时可以处理窗口或触发某些基于时间的操作。如果没有 watermarks,Flink 就无法知道何时足够的数据已经到达,可以安全地执行这些操作。

  2. 迟到数据的处理:在实际应用中,数据可能会迟到。Watermarks 允许 Flink 等待一定程度的迟到数据,然后再触发计算。没有 watermarks,系统就无法有效地处理这些迟到的数据。

  3. 状态管理KeyedProcessFunction 允许你为每个键维护状态。在事件时间语义下,这些状态可能会根据 watermarks 的进展来更新或清理。如果没有 watermarks,状态管理可能无法正确进行,导致数据积压或不正确的结果。

  4. 定时器KeyedProcessFunction 允许你注册定时器,这些定时器是基于事件时间的。如果没有 watermarks,定时器就无法正确触发,因为你无法确定何时应该触发这些定时器。

因此,为了充分利用 KeyedProcessFunction 的能力,通常需要配置 watermarks。这样,你可以根据事件时间来处理数据,包括正确地处理迟到数据和维护基于时间的状态。

三、KeyedProcessFunction统计每小时的收入代码

package com.example.trublingwindow;

import com.example.trublingwindow.source.TaxiFare;
import com.example.trublingwindow.source.TaxiFareSource;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.api.java.tuple.Tuple3;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.TimerService;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SideOutputDataStream;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;
import org.apache.flink.util.OutputTag;

import java.time.Duration;


public class TumblingEventTimeWindowTest2 {

    private static final OutputTag<TaxiFare> lateFares = new OutputTag<TaxiFare>("lateFares") {};

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

        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.STREAMING);

        DataStreamSource<TaxiFare> source = env.addSource(new TaxiFareSource());


        SingleOutputStreamOperator<Tuple3<Long, Long, Float>> process = source
                .assignTimestampsAndWatermarks(
                        WatermarkStrategy.<TaxiFare>forBoundedOutOfOrderness(Duration.ofMinutes(10)).
                                withTimestampAssigner(((taxiFare, recordTimestamp) -> taxiFare.getTimestamp()))
                )
                .keyBy(taxi -> taxi.getDriverId())
                .process(new KeyedProcessFunction<Long, TaxiFare, Tuple3<Long, Long, Float>>() {
                    private MapState<Long, Float> windowEndTipsMap;

                    @Override
                    public void open(Configuration parameters) throws Exception {
                        super.open(parameters);
                        MapStateDescriptor windowEndTipsMapStateDescriptor = new MapStateDescriptor("windowEndTipsMap", Long.class, Float.class);
                        windowEndTipsMap = getRuntimeContext().getMapState(windowEndTipsMapStateDescriptor);
                    }

                    @Override
                    public void onTimer(long timestamp, KeyedProcessFunction<Long, TaxiFare, Tuple3<Long, Long, Float>>.OnTimerContext ctx, Collector<Tuple3<Long, Long, Float>> out) throws Exception {
                        out.collect(Tuple3.of(ctx.getCurrentKey(), timestamp, windowEndTipsMap.get(timestamp)));
                        windowEndTipsMap.remove(timestamp);
                    }

                    @Override
                    public void processElement(TaxiFare value, KeyedProcessFunction<Long, TaxiFare, Tuple3<Long, Long, Float>>.Context ctx, Collector<Tuple3<Long, Long, Float>> out) throws Exception {
                        long timestamp = value.getTimestamp();
                        TimerService timerService = ctx.timerService();
                        if (timestamp < timerService.currentWatermark()) {
                            //skip 事件延迟;其对应的窗口已经触发。
                            ctx.output(lateFares, value);
                        } else {
                            long windowEnd = (timestamp - timestamp % (1000 * 60 * 60 * 1L) + 1000 * 60 * 60 * 1L - 1);
                            timerService.registerEventTimeTimer(windowEnd);
                            Float sum = windowEndTipsMap.get(windowEnd);
                            if (null == sum) {
                                sum = 0F;
                            }
                            sum = sum + value.getTips();
                            windowEndTipsMap.put(windowEnd, sum);
                        }
                    }
                });
        //迟到的数据打印
        SideOutputDataStream<TaxiFare> sideOutput = process.getSideOutput(lateFares);
        sideOutput.print();
        //每小时的tips总和
        process.print();
        env.execute();
    }
}

旁路输出(Side Outputs) #

简介 #

有几个很好的理由希望从 Flink 算子获得多个输出流,如下报告条目:

  • 异常情况(exceptions)
  • 格式错误的事件(malformed events)
  • 延迟的事件(late events)
  • operator 告警(operational alerts),如与外部服务的连接超时

旁路输出(Side outputs)是一种方便的方法。除了错误报告之外,旁路输出也是实现流的 n 路分割的好方法。

示例 #

现在你可以对上一节中忽略的延迟事件执行某些操作。

Side output channel 与 OutputTag<T> 相关联。这些标记拥有自己的名称,并与对应 DataStream 类型一致。

private static final OutputTag<TaxiFare> lateFares = new OutputTag<TaxiFare>("lateFares") {};

上面显示的是一个静态 OutputTag<TaxiFare> ,当在 PseudoWindow 的 processElement 方法中发出延迟事件时,可以引用它:

if (eventTime <= timerService.currentWatermark()) {
    // 事件延迟,其对应的窗口已经触发。
    ctx.output(lateFares, fare);
} else {
    . . .
}

以及当在作业的 main 中从该旁路输出访问流时:

// 计算每个司机每小时的小费总和
SingleOutputStreamOperator hourlyTips = fares
        .keyBy((TaxiFare fare) -> fare.driverId)
        .process(new PseudoWindow(Time.hours(1)));

hourlyTips.getSideOutput(lateFares).print();

或者,可以使用两个同名的 OutputTag 来引用同一个旁路输出,但如果这样做,它们必须具有相同的类型。

  • 25
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值