Fllink实时计算运用(三)Flink Table API运用

508 阅读6分钟

1. 什么是Table API & SQL

Table API& SQL 是一种关系型API,用户可以像操作MySQL数据库表一样的操作数据,而不需要写Java代码完成flink function,更不需要手工的优化Java代码调优。SQL对一个非程序员操作来讲,学习成本很低,如果一个系统提供SQL支持,将很容易被用户接受。

总结来说,关系型API的好处:

  1. 关系型API是声明式的

  2. 查询能够被有效的优化

  3. 查询可以高效的执行

  4. “Everybody” knows SQL

Flink本身是批流统一的处理框架,所以Table API和SQL,就是批流统一的上层处理API。

Table API& SQL 是流处理和批处理统一的API层,如下图:

file

  • flink在runtime层是统一的,因为flink将批任务看做流的一种特例来执行

  • 在API层,flink为批和流提供了两套API(DataSet和DataStream)

  • Table API是一套内嵌在Java和Scala语言中的查询API,它允许我们以非常直观的方式,组合来自一些关系运算符的查询(比如select、filter和join)。

  • 对于Flink SQL,就是直接可以在代码中写SQL,来实现一些查询(Query)操作。Flink的SQL支持,基于实现了SQL标准的Apache Calcite(Apache开源SQL解析工具)。

无论输入是批输入还是流式输入,在这两套API中,指定的查询都具有相同的语义,得到相同的结果。

2. 批处理案例实现

  1. 实现说明

    以批处理方式,加载自定义数据,并注册为table表,然后统计每个人名出现的次数,并打印出来。

    WordCount jack 2 WordCount mike 1

  2. 实现步骤

    1. 获取批处理运行环境

    2. 获取Table运行环境

    3. 加载自定义数据源信息

    4. 将外部数据构建成表

    5. 使用table方式查询数据

    6. 执行任务,打印结果

  3. 代码实现

    TableApi实现方式:

    /**
     * Table Api 实现方式
     */
    public static void tableApi() throws Exception{
        //1. 初始化运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        BatchTableEnvironment tEnv = BatchTableEnvironment.create(env);
    
        //2. 编写数据源信息
        DataSet<WordCount> input = env.fromElements(
                new WordCount("mike", 1),
                new WordCount("jack", 1),
                new WordCount("jack", 1));
    
        //3. 将dataSet转换成Table对象
        Table table = tEnv.fromDataSet(input);
    
        //4. 对word进行分组,然后查询指定的字段
        Table filterTable = table
                .groupBy("word")
                .select("word, frequency.sum as frequency");
    
        //5. 将DataSet转换成Table对象
        DataSet<WordCount> result = tEnv.toDataSet(filterTable, WordCount.class);
    
        //6. 打印输出结果
        result.print();
        
    }
    

    SQL实现方式:

    /**
     * Table Api 实现方式
     */
    public static void flinkSQL() throws Exception{
        //1. 初始化运行环境
        ExecutionEnvironment env = ExecutionEnvironment.getExecutionEnvironment();
        BatchTableEnvironment tEnv = BatchTableEnvironment.create(env);
        //2. 编写数据源信息
        DataSet<WordCount> input = env.fromElements(
                new WordCount("mike", 1),
                new WordCount("jack", 1),
                new WordCount("jack", 1));
    
        //3. 将dataSet转换成Table对象
        Table table = tEnv.fromDataSet(input);
        //4. 创建临时视图
        tEnv.createTemporaryView("WordCount", input, "word, frequency");
        //5. 执行sql查询
        Table tableSQL = tEnv.sqlQuery(
                "SELECT word, SUM(frequency) as frequency FROM WordCount GROUP BY word");
        //6. 将DataSet转换成Table对象
        DataSet<WordCount> result = tEnv.toDataSet(tableSQL, WordCount.class);
        //7. 打印输出结果
        result.print();
    }
    

3. 流处理案例实现

  1. 实现说明

    采用Flink流式环境, 加载多个集合数据, 转换为Table, 并将Table转换为DataStream,采用SQL方式进行合并处理。

  2. 实现步骤

    1. 获取流处理环境

    2. 设置并行度

    3. 获取Table运行环境

    4. 加载集合数据

    5. 转换DataStream为Table

    6. 将DataStream注册成Table

    7. 使用union all将两个表进行关联

    8. 执行任务,打印输出

  3. 代码实现

    StreamSqlApplication类:

    //1. 初始化流式开发环境
    StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
    StreamTableEnvironment tEnv = StreamTableEnvironment.create(env);
    
    //2. 创建第一个订单流
    DataStream<Order> orderA = env.fromCollection(Arrays.asList(
            new Order(1L, "beer", 3),
            new Order(1L, "diaper", 4),
            new Order(3L, "rubber", 2)));
    
    //3. 创建第二个订单流
    DataStream<Order> orderB = env.fromCollection(Arrays.asList(
            new Order(2L, "pen", 3),
            new Order(2L, "rubber", 3),
            new Order(4L, "beer", 1)));
    
    //4. 将DataStream转换成Table(可以将DataStream转换成Table)
    Table tableA = tEnv.fromDataStream(orderA, "user, product, amount");
    
    //5. 将DataStream注册成Table(也可以将DataStream注册成Table)
    tEnv.registerDataStream("OrderB", orderB, "user, product, amount");
    
    //6. 使用union all将两个表进行关联
    Table result = tEnv.sqlQuery("SELECT * FROM " + tableA +
            " UNION ALL " +
            " SELECT * FROM OrderB ");
    
    //7. 将数据流进行输出
    tEnv.toAppendStream(result, Order.class).print().setParallelism(1);
    
    //8. 执行任务
    env.execute();
    

