• Flink 自定义水位线


    一般来说,Flink内置的水位线生成器就可以满足应用需求了。不过有时由于业务逻辑可能非常复杂,这时对水位线生成的逻辑也有更高的要求,开发人员就必须自定义实现水位线策略WatermarkStrategy了。在WatermarkStrategy中,时间戳分配器TimestampAssigner都是大同小异的,指定字段提取时间戳就可以了;而不同策略的关键就在于WatermarkGenerator的实现。整体说来,Flink有两种不同的生成水位线的方式:一种是周期性的(Periodic),另一种是断点式的(Punctuated)。就是WatermarkGenerator接口中的两个方法——onEvent()和onPeriodicEmit(),前者是在每个事件到来时调用,而后者由框架周期性调用。周期性调用的方法中发出水位线,自然就是周期性生成水位线;而在事件触发的方法中发出水位线,自然就是断点式生成了。两种方式的不同就集中体现在这两个方法的实现上;

    1、周期性水位线生成器(Periodic Generator)

    周期性生成器一般是通过onEvent()观察判断输入的事件,而在onPeriodicEmit()里发出水位线

        public static void main(String[] args) throws Exception {
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
            env.setParallelism(1);
            //生成周期性水位线
            env.addSource(new ClickSource())
                    .assignTimestampsAndWatermarks(new MyWatermarkStrategy())
                    .print();
    
            env.execute();
        }
    
        //自定义周期性生成水位线
        public static class MyWatermarkStrategy implements WatermarkStrategy<Event> {
    
    
            @Override
            public TimestampAssigner<Event> createTimestampAssigner(TimestampAssignerSupplier.Context context) {
                return new SerializableTimestampAssigner<Event>() {
                    @Override
                    public long extractTimestamp(Event element, long recordTimestamp) {
                        return element.timestamp * 1000L;
                    }
                };
            }
    
            @Override
            public WatermarkGenerator<Event> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context) {
                //周期生成水位线
                return new MyPeriodicGenerator();
                
            }
        }
    
        //周期性生成水位线
        public static class MyPeriodicGenerator implements WatermarkGenerator<Event> {
            //延迟时间
            private long delayTime = 5000L;
            //观察到最大时间cuo
            private long maxTs = Long.MIN_VALUE + delayTime + 1L;
    
            //每来一条数据调研一次
            @Override
            public void onEvent(Event event, long eventTimestamp, WatermarkOutput output) {
                //更新最大时间cuo
                maxTs = Math.max(event.timestamp, maxTs);
            }
    
            @Override
            public void onPeriodicEmit(WatermarkOutput output) {
                //发射水位线,默认200 ms 调用一次
                output.emitWatermark(new Watermark(maxTs - delayTime - 1L));
            }
        }

    在onPeriodicEmit()里调用output.emitWatermark(),就可以发出水位线了;这个方法由系统框架周期性地调用,默认200ms一次。所以水位线的时间戳是依赖当前已有数据的最大时间戳的(这里的实现与内置生成器类似,也是减去延迟时间再减1),但具体什么时候生成与数据无关。

    2、断点式水位线生成器(PunctuatedGenerator)

        public static void main(String[] args) throws Exception {
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
            env.setParallelism(1);
            //生成周期性水位线
            env.addSource(new ClickSource())
                    .assignTimestampsAndWatermarks(new MyWatermarkStrategy())
                    .print();
    
            env.execute();
        }
    
        //自定义周期性生成水位线
        public static class MyWatermarkStrategy implements WatermarkStrategy<Event> {
    
    
            @Override
            public TimestampAssigner<Event> createTimestampAssigner(TimestampAssignerSupplier.Context context) {
                return new SerializableTimestampAssigner<Event>() {
                    @Override
                    public long extractTimestamp(Event element, long recordTimestamp) {
                        return element.timestamp * 1000L;
                    }
                };
            }
    
            @Override
            public WatermarkGenerator<Event> createWatermarkGenerator(WatermarkGeneratorSupplier.Context context) {
                //周期生成水位线
                // return new MyPeriodicGenerator(); 
                //断点生成水位线
                return new MyPunctuatedGenerator();
            }
        }
    
        //断点生成水位线
        public static class MyPunctuatedGenerator implements WatermarkGenerator<Event> {
    
            @Override
            public void onEvent(Event event, long eventTimestamp, WatermarkOutput output) {
                //只有遇到特定数据时,才发送水位线
                if (event.user.equals("依琳")) {
                    output.emitWatermark(new Watermark(event.timestamp - 1L));
                }
            }
    
            @Override
            public void onPeriodicEmit(WatermarkOutput output) {
                //onEvent 已经发送了水位线,onPeriodicEmit不做处理即可
            }
        }

    在onEvent()中判断当前事件的user字段,只有遇到“依琳”这个特殊的值时,才调用output.emitWatermark()发出水位线。这个过程是完全依靠事件来触发的,所以水位线的生成一定在某个数据到来之后。

    3、在自定义数据源中发送水位线

    也可以在自定义的数据源中抽取事件时间,然后发送水位线。这里要注意的是,在自定义数据源中发送了水位线以后,就不能再在程序中使用assignTimestampsAndWatermarks方法 来 生 成 水 位 线 了 。 在 自 定 义 数 据 源 中 生 成 水 位 线 和 在 程 序 中 使 用assignTimestampsAndWatermarks方法生成水位线二者只能取其一。示例程序如下:

        public static void main(String[] args) throws Exception {
            StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
            env.setParallelism(1);
            env.addSource(new ClickSourceWithWatermark()).print();
            env.execute();
    
        }
    ClickSourceWithWatermark 逻辑
    public class ClickSourceWithWatermark implements ParallelSourceFunction<Event> {
        // 声明标志位
        private Boolean running = true;
    
        @Override
        public void run(SourceContext<Event> ctx) throws Exception {
            //随机生成数据
            Random random = new Random();
            //随机范围
            String[] users = {"令狐冲", "依琳", "任盈盈", "莫大", "风清扬"};
    
            String[] urls = {"./home", "./cat", "./pay", "./info"};
            //循环生成数据
            while (running) {
                //生成数据
                String user = users[random.nextInt(users.length)];
                String url = urls[random.nextInt(urls.length)];
                Event event = new Event(user, url, Calendar.getInstance().getTimeInMillis());
    
                //发送数据
                ctx.collect(event);
    
                //发送水位线
                ctx.emitWatermark(new Watermark(event.timestamp - 1L));
            }
        }
    
        @Override
        public void cancel() {
            running = false;
        }
    }

    在自定义水位线中生成水位线相比assignTimestampsAndWatermarks方法更加灵活,可以任意的产生周期性的、非周期性的水位线,以及水位线的大小也完全由我们自定义。所以非常适合用来编写Flink的测试程序,测试Flink的各种各样的特性。

  • 相关阅读:
    好一张图(饼得慢慢吃)
    kafka消息存储原理及查询机制
    (四)SpringCloud入门篇——工程重构
    (三)SpringCloud入门篇——微服务消费者订单Module模块
    java.sql.SQLException: org.gjt.mm.mysql.Driver
    (二)SpringCloud入门篇——Rest微服务工程:支付模块构建
    (一)SpringCloud入门篇——微服务cloud整体聚合:父工程步骤
    虚拟机中docker安装mysql远程无法访问
    Java初级开发项目
    类和对象
  • 原文地址:https://www.cnblogs.com/wdh01/p/16412928.html
Copyright © 2020-2023  润新知