Flink stream API和table SQL API下多流,多分枝,流复用 支持的讨论
一、stream API
stream API是支持将一个流(DataStream)使用多次的。很简单例子。可以将一个流打印多次。
需要注意:stream API中是可以使用Table API的。
基本流程是:
StreamSource --> Stream API --> StreamToTable --> Table API 使用各种转化 --> TableToStream --> StreamSink
即只要开头 和 结尾不使用Table API就可以了。如source不能使用 tableEnv.from(tablePath)
或者 tableEnv.sqlQuery( ... )
而sink 不能使用 sourceTblObj.executeInsert(distintionPath);
和 tenv.executeSql( ... )
。
而在中间转换过程中是可以使用 tableEnv.createTemporaryView(表路径, 表对象)
将从而使用 Table new tblObj=tableEnv.sqlQuery( "SQL 语句" )
进行相关的转换。因此在中间转换过程中可以实现SQL 和 DSL风格语句无缝转换。flink 内部会自动将SQL翻译为DSL的执行语句的。
二、Table API
2.1 结论:
table API仅仅支持一个表 输入和 输出。并不支持。一个表简单的多次使用。如果需要多次使用一个表需要执行tableEnv.createStatementSet()
API才可以。另外经过验证,将一个Table转换为DataStream 再转换会Table一样也不能实现多次使用一个表,也就是没法实现Table对象的复制。
2.2 官网原文解释
官网原文(1)flink 1.13
当一个表被多次使用的时候,会将查询的过程内联(可能像面向对象的内联对象),结果就是追溯流的来源多次执行。
比如一个flink sql jdbc表被多次查询,实际就是向数据库 申请了多次查询。或者 flink cdc stream API 将stream转化为table后,多次使用table对象,会造成flink cdc 向数据库多次申请获取日志,而不是branch(复用)已有的Stream。
Note: Table objects are similar to VIEW’s from relational database systems, i.e., the query that defines the Table is not optimized but will be inlined when another query references the registered Table. If multiple queries reference the same registered Table, it will be inlined for each referencing query and executed multiple times, i.e., the result of the registered Table will not be shared.
官网原文(2) flink 1.13:
注意这里说的branch
就是将一个流多次使用,即多分支任务的作用。
可以看到对于Table API 当不使用 statement set
的时候,就只支持 a single source-to-sink pipeline
。
DataStream API
The DataStream API’s StreamExecutionEnvironment acts as a builder pattern to construct a complex pipeline. The pipeline possibly splits into multiple branches that might or might not end with a sink.
At least one sink must be defined. Otherwise, the following exception is thrown:
java.lang.IllegalStateException: No operators defined in streaming topology. Cannot execute.
StreamExecutionEnvironment.execute() submits the entire constructed pipeline and clears the builder afterward. In other words: no sources and sinks are declared anymore, and a new pipeline can be added to the builder. Thus, every DataStream program usually ends with a call to StreamExecutionEnvironment.execute(). Alternatively, DataStream.executeAndCollect() implicitly defines a sink for streaming the results to the local client and only executes the current branch.
Table API
In the Table API, branching pipelines is only supported within a StatementSet where each branch must declare a final sink. Both TableEnvironment and also StreamTableEnvironment do not offer a dedicated general execute() method. Instead, they offer methods for submitting a single source-to-sink pipeline or a statement set:
引用官网的 createStatementSet 案例:
// execute with explicit sink
tableEnv.from("InputTable").executeInsert("OutputTable")
tableEnv.executeSql("INSERT INTO OutputTable SELECT * FROM InputTable")
tableEnv.createStatementSet()
.addInsert("OutputTable", tableEnv.from("InputTable"))
.addInsert("OutputTable2", tableEnv.from("InputTable"))
.execute()
tableEnv.createStatementSet()
.addInsertSql("INSERT INTO OutputTable SELECT * FROM InputTable")
.addInsertSql("INSERT INTO OutputTable2 SELECT * FROM InputTable")
.execute()
// execute with implicit local sink
tableEnv.from("InputTable").execute().print()
tableEnv.executeSql("SELECT * FROM InputTable").print()
2.2 能不能绕开
结论:不能的。
是否可以利用stream api 先把流复制一下,再把复制后的流转化为table呢?答案:不行!!!
如下流程也是行不通的:
最终不管是 sourceTblObj.executeInsert(distintionPath);
和 tenv.executeSql( ... )
的insert 都是不可以的。
需要注意:Stream 的 exectue 和 Table API的 execute 方法(execute 是指类似 execute
的方法) 是不会相互触发的。如下的流都是不会执行的。
Table API 都执行了 execute
如果代码最后没有添加 streamEnv.execute(jobName)
的话,都是不会执行的。因为没有完整的流产生。
再举个例子:
比如向通过flink CDC stream API将在日志中不同表读取出来再通过tableApi Sink到其他库是行不通的。
如下表的流程是走不通的。
这样会导致日志被多次读取。有几张表和table 的exectue
就会读取几次日志。
如下图,日志会被读取了多次。
比如两个insert debezium会打印如下:
九月 13, 2023 5:24:08 下午 com.github.shyiko.mysql.binlog.BinaryLogClient connect
信息: Connected to 10.80.21.156:3306 at mysql-bin.000008/77061057 (sid:5917, cid:11536)
九月 13, 2023 5:24:08 下午 com.github.shyiko.mysql.binlog.BinaryLogClient connect
信息: Connected to 10.80.21.156:3306 at mysql-bin.000008/77061057 (sid:5715, cid:11535)
... for job 7edab4e5af3e3704553e83b691899958 ...
... for job 11709cd2cbef89294f10495e624a5531 ...
... for job f080b028795a2e4860fce91c6dd4836b ...
其中sid是指mysql 的serverId,而cid是指连接mysql的connectionId 。连续两次打印的sid 和 cid都不同。
说明debezium伪装成两个slaver去连接了master mysql服务器,分别读取日志。mysql要处理两个请求。100张表就意味着一百个连接。没有起到分流的作用。
而日中中 for job ID
发现 会有三个不同的ID,说明启动了三个Job,观察checkpoint文件夹发现会有三个。
三个Job分别是:
(1)stream API (由 streamEnv.execute()
触发)
(2)stream API --> Table API tbl1 sink (由 tbl1.executeInsert()
或 tableEnv.executeSql("insert into tbl1 ... ")
触发)
(3)stream API --> Table API tbl1 sink (由 tbl2.executeInsert()
或 tableEnv.executeSql("insert into tbl2 ... ")
触发)
此时如果如果在stream API中强制指定job-id(PipelineOptionsInternal#PIPELINE_FIXED_JOB_ID
也即 $internal.pipeline.job-id
)就会将三个job的checkpoint指定到一个文件夹。会造成checkpoint报错,内容:chk-n
已经存在。
结论:
Flink中如果想多次branch(分支、复用)一个流stream
请
(1)使用Stream API Source和Sink,不要使用Table的Source或Sink
(2)中间可以将stream转化为table或将table转化为sream 也可以在中间 将 table对象注册到catalog中(createTemporaryView方法)使用DSL或者SQL方式对table进行转化。
引用别人的案例 Flink SQL 一个流 输出 2 次:
本以为会两次输出,想达到如下效果。但是实际测试,只有一次输出。
Table table = tableEnvironment.sqlQuery("xxxxxxxxx");
tableEnvironment.createTemporaryView("base_table",table);
tableEnvironment.executeSql("select a,count(1) ct from base_table group by b").print();//第一次使用
tableEnvironment.executeSql("select b,count(1) ct from base_table group by a").print();//第二次使用
其实flink提供了statementset
方式,一个表多次复用。其中也是使用stream api的branch方式。
DataStream API Integration
官网案例:
// execute with explicit sink
tableEnv.from("InputTable").executeInsert("OutputTable")
tableEnv.executeSql("INSERT INTO OutputTable SELECT * FROM InputTable")
tableEnv.createStatementSet()
.addInsert("OutputTable", tableEnv.from("InputTable"))
.addInsert("OutputTable2", tableEnv.from("InputTable"))
.execute()
tableEnv.createStatementSet()
.addInsertSql("INSERT INTO OutputTable SELECT * FROM InputTable")
.addInsertSql("INSERT INTO OutputTable2 SELECT * FROM InputTable")
.execute()
// execute with implicit local sink
tableEnv.from("InputTable").execute().print()
tableEnv.executeSql("SELECT * FROM InputTable").print()
SQL API
同样一张表两次使用需要用到statementset
方式。
Execute a set of SQL statements
语法:
注意:
(1)需要在flink sql-client中执行。
(2)实际要一个一个sql的执行。
(3)实际执行的时候不要/没有 { }+
。
BEGIN STATEMENT SET;
-- one or more INSERT INTO statements
{ INSERT INTO|OVERWRITE <select_statement>; }+
END;