flink source 同步_Flink深入浅出:JDBC Source从理论到实战

88cf08c7ae7766e1a539da22c37a0bcc.png

——图片来自水彩画吧@白马苏瓦

Flink 1.10之后针对Table API&SQL提供了很多外部连接器,可以使用DDL快速创建外部表,从而可以在Flink中基于SQL直接读取外部数据源。在大数据环境中,有一个很经典的场景是数据交换——即以一定的时间周期把业务库中的数据同步到hive或者hdfs中,下面就先介绍官方的使用方法,再通过源码分析其中的技术细节。

1 表定义

jdbc表的定义跟普通的表定义,区别就在于with中的参数:

CREATE TABLE MyUserTable (  ... ) WITH ( 
'connector.type' = 'jdbc', 
'connector.url' = 'jdbc:mysql://localhost:3306/flink-test', 
'connector.table' = 'jdbc_table_name', 
'connector.driver' = 'com.mysql.jdbc.Driver', 
'connector.username' = 'name', 
'connector.password' = 'password', 
-- 以上都是必填参数 
'connector.read.partition.column' = 'column_name', 
'connector.read.partition.num' = '50', 
'connector.read.partition.lower-bound' = '500', 
'connector.read.partition.upper-bound' = '1000', 
'connector.read.fetch-size' = '100' 
)

password前面的参数都是必填项,后面的connector.read相关的参数都是与读取有关的。如果没有配置这些参数,那么flink不会采用任何分区,无论你的并行度设置的多大,其实只有一个slot在工作,并且执行的是全量数据的查询,这个细节稍后再说

2 表使用

如果想实现数据的抽取同步,一种比较简单的方法就是:

insert overwrite ods.xxx partition (ods_date='2020-01-03') 
select * from jdbc_table_name  
where time>'2020-05-08' and time < '2020-05-09'

这样就实现了数据交换的过程,想法很简单,现实很残酷。如果仔细观察flink ui中各个slot的执行情况就不难发现,无论并行度设置多大,总是其中一个slot执行非常缓慢....

3 原理分析

Flink通过SPI寻找到JDBC对应的TableSource,内部会执行下面的方法创建inputformat,其中164行的query就是真正提交给jdbc执行的代码。

98fab52868991b60cd0f33f4391863b0.png

191911b047801f0f56cec433e696f88f.png

仔细看代码165行,conditionFields传入的值总是为空,因此这坨sql根本不会出现where条件(只有当分区字段存在时,flink才会基于分区字段生成where条件;而我们自己sql中的where条件直接被忽略了)。回头再来说说第二部分我们的查询sql:

select * from jdbc_table_name 
where time>'2020-05-08' and time < '2020-05-09'

会最终形成两个步骤:

1 基于jdbc执行select * from jdbc_table_name

2 在flink中基于time过滤数据

5112abe13f8d9efa0d9a7bae09d04e8f.png

假设现在的并行度为3,也就是3个slot在工作,但是其实只有一个slot执行了查询任务。这样就相当于某个slot需要全表扫描数据,然后在内存中过滤数据,返回我们想要的那部分。如果源表数据量非常大,那这个sql几乎是跑不通的。

一种优化方案是使用flink提供的partition.column字段指定分区字段,然后指定上下界,flink内部会基于上下界和分区个数,形成一个个的分区范围并行查询。

d60d68ffdfdbba8be46bcbbc6e469384.png

比如还是上面的sql,假如我配置为:

'connector.read.partition.column' = 'id', 
'connector.read.partition.num' = '3', 
'connector.read.partition.lower-bound' = '1000', 
'connector.read.partition.upper-bound' = '10000',

那么最终形成的查询sql为:

select * from jdbc_table_name where id between 1000 and 3999; 
select * from jdbc_table_name where id between 4000 and 6999; 
select * from jdbc_table_name where id between 7000 and 9999;

假设flink source的并行度为3,那么”很有可能“每个slot都分配了一个查询任务,三个slot并行查询,这样效率就提升很多了。

19b009a1800ff2f21c2af7cd23f5a299.png

注意,是很有可能!flink中source的分区任务分配并不是均衡的,而是抢占式的,所以我们需要尽量把sql的分区数设置的大一些,才能保证每个slot执行的任务是尽量均衡的。

