实战|Flink不支持分库分表的改造之路(一)
大家好,我是威哥,《RocketMQ技术内幕》作者、RocketMQ社区首席布道师、中通快递基础架构资深架构师,越努力越幸运,唯有坚持不懈,与大家共勉。
1、背景
在flink提供的jdbc-connector中只支持单表的数据同步,但随着业务量的增大,单表记录数过多,会导致数据查询效率降低。
为了解决单表存在的性能瓶颈,会采用分库分表。例如将订单表order拆分为1024张分表:order -> order_0000~order_1023。
显然官方默认提供的flink jdbc插件并不适用这种情况,需要我们将会对flink插件进行改造,从而支持分库分表的数据同步。
2、技术方案
2.1 flink-jdbc-connector简介
我们在创建flink jdbc同步作业时,一般是通过下面的来声明一个table。
-- 在 Flink SQL 中注册一张 MySQL 表 'users'
CREATE TABLE MyUserTable (
id BIGINT,
name STRING,
age INT,
status BOOLEAN,
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'jdbc',
'url' = 'jdbc:mysql://localhost:3306/mydatabase',
'table-name' = 'users',
);
并且提供了可选配置,可以针对一个sql在指定数据固定范围内(scan.partition.lower-bound,scan.partition.upper-bound)根据拆分字段(scan.partition.column)和数量(scan.partition.num),将sql进行等步长拆分。
可选配置如下:
scan.partition.column:用于将输入进行分区的列名
scan.partition.num:分区数。
scan.partition.lower-bound:第一个分区的最小值。
scan.partition.upper-bound:最后一个分区的最大值。
例如我们预估需要通过1000条订单数据,如果不做拆分,基于flink sql的同步语句如下:
select id,name from order
如果按照id拆分成两个子任务,则sql语句如下:
select id,name from order where id between 1 and 50
select id,name from order where id between 51 and 100
上面只是为了方便举例,在真实的生产环境,同步订单表都是千万级别,将一条大SQL拆分成小任务,一方面可以减少对数据表的锁操作,降低对源端数据库的压力,另一方面可以结合flink配置的并发度,并发同步数据,增大同步效率。
基于flink-jdbc-connector数据拆分的原理如下图所示:
2.2 数据分库分表原理探究与技术方案
flink-jdbc-connector数据拆分属性原理如下:
在flink-jdbc-connector包中提供了JdbcParameterValuesProvider接口,被JdbcInputFormat用来计算要运行的并行查询列表(即拆分)。
每个查询将使用由每个JdbcParameterValuesProvider实现提供的矩阵行进行参数化。
public interface JdbcParameterValuesProvider {
/** Returns the necessary parameters array to use for query in parallel a table. */
Serializable[][] getParameterValues();
}
其中getParameterValues()的返回值:Serializable[x][y] ,x值即为SQL拆分的数据,每个x对应的y个元素的一维数组,包含的是每个sql的变量信息,例如上述根据id进行拆分数量为2。
第一个关键点Serializable[][]的二维数组结构为:
//结构 :x=0~1
//Serializable[x] = {{id_min},{id_max}}
Serializable[0] = {1,50}
Serializable[1] = {51,100}
SQL模版语句如下:
select id,name from order where id between ? and ?
那么对于分表来说,其变量相当于又增加了一个table_name,这样我们只需要改动两个地方,就可以实现分表的效果:
在构建Serializable [] [] 时,新增维度:table_name,其结构如下:
//结构 :x=0~2047
//Serializable[x] = {"order_{0000~1023}",{id_min},{id_max}}
Serializable[0] = {"order_0000",1,50}
Serializable[1] = {"order_0000",51,100}
Serializable[2] = {"order_0001",1,50}
Serializable[3] = {"order_0001",51,100}
...
Serializable[2047] = {"order_1023",51,100}
对应SQL的模版为:
select id,name from ${table_name} where id between ? and ?
在分表的基础上继续再推导,例如如果实现2库(10.1.1.2、10.1.1.2),4个schema(order_00~order_03),1024张表,最终拆解如下:
Serializable [] [] 存储数据格式为:
//结构:x=0~2047
//Serializable[x] = {"{db_url}","{schema_name}","order_{0000~1023}",{id_min},{id_max}}
Serializable[0] = {"jdbc://10.1.1.2","order_00","order_0000",1,50}
...
Serializable[2047] = {"jdbc://10.1.1.3","order_03","order_1023",1,50}
对应的SQL模版如下:
select id,name from {table_name} where id between {id_min} and {id_max}
文章转自公众号:中间件兴趣圈