Flink源码分析:Flink SQL Submit源码提交过程

系列文章

Flink源码分析:基于事件时间场景下WaterMark源码分析

Flink源码分析: 重启策略机制RestartStrategy

Flink源码分析: 广播状态流实现动态更新或字段参数变更

Flink源码分析: Flink JDBC Upsert模式实现原理

目录

系列文章

一. SqlClient.class

加载外部jar包

加载lib包下的jar包

创建一个本地执行器

用执行器加载默认环境创建客户端

客户端中打开一个新的会话

执行SQL

总结

关注收藏不迷路~~

一. SqlClient.class

我们打开Flink Sql启动客户端脚本sql-client.sh,从中看出脚本主要是调用了

org.apache.flink.table.client.SqlClient这个类。
# start client without jar
    exec $JAVA_RUN $JVM_ARGS "${log_setting[@]}" -classpath "`manglePathList "$CC_CLASSPATH:$INTERNAL_HADOOP_CLASSPATHS"`" org.apache.flink.table.client.SqlClient "$@"

 我们找到SqlClient.class这个类,首先SQL Client作用是为了用户提交SQL语句,并且支持两种模式:embedded mode 和 gateway mode (Flink1.12.0版本不支持getway模式,下面的源码里写了)。

创建SqlClient对象时会调用start()类型,这里主要是封装了SqlClient的主要SQL提交操作。

private void start() {
        if (isEmbedded) {
            // create local executor with default environment
            final List<URL> jars;
            if (options.getJars() != null) {
                jars = options.getJars();
            } else {
                jars = Collections.emptyList();
            }
            final List<URL> libDirs;
            if (options.getLibraryDirs() != null) {
                libDirs = options.getLibraryDirs();
            } else {
                libDirs = Collections.emptyList();
            }
            final Executor executor = new LocalExecutor(options.getDefaults(), jars, libDirs);
            executor.start();

            // create CLI client with session environment
            final Environment sessionEnv = readSessionEnvironment(options.getEnvironment());
            appendPythonConfig(sessionEnv, options.getPythonConfiguration());
            final SessionContext context;
            if (options.getSessionId() == null) {
                context = new SessionContext(DEFAULT_SESSION_ID, sessionEnv);
            } else {
                context = new SessionContext(options.getSessionId(), sessionEnv);
            }

            // Open an new session
            String sessionId = executor.openSession(context);
            try {
                // add shutdown hook
                Runtime.getRuntime()
                        .addShutdownHook(new EmbeddedShutdownThread(sessionId, executor));

                // do the actual work
                openCli(sessionId, executor);
            } finally {
                executor.closeSession(sessionId);
            }
        } else {
            throw new SqlClientException("Gateway mode is not supported yet.");
        }
    }

start()私有方法主要干了以下几件事:

  • 加载外部jar包
  • 加载lib包下的jar包
  • 创建一个本地执行器
  • 用执行器加载默认环境创建客户端
  • 客户端中打开一个新的会话
  • 执行SQL

加载外部jar包

是一个外部连接的集合,这里的Jar包可以是程序所需要的的依赖,也可以是自定义函数

加载lib包下的jar包

是一个外部连接的集合,这里的Jar包可以是程序所需要的的依赖,也可以是自定义函数

创建一个本地执行器

LocalExecutor对象的作用主要是用来提交程序以及返回执行结果,本地执行器会加载上文的外部Jar包以及默认的配置文件。

用执行器加载默认环境创建客户端

从CliOptions里加载环境信息,初始化一个默认环境给执行器创建客户端,这个默认环境由系统默认的SessionID决定。

客户端中打开一个新的会话

这里没什么好讲的,源码里很简单

执行SQL

我们来看下执行SQL的过程,主要封装在openCli方法中,该方法需要传入两个参数:

String sessionId, Executor executor

当所有环境配置都准备好,会执行以下代码提交SQL语句:

try (CliClient cli = new CliClient(sessionId, executor, historyFilePath)) {
            // interactive CLI mode
            if (options.getUpdateStatement() == null) {
                cli.open();
            }
            // execute single update statement
            else {
                final boolean success = cli.submitUpdate(options.getUpdateStatement());
                if (!success) {
                    throw new SqlClientException(
                            "Could not submit given SQL update statement to cluster.");
                }
            }
        }

可以看出关键点在

cli.submitUpdate(options.getUpdateStatement())

点进去看下submitUpdate源码

public boolean submitUpdate(String statement) {
        terminal.writer().println(CliStrings.messageInfo(CliStrings.MESSAGE_WILL_EXECUTE).toAnsi());
        terminal.writer().println(new AttributedString(statement).toString());
        terminal.flush();

        final Optional<SqlCommandCall> parsedStatement = parseCommand(statement);
        // only support INSERT INTO/OVERWRITE
        return parsedStatement
                .map(
                        cmdCall -> {
                            switch (cmdCall.command) {
                                case INSERT_INTO:
                                case INSERT_OVERWRITE:
                                    return callInsert(cmdCall);
                                default:
                                    printError(CliStrings.MESSAGE_UNSUPPORTED_SQL);
                                    return false;
                            }
                        })
                .orElse(false);
    }

 这里一个很关键的地方:

final Optional<SqlCommandCall> parsedStatement = parseCommand(statement);

这条代码的作用很神奇,它可以把SQL语言转成Table API来执行,这就是我们常说的数据库的解析过程,从上层的SQL解析成底层代码API执行,接着跟进这段代码:

private Optional<SqlCommandCall> parseCommand(String line) {
        final SqlCommandCall parsedLine;
        try {
            parsedLine = SqlCommandParser.parse(executor.getSqlParser(sessionId), line);
        } catch (SqlExecutionException e) {
            printExecutionException(e);
            return Optional.empty();
        }
        return Optional.of(parsedLine);
    }

 接着跟进SqlCommandParse这个类,来看看这个类的结构:

 看到了吧,SQL的常规语法全部在里面,String类型的SQL字符串会在这里做解析成语法树,这已经是Table API层面的代码了。由于篇幅太长,会在下期具体讲解SQL是如何解析的。

到这里一个SQL语句通过客户端提交流程已经完全讲完了,再次帮大家总结下SQL提交过程:

加载Jar包(jar/lib包下) -> 创建本地执行器 -> 初始化环境 -> 创建一个新的会话 -> 提交SQL -> 提交后交给Table API层面的解析器 -> 生成语法树


总结

下面一章节会给大家讲解Flink SQL在解析层面的源码是如果走的,记得关注收藏哦!!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

XuTengRui

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

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

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

打赏作者

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

抵扣说明:

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

余额充值