如果想要利用上面的机制加速数据抽取的速度,可以采用投机取巧的方法:查询目标时间范围内的最大id和最小id,设置为分区字段的上下界。不过这样也不一定能解决问题,如果上下界范围过大,效率一样提不上来。

4 优化实战

由于flink官方仅仅提供了一种基于long类型字段的数字分区方法,因此不能实现基于时间范围的查询,这个时间范围也无法直接下推到jdbc层。所以可以自定义一种split方法,上下界改成时间范围的上下界,中间基于分区数自动计算时间范围。比如时间范围为2020-05-08 00:00:00~2020-05-09 00:00:00,假设分区数为24,那么一个小时就是一个分区范围,这样优化后速度就快很多了。

416858c4d04e17e58b88cfbee1c05c7b.png

如果再考虑的全面点,每天的数据变化是不一样的,往往呈正态分布的趋势。比如白天时间段数据两会很大,夜间数据量往往比较小。如果希望分区的数据量更平均一点,可以使用加权分区方法,把各个时间段的数据量考虑进来,这样就能尽量保证每个分区的数据均衡了。

往期推荐:

Flink深入浅出:Sql Gateway源码分析​mp.weixin.qq.com
2da91db8ae4bd5f3bd06d44178777fed.png
Flink深入浅出:JDBC Connector源码分析​mp.weixin.qq.com
f6997b7e2b7c988608f0e9e289d11c88.png
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
非常感谢您的提问!如果您想要在Flink中获取MySQL多张表的信息,可以按照如下步骤进行: 1. 首先需要在Flink中使用JDBC连接器连接MySQL数据库,并创建一个JDBC输入源,以便从MySQL中读取数据。 2. 然后可以通过Flink的Table API或SQL API将多张表的数据进行连接或者关联,从而得到您需要的数据。 3. 最后可以使用自定义的Sink将数据写入MySQL中。下面就是一个简单的Java代码示例,可以帮助您实现该功能: ``` public class FlinkMySQLSink { public static void main(String[] args) throws Exception { // set up the execution environment StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); // set up JDBC connection options String url = "jdbc:mysql://localhost:3306/test"; String username = "root"; String password = "password"; String driverName = "com.mysql.jdbc.Driver"; // create a JDBC input source to read multiple tables from MySQL JdbcInputFormat jdbcInputFormat = JdbcInputFormat.buildJdbcInputFormat() .setDrivername(driverName) .setDBUrl(url) .setUsername(username) .setPassword(password) .setQuery("SELECT * FROM table1; SELECT * FROM table2;") .finish(); // create a data stream from the JDBC input source DataStream<Tuple2<String, String>> inputDataStream = env.createInput(jdbcInputFormat); // use Table API or SQL API to join or combine multiple tables Table table = inputDataStream .map(new MapFunction<Tuple2<String, String>, Row>() { public Row map(Tuple2<String, String> value) throws Exception { return Row.of(value.f0, value.f1); } }) .toTable(new TableSchema(new String[]{"column1", "column2"}, new TypeInformation[]{Types.STRING, Types.STRING})); // create a custom Sink to write data back to MySQL JDBCOutputFormat jdbcOutputFormat = JDBCOutputFormat.buildJDBCOutputFormat() .setDrivername(driverName) .setDBUrl(url) .setUsername(username) .setPassword(password) .setQuery("INSERT INTO result_table (column1, column2) VALUES (?, ?)") .finish(); // write the data stream to the custom Sink table.writeToSink(jdbcOutputFormat); // execute the Flink job env.execute("Flink MySQL Sink Example"); } } ``` 在这个示例中,我们首先设置了JDBC连接器所需的参数,然后使用JdbcInputFormat创建了一个JDBC输入源,该源可以从MySQL中读取多个表的数据。 接下来,我们使用Table API或SQL API将多个表的数据连接或者关联起来,并生成一个包含所需数据的Table对象。 最后,我们使用自定义的JDBCOutputFormat创建一个Sink,将Table中的数据写回到MySQL中。在这个Sink中,我们需要指定要写入哪个表,以及如何将数据映射到表中的列。 希望这个示例可以帮助您实现获取MySQL多张表信息的功能!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值