实战|Flink不支持分库分表的改造之路(一)

WilliamGates
发布于 2022-6-20 17:53
浏览
0收藏

 

大家好,我是威哥,《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数据拆分的原理如下图所示:

实战|Flink不支持分库分表的改造之路(一)-鸿蒙开发者社区

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}

 

文章转自公众号:中间件兴趣圈

标签
已于2022-6-20 17:53:38修改
收藏
回复
举报
回复
    相关推荐