Flink SQL 执行框架源码

flinksql 利用了Apache Calcite的查询优化框架和SQL parser

Calcite的五个阶段 :

Parse:语法解析,把 SQL 语句转换成为一个抽象语法树(AST),在 Calcite 中用 SqlNode 来表示(SQL–>SqlNode)

Validate:语法校验,根据catalog进行验证,例如查询的表、使用的函数是否存在等,校验之后仍然是 SqlNode 构成的语法树(SqlNode–>SqlNode)

Optimize(Logical Plan):查询计划优化,这里包含了两个阶段

   1. 首先将 SqlNode 语法树转换成关系表达式 RelNode 构成的逻辑树(SqlNode–>RelNode/RexNode)

   2. 然后使用优化器基于规则进行等价变换,例如我们比较熟悉的谓词下推、列裁剪等,经过优化器优化后得到最优的查询计划(RelNode–>RelNode),先基于calcite rules 去优化logical Plan, 再基于Flink定制的一些优化rules去优化logical Plan

Execute:生成ExecutionPlan,生成物理执行计划(DataStream Plan),提交运行。

Calcite 比较复杂,且扩展性很强,这里介绍了Flink 的几个阶段

1. 入口,环境准备

入口在 TableEnvironment 类,代码示例
        EnvironmentSettings settings = EnvironmentSettings
                .newInstance()
                .inStreamingMode()
                .build();

        TableEnvironment tableEnv = TableEnvironment.create(settings);

        //注册source和sink
        tableEnv.executeSql("sourceDDL");
        tableEnv.executeSql("sinkDDL");
        tableEnv.executeSql("sql语句");

首先是环境准备,包括catalog,resource,fucntion,table等

抽象出了 Planner 接口和 Executor 接口,可以支持多个不同的SQL 执行器,用户可以自行选择希望使用的 Runner。不同的 Runner 只需要正确地实现这两个接口即可

简单聊聊catalog------------------------------------------------------------------------------------------------------

Catalog类

不使用外部catalog 的时候,对应是GenericInMemoryCatalog,就是用这个类记录我们的元数据信息

----------------------------------------------------------------------------------------------------------------------------

2. SQL 解析 部分

planner 有几个实现

batch 和 stream 继承于 PlannerBase 



parser方法去创建 Parse 

Parse 的 parse() 做了

  1. 先用ExtendedParser解析Calcite不支持的SQL语句
  2. parser.parse 解析得到 SqlNode
  3. 使用 SqlNodeToOperationConversion 将 SqlNode 转化为 Operation

SqlNodeToOperationConversion 代码有些多,copy一些核心代码 ,主要就是validate,然后把validate后的SqlNode转为Optional<Operation>

public class SqlNodeToOperationConversion {
    private final FlinkPlannerImpl flinkPlanner;
    private final CatalogManager catalogManager;
    private final SqlCreateTableConverter createTableConverter;
    private final AlterSchemaConverter alterSchemaConverter;

    public static Optional<Operation> convert(
            FlinkPlannerImpl flinkPlanner, CatalogManager catalogManager, SqlNode sqlNode) {
        // validate the query
        final SqlNode validated = flinkPlanner.validate(sqlNode);
        return convertValidatedSqlNode(flinkPlanner, catalogManager, validated);
    }

