hudi-0.12.1
flink-1.15.2
CDC(change data capture) 保证了完整数据变更,目前主要有两种方式
DB
的 binlog
数据导入。优点是不依赖消息队列,缺点是对 db server 造成压力。接下来我们主要介绍 第二种方式
create database hudi_test;
use hudi_test;
-- 建表
create table person(
id int auto_increment primary key,
name varchar(30),
age int
);
mysql-cdc
参考: https://chbxw.blog.csdn.net/article/details/119841434
使用cdc-2.x
wget https://maven.aliyun.com/repository/central/com/ververica/flink-connector-mysql-cdc/2.0.0/flink-connector-mysql-cdc-2.0.0.jar
Flink SQL>
create database hudi_test;
use hudi_test;
create table person_binlog (
id bigint not null,
name string,
age int,
primary key (id) not enforced
) with (
'connector' = 'mysql-cdc',
'hostname' = 'chb1',
'port' = '3306',
'username' = 'root',
'password' = '123456',
'database-name' = 'flinktest',
'table-name' = 'person'
);
使用mysql-cdc 报错
NoClassDefFoundError: org/apache/flink/shaded/guava18/com/google/common/util/concurrent/Thr
原因在于sql和非sql connector实现中对于shaded guava的处理不同,
使用 flink-sql-connector-mysql-cdc
替代 flink-connector-mysql-cdc
而且2.0.0版本不行,提升到2.2.1版本解决问题。
-- 为了显示更清晰
Flink SQL> SET 'sql-client.execution.result-mode' = 'tableau';
[INFO] Session property has been set.
Flink SQL> SET 'execution.runtime-mode' = 'streaming';
[INFO] Session property has been set.
Flink SQL>
create table person_binlog_sink_kafka(
id bigint not null,
name string,
age int not null,
primary key (id) not enforced -- 主键
) with (
'connector' = 'upsert-kafka' -- kafka connector upsert-kafka
,'topic' = 'cdc_mysql_person_sink'
,'properties.zookeeper.connect' = 'chb1:2181'
,'properties.bootstrap.servers' = 'chb1:9092'
,'key.format' = 'json'
,'value.format' = 'json'
);
Flink SQL>
insert into person_binlog_sink_kafka
select * from person_binlog;
Flink SQL>
create table person_binlog_source_kafka (
id bigint not null,
name string,
age int not null
) with (
'connector' = 'kafka'
,'topic' = 'cdc_mysql_person_sink'
,'properties.bootstrap.servers' = 'chb1:9092'
,'format' = 'json'
,'scan.startup.mode' = 'earliest-offset'
,'properties.group.id' = 'testGroup'
);
2、创建hudi目标表
Flink SQL>
create table person_binlog_sink_hudi (
id bigint not null,
name string,
age int not null,
primary key (id) not enforced -- 主键
) with (
'connector' = 'hudi',
'path' = 'hdfs://chb3:8020/hudi_db/person_binlog_sink_hudi',
'table.type' = 'MERGE_ON_READ',
'write.option' = 'insert'
);
3、将 kafka 中数据 写入 hudi
Flink SQL>
insert into person_binlog_sink_hudi
select * from person_binlog_source_kafka;
如果数据源来源于其他系统,可以使用批量导入数据功能,快速的将存量数据导入hudi。
Flink SQL>
SET 'execution.runtime-mode' = 'streaming'; // 默认是流模式
SET 'execution.checkpointing.interval' = '0'; // 关闭checkpoint, batch模式不支持checkpoint
bulk_insert
的并行度由write.tasks
指定。并行度会影响小文件的数量。理论上,bulk_insert
的并行度是桶的数量(特别是,当每个桶写入到最大文件大小时,它将切换到新的文件句柄。最后,文件的数量 >= write.bucket_assign.tasks
。参考: https://chbxw.blog.csdn.net/article/details/119479967
Flink SQL>
create table person (
id int not null,
name string,
age int not null,
primary key (id) not enforced
) with (
'connector' = 'jdbc',
'url' = 'jdbc:mysql://chb1:3306/flinktest',
'username' = 'root',
'password' = '123456',
'table-name' = 'person'
);
报错 java.lang.Integer cannot be cast to java.lang.Long
, 由于 mysql 中 person的id 是 int 类型, 转为 flink 对应的是 int, 但是在flink建表时 字段为 bigint.所以报错。
Flink SQL>
create table person_binlog_sink_hudi_buck (
id int not null,
name string,
age int not null,
primary key (id) not enforced -- 主键
) with (
'connector' = 'hudi',
'path' = 'hdfs://chb3:8020/hudi_db/person_binlog_sink_hudi_buck',
'table.type' = 'MERGE_ON_READ',
'write.option' = 'bulk_insert' -- 配置 buck_insert 模式
);
Flink SQL>
insert into person_binlog_sink_hudi_buck
select * from person;
一次性的。
在上面使用 buck_insert
已经完成全量数据导入,接下来, 用户可以通过Index Bootstrap
功能实时插入增量数据,保证数据不重复。
WITH 参数
参数名 | 是否必选 | 默认值 | 备注 |
---|---|---|---|
index.bootstrap.enabled |
true | false | 此功能开启,Hudi 表中剩余的记录将一次性加载到Flink状态 |
index.partition.regex |
false | * | 优化选择。设置正则表达式以过滤分区。默认情况下,所有分区都加载到flink状态 |
使用方法
CREATE TABLE
创建一条与Hudi表对应的语句。 注意这个 table.type
配置必须正确。index.bootstrap.enabled = true
来启用index bootstrap
功能flink-conf.yaml
文件中设置Flink checkpoint
的容错机制,设置配置项execution.checkpointing.tolerable-failed-checkpoints = n
(取决于Flink checkpoint
执行时间)checkpoint
成功,表明index bootstrap
完成。index bootstrap
完成后,用户可以退出并保存savepoint
(或直接使用外部 checkpoint`)。index.bootstrap.enable
为 false
。注意:
finish loading the index under partition
以及Load record form file
观察index bootstrap的进度。index bootstrap
已完成。 从checkpoint
恢复时,不需要再次加载索引。Flink SQL>
create table person_binlog_sink_hudi_boot (
id bigint not null,
name string,
age int not null,
primary key (id) not enforced -- 主键
) with (
'connector' = 'hudi',
'path' = 'hdfs://chb3:8020/hudi_db/person_binlog_sink_hudi_buck',
'table.type' = 'MERGE_ON_READ',
'index.bootstrap.enabled'='true'
);
index bootstrap表接cdc表
Flink SQL>
insert into person_binlog_sink_hudi_boot
select * from person_binlog;
Hudi可以保留消息的所有中间变化(I / -U / U / D),然后通过flink的状态计算消费,从而拥有一个接近实时的数据仓库ETL管道(增量计算)。 Hudi MOR表以行的形式存储消息,支持保留所有更改日志(格式级集成)。 所有的更新日志记录可以使用Flink流阅读器。
参数名 | 是否必选 | 默认值 | 备注 |
---|---|---|---|
changelog.enabled |
false | false | 默认是关闭的,即upsert 语义,只有合并的消息被确保保留,中间的更改可以被合并。 设置为true以支持消费所有的更改 |
注意
不管格式是否存储了中间更改日志消息,批(快照)读取仍然合并所有中间更改。
在设置changelog.enable
为true
时,中间的变更也是 best effort: 异步压缩任务将更新日志记录合并到一条记录中,因此如果流源不及时消费,则压缩后只能读取每个key的合并记录。
compress.delta_commits
和compression.delta_seconds
,为读取器保留一些缓冲时间。Flink SQL> SET 'sql-client.execution.result-mode' = 'tableau'; -- table tableau changelog
[INFO] Session property has been set.
Flink SQL> SET 'execution.runtime-mode' = 'streaming';
[INFO] Session property has been set.
Flink SQL>
create table person2(
id bigint not null,
name string,
age int not null,
primary key (id) not enforced -- 主键
) with (
'connector' = 'hudi',
'path' = 'hdfs://chb3:8020/hudi_db/person2',
'table.type' = 'MERGE_ON_READ',
'read.streaming.enabled' = 'true',
'read.streaming.check-interval' = '4',
'changelog.enabled' = 'true'
);
-- 插入数据
insert into person2 values (1, 'chb', 23);
insert into person2 values (1, 'chb', 24);
select * from person2;
创建非changelog表, url 指向person2同一路径
Flink SQL>
create table person3(
id bigint not null,
name string,
age int not null,
primary key (id) not enforced -- 主键
) with (
'connector' = 'hudi',
'path' = 'hdfs://chb3:8020/hudi_db/person2',
'table.type' = 'MERGE_ON_READ',
'read.streaming.enabled' = 'true',
'read.streaming.check-interval' = '4'
);
结果只有最新数据
报错 Caused by: java.lang.ClassNotFoundException: org.apache.hadoop.mapred.FileInputFormat
拷贝 hadoop-mapreduce-client-core.jar 到 flink lib.
从 0.10 开始支持
对于 INSERT
模式:
MOR
默认会 apply 小文件策略: 会追加写 avro log 文件COW
每次直接写新的 parquet 文件,没有小文件策略Hudi 支持丰富的 Clustering
策略,优化 INSERT
模式下的小文件问题。
参数名 | 是否必选 | 默认值 | 备注 |
---|---|---|---|
write.insert.cluster |
false | false | 是否在写入时合并小文件,COW 表默认 insert 写不合并小文件,开启该参数后,每次写入会优先合并之前的小文件(不会去重),吞吐会受影响 (用的比较少,建议使用 Async Clustering) |
从 0.12 开始支持
WITH 参数
名称 | Required | 默认值 | 说明 |
---|---|---|---|
clustering.schedule.enabled | false | false | 是否在写入时定时异步调度 clustering plan,默认关闭 |
clustering.delta_commits | false | 4 | 调度 clsutering plan 的间隔 commits,clustering.schedule.enabled 为 true 时生效 |
clustering.async.enabled | false | false | 是否异步执行 clustering plan,默认关闭 |
clustering.tasks | false | 4 | Clustering task 执行并发 |
clustering.plan.strategy.target.file.max.bytes | false | 1024 * 1024 * 1024 | Clustering 单文件目标大小,默认 1GB |
clustering.plan.strategy.small.file.limit | false | 600 | 小于该大小的文件才会参与 clustering,默认600MB |
false | N/A | 支持指定特殊的排序字段 | |
false | NONE | 支持NONE:不做限制RECENT_DAYS:按时间(天)回溯SELECTED_PARTITIONS:指定固定的 partition | |
false | 2 | RECENT_DAYS 生效,默认 2 天 |
支持定制化的 clustering 策略。
名称 | Required | 默认值 | 说明 |
---|---|---|---|
clustering.plan.partition.filter.mode | FALSE | NONE | 支持· NONE:不做限制· RECENT_DAYS:按时间(天)回溯· SELECTED_PARTITIONS:指定固定的 partition |
clustering.plan.strategy.daybased.lookback.partitions | FALSE | 2 | RECENT_DAYS 生效,默认 2 天 |
clustering.plan.strategy.cluster.begin.partition | FALSE | N/A | SELECTED_PARTITIONS 生效,指定开始 partition(inclusive) |
clustering.plan.strategy.cluster.end.partition | FALSE | N/A | SELECTED_PARTITIONS 生效,指定结束 partition(incluseve) |
clustering.plan.strategy.partition.regex.pattern | FALSE | N/A | 正则表达式过滤 partitions |
clustering.plan.strategy.partition.selected | FALSE | N/A | 显示指定目标 partitions,支持逗号 , 分割多个 partition |
默认的 flink 流式写入使用 state 存储索引信息:primary key 到 fileId 的映射关系。当数据量比较大的时候,state的存储开销可能成为瓶颈,bucket 索引通过固定的 hash 策略,将相同 key 的数据分配到同一个 fileGroup 中,避免了索引的存储和查询开销。
名称 | Required | 默认值 | 说明 |
---|---|---|---|
index.type | false | FLINK_STATE | 设置 BUCKET 开启 Bucket 索引功能 |
hoodie.bucket.index.hash.field | false | 主键 | 可以设置成主键的子集 |
hoodie.bucket.index.num.buckets | false | 4 | 默认每个 partition 的 bucket 数,当前设置后则不可再变更。 |
有许多用户将完整的历史数据集与实时增量数据一起放到消息队列中的用例。然后使用 flink 将队列中的数据从最早的偏移量消费到hudi中。
消费历史数据集具有以下特点:
这将导致写入性能下降和吞吐量故障。对于这种情况,可以打开速度限制参数以确保流的平滑写入。
名称 | Required | 默认值 | 说明 |
---|---|---|---|
write.rate.limit | false | 0 | 默认禁止限流 |
参考:
https://hudi.apache.org/cn/docs/hoodie_deltastreamer/#flink-ingestion