zoukankan      html  css  js  c++  java
  • Flink流处理程序在Local模式下的运行流程源码分析

     

     

    首先,查看local模式下程序是如何运行的,上代码,从最简单的例子开始,

    第一步:编写流处理的小例子

      需求:接收来自Kafka中sensor-temperature主题下的温度传感器数据,计算各传感器每天的5秒内的平均温度

      代码:TemperatureAnalysis.java

    package com.mengyao.flink;
    
    import org.apache.commons.lang3.StringUtils;
    import org.apache.flink.api.common.accumulators.AverageAccumulator;
    import org.apache.flink.api.common.eventtime.WatermarkStrategy;
    import org.apache.flink.api.common.functions.AggregateFunction;
    import org.apache.flink.api.common.typeinfo.Types;
    import org.apache.flink.streaming.api.CheckpointingMode;
    import org.apache.flink.streaming.api.TimeCharacteristic;
    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.streaming.api.functions.sink.PrintSinkFunction;
    import org.apache.flink.streaming.api.functions.source.FromElementsFunction;
    import org.apache.flink.streaming.api.windowing.time.Time;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.time.Duration;
    import java.util.*;
    
    /**
     * @ClassName TemperatureAnalysis
     * @Description
     * @Created by MengYao
     * @Date 2020/11/18 10:14
     * @Version V1.0
     */
    public class TemperatureAnalysis {
    
        // 作业名称
        private static final String JOB_NAME = TemperatureAnalysis.class.getSimpleName();
        // 解析字符串时间
        private static final ThreadLocal<SimpleDateFormat> FMT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        // 输入的温度数据
        private static final List<String> DATA = Arrays.asList(
            "T1,2020-01-30 19:00:00,22",
            "T1,2020-01-30 19:00:01,25",
            "T1,2020-01-30 19:00:03,28",
            "T1,2020-01-30 19:00:06,26",
            "T1,2020-01-30 19:00:05,27",
            "T1,2020-01-30 19:00:12,31"
        );
    
        public static void main(String[] args) {
            final StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment()
                    // 每隔5秒进行一次CheckPoint,事件传递语义为恰好一次
                    .enableCheckpointing(5000, CheckpointingMode.EXACTLY_ONCE);
            // 使用事件时间
            env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime);
    
            try {
                // 从集合中创建数据源
                env.addSource(new FromElementsFunction<>(Types.STRING.createSerializer(env.getConfig()), DATA), Types.STRING)
                    .map(line -> TemperatureBean.of(line))// 将字符串时间转换为Temperature类型的POJO
                    .filter(Objects::nonNull)// 排除空数据
                    .assignTimestampsAndWatermarks(WatermarkStrategy.<TemperatureBean>forBoundedOutOfOrderness(Duration.ofSeconds(5)).withTimestampAssigner((TemperatureBean event, long timestamp) -> event.getTs(FMT.get())))// 提取Temperature类型事件的事件时间字段(cdt)作为注册水位线的时间戳
                    .keyBy(TemperatureBean::getKey)// 根据Key(ID_yyyy-MM-dd)分组,按照每个温度传感器每天的所有温度数据放到一个分区中
                    .timeWindow(Time.seconds(5))// 5秒的滚动窗口
                    .aggregate(new AggregateFunction<TemperatureBean, Acc, String>() {// 对每个窗口的温度数据进行聚合运算(平均)
                        @Override
                        public Acc createAccumulator() {
                            return new Acc();
                        }
                        @Override
                        public Acc add(TemperatureBean value, Acc acc) {
                            acc.setKey(value.getKey());
                            acc.add(value.getTemperature());
                            return acc;
                        }
                        @Override
                        public String getResult(Acc acc) {
                            return acc.toString();
                        }
                        @Override
                        public Acc merge(Acc a, Acc b) {
                            b.add(a.getLocalValue());
                            return b;
                        }
                    })
                    .addSink(new PrintSinkFunction<>());// 从控制台中打印
                env.execute(JOB_NAME);// 执行作业
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
        static class TemperatureBean {
            private String sensorId;
            private String cdt;
            private double temperature;
            public static TemperatureBean of(String line) {
                return of(",", line);
            }
            public static TemperatureBean of(String delimiter, String line) {
                if (StringUtils.isNotEmpty(line)) {
                    String[] values = line.split(delimiter, 3);
                    return new TemperatureBean(values[0], values[1], Double.parseDouble(values[2]));
                }
                return null;
            }
            public TemperatureBean() {}
            public TemperatureBean(String sensorId, String cdt, double temperature) {
                this.sensorId=sensorId;
                this.cdt = cdt;
                this.temperature = temperature;
            }
            public String getKey() {
                return getSensorId()+"_"+getDay();
            }
            public long getTs(SimpleDateFormat sdf) {
                try {
                    return sdf.parse(cdt).getTime();
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                return -1;
            }
            public String getDay() {
                return cdt.substring(0,10);
            }
            public String getSensorId() {
                return sensorId;
            }
            public void setSensorId(String sensorId) {
                this.sensorId = sensorId;
            }
            public String getCdt() {
                return cdt;
            }
            public void setCdt(String cdt) {
                this.cdt = cdt;
            }
            public double getTemperature() {
                return temperature;
            }
            public void setTemperature(double temperature) {
                this.temperature = temperature;
            }
            @Override
            public String toString() {
                return String.join(", ", sensorId, cdt, Double.toString(temperature));
            }
        }
    
        static class Acc extends AverageAccumulator {
            private String key;
            public void setKey(String key) {
                this.key = key;
            }
            @Override
            public String toString() {
                return String.join(",", key, Double.toString(super.getLocalValue()));
            }
        }
    
    }
    展开这段代码

    第二步:在开发工具IDEA中运行程序,查看作业是如何执行的

      整体的运行流程为:

        1、创建LocalStreamEnvironment实例(它继承自StreamExecutionEnvironment类)

        2、

    2.1、在StreamExecutionEnvironment.java类中
    由于是直接在IDEA中执行,StreamExecutionEnvironment会创建LocalStreamEnvironment的实例,如下:

     我们可以看到,LocalStreamEnvironment中只有三个方法:构造方法、校验配置对象方法和执行作业方法(调用父类实现提交)

     

    上面主要描述了Environment是如何初始化的,接下来我们看一下主程序TemperatureAnalysis中的env.execute(JOB_NAME)方法实际上是StreamExecutionEnvironment类的1694行的execute(String jobName)。

      然后,在此方法中,做了两件事情:1)调用getStreamGraph(jobName)方法来得到一个StreamGraph对象2)将StreamGraph对象传递给execute(StreamGraph streamGraph)方法;

         1)获取StreamGraph对象的方法:getStreamGraph(jobName)方法的源码实现

           2)将StreamGraph对象传递给execute(StreamGraph streamGraph)方法,我们发现它又调用了executeAsync(streamGraph)方法

         最后,来到executeAsync(StreamGraph streamGraph)方法中,发现它也做了2件事:1创建LocalExecutorFactory来获取LocalExecutor对象;2通过LocalExecutor对象的execute提交作业

      2.2、进入LocalExecutor.java类中

        通过2.1部分的代码,我们已经知道了StreamExecutionEnvironment类的executeAsync方法中,是通过创建LocalExecutor对象并调用其execute方法来进行提交作业的,然后,我们来看一下LocalExecutor类的execute方法是怎么实现的

          我们看到在方法内部,又是两件事:1)先获取JobGraph对象(标记红色数字5的地方),2)再通过PreJobMiniClusterFactory创建工厂实例并进行提交作业(标记红色数字12的地方)

          1)先获取JobGraph对象-1:先看一下获取JobGraph对象的源码定义,看到实际上是调用了LocalExecutor类中的getJobGraph方法,

            在LocalExecutor.getJobGraph方法中:

              1、判断传入的Pipeline接口的pipeline对象是属于StreamGraph类型还是Plan类型(因为Pipeline接口具有两个子类,分别是面向批的Plan类和面向流的StreamGraph类)

              2、调用PipelineExecutorUtils类的静态方法getJobGraph来获取最终的JobGraph对象

            1)先获取JobGraph对象-2:查看PipelineExecutorUtils类中,getJobGraph方法是如何实现的

            在PipelineExecutorUtils.getJobGraph方法中:

              1、使用FlinkPipelineTranslationUtil类的静态方法getJobGraph来获取JobGraph对象

              2、对得到的JobGraph对象设置Jar、Classpath和Savepoint等配置项

     

         1)先获取JobGraph对象-3:查看FlinkPipelineTranslationUtil类中,getJobGraph方法是如何实现的

          在FlinkPipelineTranslationUtil.getJobGraph方法中:

            1、创建FlinkPipelineTranlator接口的实例FlinkPipelineTranlator接口分别提供了面向流的StreamGraphTranslator子类和面向批的PlanTranlator子类),因为我们写的代码是流处理代码,所以这个传入的pipeline实际上是StreamGraph类型,所以会返回StreamGraphTranslator对象

            2、调用StreamGraphTranslator对象的.translateToJobGraph方法。

         1)先获取JobGraph对象-4:查看StreamGraphTranslator类中,translateToJobGraph方法是如何实现的

          在StreamGraphTranslator.translateToJobGraph方法中:

            1、将Pipeline强转为StreamGraph类型,并调用StreamGraph类自身的getJobGraph方法来得到最终的JobGraph实例(到此处,有没有豁然开朗!)

         2)再通过PreJobMiniClusterFactory创建工厂实例并进行提交作业-1:

           红色数字12部分显示的代码【return PerJobMiniClusterFactory.createWithFactory(effectiveConfig, miniClusterFactory).submitJob(jobGraph)】的主要作用是,创建PerJobMiniClusterFactory实例,然后提交作业。

  • 相关阅读:
    django-templates 模板变量
    实用代码
    游戏渠道后台上线
    游戏投放中的-LTV概念与价值
    转字符串格式format技巧
    mysql -数据库设计三范式
    OAuth2.0基本原理及应用
    回调函数
    GitHub 博客园快捷发布工具
    前端react antd加载错误解决
  • 原文地址:https://www.cnblogs.com/mengyao/p/14001510.html
Copyright © 2011-2022 走看看