整库入湖方案设计方法

Posted 咬定青松

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了整库入湖方案设计方法相关的知识,希望对你有一定的参考价值。

整库入湖,顾名思义就是将整个库中的表入湖,为甚强调整库?先来看看如何进行单表入湖。

单表入湖有三种实现方式:

1. 全量入湖

一次性将整个表的数据入湖,实现单表整库入湖有很多工具可以完成,类似的工具有Datax、Sqoop、Kettle等,说类似是因为这些工具暂时还不支持最新数据湖,比如Iceberg、Hudi、Deltlake,但是经过开发修改是能够满足这类入湖需求的。Datax、Sqoop、Kettle之所以存在,是因为它们面向Hadoop生态,且表现不错,特别是面向高延时(T-1)数据同步场景,即使采用周期性全量替换数据湖数据已有数据也是可接受的,毕竟这种方式不存在数据错乱、丢失、重复、Schema不兼容等问题,但数据同步数据量较大,时效性太差。

2. 历史全量入湖+离线增量入湖

历史数据先通过上述全量一次性入湖后,再通过离线增量入湖,减轻了每次全量入湖的数据总量,且时效性大大提高。类似的工具仍然可以采用Datax、Sqoop、Kettle等,因为这些工具支持增量查询数据源,前提是数据源本身存在自增属性、带有能标识增量更新的时间戳属性等。这种方式能解决80%以上的数据同步需求,但是对实时性要求较高的场景无能为力。

3. 历史全量入湖+实时增量入湖

将上述离线增量入湖换成实时入湖就能解决余下20%的实时需求。实现实时入湖的工具有Debezium、Canal等,Debezium、Canal不仅支持通过API方式直接读取并入湖,还提供了现成服务,允许用户自定义配置将binlog数据发送到Kafka,然后再入湖。

这三种方式,后面两种增量入湖方式都存在数据重复或更新问题,但是处理方式不同,前者要求用户主动将增量部分跟全量部分合并生成新的全量数据,后者通过数据湖的核心能力(支持去重与更新)自动完成数据合并(这是我们对数据湖的期许,但是现实可能没那么美好)。本文重点关注实时增量入湖。

既然Debezium、Canal支持通过API方式直接读取并入湖,为什么要用Kafka呢?主要有几个原因:

1. binlog的产生速率可能会高于数据入湖速率,且binlog存储有存储时长约束,Kafka可以作为数据缓冲;

2. binlog数据可能被下游多次读取,用于不同作用的消费,Kafka存储binlog可以一数多用;

3. binlog一般不支持并行读取,用kafka存储可以实现并行消费;

基于Kafka实现的实时增量数据入湖的典型架构:

数据库的一张表,映射到kafka的一个Topic,Topic包含多个分区,同一份数据可以按照主键均衡分布多个分区。下游通过不同类型的算子并行执行Kafka分区读取数据、写入临时文件和提交文件最终入湖的操作。

一张表是这样,多张表是不是上述架构的简单叠加呢?显然不是!

为了节省资源,可以将多张表投递到同一个Kafka Topic,如分库分表的情形。实际上,投递规则可以更一般化,这方面Canal做得比较完善,它支持正则表达式定义投递规则,有以下几种常见方式,比如:

  • 例子1:test\\\\.test 指定匹配的单表,发送到以test_test为名字的topic上

  • 例子2:.*\\\\..* 匹配所有表,则每个表都会发送到各自表名的topic上

  • 例子3:test 指定匹配对应的库,一个库的所有表都会发送到库名的topic上

  • 例子4:test\\\\..* 指定匹配的表达式,针对匹配的表会发送到各自表名的topic上

  • 例子5:test,test1\\\\.test1,指定多个表达式,会将test库的表都发送到test的topic上,test1\\\\.test1的表发送到对应的test1_test1 topic上,其余的表发送到默认的canal.mq.topic值

确定了对Kafka的投递规则后,接下来需要确定如何划分Topic内部分区?不可能将所有表的数据都发到只有一个分区的Topic里面,Canal支持用户设置表名或者主键后自动根据Topic的分区数动态散列数据到不同的分区,它支持以下规则,比如:

  • 例子1:test\\\\.test:pk1^pk2 指定匹配的单表,对应的hash字段为pk1 + pk2

  • 例子2:.*\\\\..*:id 正则匹配,指定所有正则匹配的表对应的hash字段为id

  • 例子3:.*\\\\..*:$pk$ 正则匹配,指定所有正则匹配的表对应的hash字段为表主键(自动查找)

  • 例子4: 匹配规则啥都不写,则默认发到0这个partition上

  • 例子5:.*\\\\..* ,不指定pk信息的正则匹配,将所有正则匹配的表,对应的hash字段为表名

    • 按表hash: 一张表的所有数据可以发到同一个分区,不同表之间会做散列 (会有热点表分区过大问题)

  • 例子6: test\\\\.test:id,.\\\\..* , 针对test的表按照id散列,其余的表按照table散列

在实现整库或多表入湖实现上,可能会存在两个极端路线:

1. 全部库表都投递到同一个Topic上的一个或多个分区上,然后下游使用同一个作业消费入湖(简称方案一)

该方案首先要解决Topic内分区规则,前面介绍Canal支持按照表分区,也可以按照PK分区,而通过表名+PK分区的方式能够保证数据均衡分布,但是对PK要求存在且不可修改,否则容易造成数据顺便错乱。

然后对各分区的数据在进入下游消费的时候再次将相同表名+PK的数据路由到同一个Stream Writer任务中,如果表不支持PK分区,会造成算子级“数据倾斜”。

2. 每个表独立投递到不同的Topic,且独立分区,每个Topic使用独立的作业消费入湖(简称方案二)

两种方式理论上都可行,且都有存在的理由。先比较下优缺点吧:

可见,两种方案的折中不失为一种不错的策略。

最后一个问题:两种方案都涉及到相同的问题,如何解决Schema动态变更?

最简单的办法是binlog要带上Schema信息,或者采用类似Schema Registry的方式。

综上,整库入湖就是通过自动化、资源最优的方式实现可靠地所有表入湖。

以上是关于整库入湖方案设计方法的主要内容,如果未能解决你的问题,请参考以下文章

送你两个神器,关系数据库数据入湖轻松应对

Hudi自带工具DeltaStreamer的实时入湖最佳实践

Hudi自带工具DeltaStreamer的实时入湖最佳实践

通过日志服务实现数据库MySQL 入湖 OSS实践

技术干货|基于Apache Hudi 的CDC数据入湖

技术干货|基于Apache Hudi 的CDC数据入湖「内附干货PPT下载渠道」