基于Apache Hudi的多库多表实时入湖最佳实践

1. 前言

CDC(Change Data Capture)从广义上讲所有能够捕获变更数据的技术都可以称为CDC,但本篇文章中对CDC的定义限定为以非侵入的方式实时捕获数据库的变更数据。例如:通过解析MySQL数据库的Binlog日志捕获变更数据,而不是通过SQL Query源表捕获变更数据。Hudi 作为最热的数据湖技术框架之一, 用于构建具有增量数据处理管道的流式数据湖。其核心的能力包括对象存储上数据行级别的快速更新和删除,增量查询(Incremental queries,Time Travel),小文件管理和查询优化(Clustering,Compactions,Built-in metadata),ACID和并发写支持。Hudi不是一个Server,它本身不存储数据,也不是计算引擎,不提供计算能力。其数据存储在S3(也支持其它对象存储和HDFS),Hudi来决定数据以什么格式存储在S3(Parquet,Avro,…), 什么方式组织数据能让实时摄入的同时支持更新,删除,ACID等特性。Hudi通过Spark,Flink计算引擎提供数据写入, 计算能力,同时也提供与OLAP引擎集成的能力,使OLAP引擎能够查询Hudi表。从使用上看Hudi就是一个JAR包,启动Spark, Flink作业的时候带上这个JAR包即可。Amazon EMR 上的Spark,Flink,Presto ,Trino原生集成Hudi, 且EMR的Runtime在Spark,Presto引擎上相比开源有2倍以上的性能提升。在多库多表的场景下(比如:百级别库表),当我们需要将数据库(mysql,postgres,sqlserver,oracle,mongodb等)中的数据通过CDC的方式以分钟级别(1minute+)延迟写入Hudi,并以增量查询的方式构建数仓层次,对数据进行实时高效的查询分析时。我们要解决三个问题,第一,如何使用统一的代码完成百级别库表CDC数据并行写入Hudi,降低开发维护成本。第二,源端Schema变更如何同步到Hudi表。第三,使用Hudi增量查询构建数仓层次比如ODS->DWD->DWS(各层均是Hudi表),DWS层的增量聚合如何实现。本篇文章推荐的方案是: 使用Flink CDC DataStream API(非SQL)先将CDC数据写入Kafka,而不是直接通过Flink SQL写入到Hudi表,主要原因如下,第一,在多库表且Schema不同的场景下,使用SQL的方式会在源端建立多个CDC同步线程,对源端造成压力,影响同步性能。第二,没有MSK做CDC数据上下游的解耦和数据缓冲层,下游的多端消费和数据回溯比较困难。CDC数据写入到MSK后,推荐使用Spark Structured Streaming DataFrame API或者Flink StatementSet 封装多库表的写入逻辑,但如果需要源端Schema变更自动同步到Hudi表,使用Spark Structured Streaming DataFrame API实现更为简单,使用Flink则需要基于HoodieFlinkStreamer做额外的开发。Hudi增量ETL在DWS层需要数据聚合的场景的下,可以通过Flink Streaming Read将Hudi作为一个无界流,通过Flink计算引擎完成数据实时聚合计算写入到Hudi表。

2. 架构设计与解析

基于Apache Hudi的多库多表实时入湖最佳实践

2.1 CDC数据实时写入MSK

图中标号1,2是将数据库中的数据通过CDC方式实时发送到MSK(Amazon托管的Kafka服务)。flink-cdc-connectors[1]是当前比较流行的CDC开源工具。它内嵌debezium[2]引擎,支持多种数据源,对于MySQL支持Batch阶段(全量同步阶段)并行,无锁,Checkpoint(可以从失败位置恢复,无需重新读取,对大表友好)。支持Flink SQL API和DataStream API,这里需要注意的是如果使用SQL API对于库中的每张表都会单独创建一个链接,独立的线程去执行binlog dump。如果需要同步的表比较多,会对源端产生较大的压力。在需要整库同步表非常多的场景下,应该使用DataStream API写代码的方式只建一个binlog dump同步所有需要的库表。另一种场景是如果只同步分库分表的数据,比如user表做了分库,分表,其表Schema都是一样的,Flink CDC的SQL API支持正则匹配多个库表,这时使用SQL API同步依然只会建立一个binlog dump线程。需要说明的是通过Flink CDC可以直接将数据Sink到Hudi, 中间无需MSK,但考虑到上下游的解耦,数据的回溯,多业务端消费,多表管理维护,依然建议CDC数据先到MSK,下游再从MSK接数据写入Hudi。

2.2 CDC工具对比

图中标号3,除了flink-cdc-connectors之外,DMS(Amazon Database Migration Services)是Amazon 托管的数据迁移服务,提供多种数据源(mysql,oracle,sqlserver,postgres,mongodb,documentdb等)的CDC支持,支持可视化的CDC任务配置,运行,管理,监控。因此可以选择DMS作为CDC的解析工具,DMS支持将MSK或者自建Kafka作为数据投递的目标,所以CDC实时同步到MSK通过DMS可以快速可视化配置管理。当然除了DMS之外还有很多开源的CDC工具,也可以完成CDC的同步工作,但需要在EC2上搭建相关服务。下图列出了CDC工具的对比项,供大家参考

基于Apache Hudi的多库多表实时入湖最佳实践

2.3 Spark Structured Streaming多库表并行写Hudi及Schema变更

图中标号4,CDC数据到了MSK之后,可以通过Spark/Flink计算引擎消费数据写入到Hudi表,我们把这一层我们称之为ODS层。无论Spark还是Flink都可以做到数据ODS层的数据落地,使用哪一个我们需要综合考量,这里阐述一些相对重要的点。首先对于Spark引擎,我们一定是使用Spark Structured Streaming 消费MSK写入Hudi,由于可以使用DataFrame API写Hudi, 因此在Spark中可以方便的实现消费CDC Topic并根据其每条数据中的元信息字段(数据库名称,表名称等)在单作业内分流写入不同的Hudi表,封装多表并行写入逻辑,一个Job即可实现整库多表同步的逻辑。样例代码截图如下,完整代码点击Github[3]获取

基于Apache Hudi的多库多表实时入湖最佳实践

我们知道CDC数据中是带着I(insert)、U(update)、D(delete)信息的, 不同的CDC工具数据格式不同,但要表达的含义是一致的。使用Spark写入Hudi我们主要关注U、D信息,数据带着U信息表示该条数据是一个更新操作,对于Hudi而言只要设定源表的主键为Hudi的recordKey,同时根据需求场景设定precombineKey即可。这里对precombineKey做一个说明,它表示的是当数据需要更新时(recordKey相同), 默认选择两条数据中precombineKey的大保留在Hudi中。其实Hudi有非常灵活的Payload机制,通过参数hoodie.datasource.write.payload.class可以选择不同的Payload实现,比如Partial Update(部分字段更新)的Payload实现OverwriteNonDefaultsWithLatestAvroPayload,也可以自定义Payload