    /** Convert a validated sql node to Operation. */
    private static Optional<Operation> convertValidatedSqlNode(
            FlinkPlannerImpl flinkPlanner, CatalogManager catalogManager, SqlNode validated) {
        beforeConversion();

        // delegate conversion to the registered converters first
        SqlNodeConvertContext context = new SqlNodeConvertContext(flinkPlanner, catalogManager);
        Optional<Operation> operation = SqlNodeConverters.convertSqlNode(validated, context);
        if (operation.isPresent()) {
            return operation;
        }

        // TODO: all the below conversion logic should be migrated to SqlNodeConverters
        SqlNodeToOperationConversion converter =
                new SqlNodeToOperationConversion(flinkPlanner, catalogManager);
        if (validated instanceof SqlDropCatalog) {
            return Optional.of(converter.convertDropCatalog((SqlDropCatalog) validated));
        } else if (validated instanceof SqlLoadModule) {
            return Optional.of(converter.convertLoadModule((SqlLoadModule) validated));
        } else if (validated instanceof SqlShowCatalogs) {
            return Optional.of(converter.convertShowCatalogs((SqlShowCatalogs) validated));
......................................................
        } else if (validated instanceof SqlShowCurrentCatalog) {
            return Optional.of(
                    converter.convertShowCurrentCatalog((SqlShowCurrentCatalog) validated));
        } else if (validated instanceof SqlShowModules) {
            return Optional.of(converter.convertShowModules((SqlShowModules) validated));
        } else if (validated instanceof SqlUnloadModule) {
            return Optional.of(converter.convertUnloadModule((SqlUnloadModule) validated));
        } else if (validated instanceof SqlUseCatalog) {
        } else {
            return Optional.empty();
        }
    }

3. SQL 转换及优化

SQL 语句解析成 Operation 后,为了得到 Flink 运行时的具体操作算子,需要进一步将 ModifyOperation 转换为 Transformation

核心代码在 PlannerBase 的 translate(),如下

ModifyOperation 对应的是一个 DML 的操作。比如,在将查询结果插入到一张结果表或者转换为 DataStream 时,就会得到 ModifyOperation

上面代码主要做了 4 步

  • 1 将 Operation 转换为 RelNode

Operation 其实类似于 SQL 语法树,也构成一个树形结构,并实现了访问者模式,支持使用 QueryOperationVisitor 遍历整棵树,QueryOperationConverter 实现了 QueryOperationVisitor 接口。对于 PlannerQueryOperation,其内部封装的就是已经构建好的 RelNode,直接取出即可;对于其它类型的 Operation,则按需转换为对应的 RelNode

  • 2 优化 RelNode

得到 RelNode 后, Calcite 使用 CommonSubGraphBasedOptimizer 优化器(针对流式、批次的优化分别实现了子类StreamCommonSubGraphBasedOptimizer、BatchCommonSubGraphBasedOptimizer)进行 RelNode 的优化流程。优化器将拥有共同子树的 RelNode 看作一个 DAG 结构,并将 DAG 划分成 RelNodeBlock,然后在RelNodeBlock 的基础上进行优化操作。这和正常的 Calcite 处理流程还是保持一致的

CommonSubGraphBasedOptimizer extends Optimizer ,核心代码在 optimize() 和 doOptimize()

abstract class CommonSubGraphBasedOptimizer extends Optimizer {
  override def optimize(roots: Seq[RelNode]): Seq[RelNode] = {
    //以RelNodeBlock为单位进行优化,在子类(StreamCommonSubGraphBasedOptimizer,BatchCommonSubGraphBasedOptimizer)中实现
    val sinkBlocks = doOptimize(roots)
    //获得优化后的逻辑计划
    val optimizedPlan = sinkBlocks.map { block =>
      val plan = block.getOptimizedPlan
      require(plan != null)
      plan
    }
    //将 RelNodeBlock 使用的中间表展开
    expandIntermediateTableScan(optimizedPlan)
  }

Caclite 用一套基于规则的框架优化逻辑计划,用户可以通过添加规则进行扩展,Flink 也是基于自定义规则来实现整个优化过程。优化器主要涉及三个方法doOptimize 、optimizeBlock、optimizeTree

实时和离线分别对应 StreamCommonSubGraphBasedOptimizer 和 BatchCommonSubGraphBasedOptimizer,里面做了优化,具体可以看这两个类的上诉3个方法

经过优化器处理后,得到的逻辑树中的所有节点都是FlinkPhysicalRel ,以待生成物理执行计划了,也就是第三步 translateToExecNodeGraph()

  • 3 转换成 ExecNodeGraph

首先要将 FlinkPhysicalRel 构成的 DAG 转换成 ExecNodeGraph ,因为可能存在共用子树的情况,这里还会尝试共用相同的子逻辑计划。由于通常 FlinkPhysicalRel 的具体实现类通常也实现了 ExecNode 接口,所以这一步转换较为简单。