4. Flink 1.9 Table架构

file

在新的架构中,有两个查询处理器:

  1. Flink Query Processor,也称作Old Planner

  2. Blink Query Processor,也称作Blink Planner

查询处理器是 Planner 的具体实现,通过parser、optimizer、codegen(代码生成技术)等流程将 Table API & SQL作业转换成 Flink Runtime 可识别的 Transformation DAG,最终由 Flink Runtime 进行作业的调度和执行。

Flink 的查询处理器针对流计算和批处理作业有不同的分支处理,流计算作业底层的 API 是 DataStream API, 批处理作业底层的 API 是 DataSet API。

Blink 的查询处理器则实现流批作业接口的统一,底层的 API 都是Transformation,这就意味着我们和Dataset完全没有关系了。

5. Flink Planner 与 Blink Planner的差异

从Flink1.9开始,提供了两种不同的 planner 实现:Blink planner 和 1.9之前旧的 Old Planner。Planner 负责将算子转换为 Flink 可执行的、优化之后的 Flink job。这两个 Planner 拥有不同的优化规则和 runtime 类。

  1. 在模型角度,Flink Planner 没有考虑流计算和批处理的统一,在底层会分别转换到到 DataStream API 和 DataSet API 上。而 Blink Planner 将批数据集看作 bounded DataStream (有界流式数据) ,流计算作业和批处理作业最终都会转换到 Transformation API 上。

  2. 在架构角度,Blink Planner针对批处理和流计算,分别实现了BatchPlanner 和 StreamPlanner ,两者大部分的代码和优化逻辑都是共用的。 Old Planner 针对批处理和流计算的代码实现的是完全独立的两套体系,基本没有实现代码和优化逻辑复用。

除了模型和架构上的优点外,Blink Planner 在阿里巴巴集团内部的海量业务场景下沉淀了许多实用功能,集中在三个方面:

  1. Blink Planner 对代码生成机制做了改进、对部分算子进行了优化,提供了丰富实用的新功能,如维表 join、Top N、MiniBatch、流式去重、聚合场景的数据倾斜优化等新功能。

  2. Blink Planner 的优化策略是基于公共子图的优化算法,包含了基于成本的优化(CBO)和基于规则的优化(CRO)两种策略,优化更为全面。同时,Blink Planner 支持从 catalog 中获取数据源的统计信息,这对CBO优化非常重要。

  3. Blink Planner 提供了更多的内置函数,更标准的 SQL 支持

整体看来,Blink 查询处理器在架构上更为先进,功能上也更为完善。出于稳定性的考虑,Flink 1.9 默认依然使用 Flink Planner,用户如果需要使用 Blink Planner,可以在作业中显式指定。

6. Flink Planner 用法

  1. 导入依赖

    <!-- 表程序计划程序和运行时。这是1.9版本之前Flink的唯一计划者。仍然是推荐的。-->
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-planner_${scala.binary.version}</artifactId>
        <version>${flink.version}</version>
    </dependency>
    
  2. 代码片段

    流式查询:

    import org.apache.flink.table.api.EnvironmentSettings;
    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.table.api.java.StreamTableEnvironment;
    // Flink 流式查询
    EnvironmentSettings fsSettings = EnvironmentSettings.newInstance().useOldPlanner().inStreamingMode().build();
    StreamExecutionEnvironment fsEnv = StreamExecutionEnvironment.getExecutionEnvironment();
    StreamTableEnvironment fsTableEnv = StreamTableEnvironment.create(fsEnv, fsSettings);
    // or TableEnvironment fsTableEnv = TableEnvironment.create(fsSettings)
    
    

    批数据查询:

    //导入包 
    import org.apache.flink.api.java.ExecutionEnvironment;
    import org.apache.flink.table.api.java.BatchTableEnvironment;
    // Flink 批数据查询
    ExecutionEnvironment fbEnv = ExecutionEnvironment.getExecutionEnvironment();
    BatchTableEnvironment fbTableEnv = BatchTableEnvironment.create(fbEnv);
    

7. Blink Planner 用法

  1. 导入依赖

    <!-- 使用blink执行计划的时候需要导入这个包-->
    <dependency>
        <groupId>org.apache.flink</groupId>
        <artifactId>flink-table-planner-blink_${scala.binary.version}</artifactId>
        <version>${flink.version}</version>
    </dependency>
    
  2. 代码片段

    流式查询:

    //导入包
    import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
    import org.apache.flink.table.api.EnvironmentSettings;
    import org.apache.flink.table.api.TableEnvironment;
    import org.apache.flink.table.api.java.StreamTableEnvironment;
    // Flink 流式查询
    StreamExecutionEnvironment bsEnv = StreamExecutionEnvironment.getExecutionEnvironment();
    EnvironmentSettings bsSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inStreamingMode().build();
    StreamTableEnvironment bsTableEnv = StreamTableEnvironment.create(bsEnv, bsSettings);
    // or TableEnvironment bsTableEnv = TableEnvironment.create(bsSettings);
    
    

    批数据查询:

    // Flink 批数据查询
    EnvironmentSettings bbSettings = EnvironmentSettings.newInstance().useBlinkPlanner().inBatchMode().build();
    TableEnvironment bbTableEnv = TableEnvironment.create(bbSettings);
    

    说明: 如果作业需要运行在集群环境,打包时将 Blink Planner 相关依赖的 scope 设置为 provided,表示这些依赖由集群环境提供。这是因为 Flink 在编译打包时,已经将 Blink Planner 相关的依赖打包,不需要再次引入,避免冲突。


本文由mirson创作分享,如需进一步交流,请加QQ群:19310171或访问www.softart.cn