  • 4 转换为底层的 Transformation 算子 

得到 ExecNodeGraph 后,就可以尝试生成物理执行计划(Transformation 算子),基于算子构建 Flink 的 DAG 然后执行。

4. SQL执行

Executor抽象了SQL执行过程,DefaultExecutor是其默认实现。在得到 Transformation 后,利用 Transformation 生成 Pipeline (也就是StreamGraph),然后就可以提交 Flink 任务执行了

接着退回到executeSql() ,看看 return executeInternal(operation) 做了什么

    @Override
    public TableResultInternal executeInternal(Operation operation) {
        // delegate execution to Operation if it implements ExecutableOperation
        if (operation instanceof ExecutableOperation) {
            return ((ExecutableOperation) operation).execute(operationCtx);
        }

        // otherwise, fall back to internal implementation
        if (operation instanceof ModifyOperation) {
            return executeInternal(Collections.singletonList((ModifyOperation) operation));
        } else if (operation instanceof StatementSetOperation) {
            return executeInternal(((StatementSetOperation) operation).getOperations());
        } else if (operation instanceof ExplainOperation) {
            ExplainOperation explainOperation = (ExplainOperation) operation;
            ExplainDetail[] explainDetails =
                    explainOperation.getExplainDetails().stream()
                            .map(ExplainDetail::valueOf)
                            .toArray(ExplainDetail[]::new);
            Operation child = ((ExplainOperation) operation).getChild();
            List<Operation> operations;
            if (child instanceof StatementSetOperation) {
                operations = new ArrayList<>(((StatementSetOperation) child).getOperations());
            } else {
                operations = Collections.singletonList(child);
            }
            String explanation = explainInternal(operations, explainDetails);
            return TableResultImpl.builder()
                    .resultKind(ResultKind.SUCCESS_WITH_CONTENT)
                    .schema(ResolvedSchema.of(Column.physical("result", DataTypes.STRING())))
                    .data(Collections.singletonList(Row.of(explanation)))
                    .build();
        } else if (operation instanceof QueryOperation) {
            return executeQueryOperation((QueryOperation) operation);
        } else if (operation instanceof ExecutePlanOperation) {
            ExecutePlanOperation executePlanOperation = (ExecutePlanOperation) operation;
            try {
                return (TableResultInternal)
                        executePlan(
                                PlanReference.fromFile(
                                        resourceManager.registerFileResource(
                                                new ResourceUri(
                                                        ResourceType.FILE,
                                                        executePlanOperation.getFilePath()))));
            } catch (IOException e) {
                throw new TableException(
                        String.format(
                                "Failed to execute %s statement.", operation.asSummaryString()),
                        e);
            }
        } else if (operation instanceof CompilePlanOperation) {
            CompilePlanOperation compilePlanOperation = (CompilePlanOperation) operation;
            compilePlanAndWrite(
                    compilePlanOperation.getFilePath(),
                    compilePlanOperation.isIfNotExists(),
                    compilePlanOperation.getOperation());
            return TableResultImpl.TABLE_RESULT_OK;
        } else if (operation instanceof CompileAndExecutePlanOperation) {
            CompileAndExecutePlanOperation compileAndExecutePlanOperation =
                    (CompileAndExecutePlanOperation) operation;
            CompiledPlan compiledPlan =
                    compilePlanAndWrite(
                            compileAndExecutePlanOperation.getFilePath(),
                            true,
                            compileAndExecutePlanOperation.getOperation());
            return (TableResultInternal) compiledPlan.execute();
        } else if (operation instanceof AnalyzeTableOperation) {
            if (isStreamingMode) {
                throw new TableException("ANALYZE TABLE is not supported for streaming mode now");
            }
            try {
                return AnalyzeTableUtil.analyzeTable(this, (AnalyzeTableOperation) operation);
            } catch (Exception e) {
                throw new TableException("Failed to execute ANALYZE TABLE command", e);
            }
        } else if (operation instanceof NopOperation) {
            return TableResultImpl.TABLE_RESULT_OK;
        } else {
            throw new TableException(UNSUPPORTED_QUERY_IN_EXECUTE_SQL_MSG);
        }
    }

可以重点关注下几个return,会判断你是什么类型的语句,然后执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

orange大数据技术探索者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值