1. 介绍
Apache Hudi的核心场景之一就是无缝、高效消费数据库至数据湖。尽管有很多讨论,甚至用户已经采用了这种模型,但有关如何实现此模型的内容却很少。
在此博客中,我们将使用Hudi 0.5.1版本的功能来捕获在AWS RDS上运行的MySQL实例的变更,并将变更导入S3上Hudi表的端到端解决方案。
我们可以将该问题分为如下两部分。
- 从MySQL中提取变更日志:这仍然是一个非常棘手的问题,通常会使得Hudi用户陷入困境。幸运的是,对于AWS用户而言,有一个数据库迁移服务 (Database Migration service)(简称DMS)可以执行此变更捕获,并将其作为parquet文件上传到S3。
- 将变更日志应用于数据湖表:一旦存在某种形式的变更日志,下一步就是将它们增量地应用于表。这个任务可以使用Hudi DeltaStreamer工具完全自动化。
2. 方案
端到端的架构如下
下面先说明如何使用存储在MySQL中的order(订单)表来完成此操作(这些命令广泛应用于其他数据库引擎,例如Postgres或Aurora,尽管SQL /语法可能会有细微不同)
CREATE DATABASE hudi_dms
;
USE hudi_dms
;
CREATE TABLE orders
(
order_id INTEGER
,
order_qty INTEGER
,
customer_name VARCHAR
(
100
),
updated_at TIMESTAMP DEFAULT NOW
()
ON UPDATE NOW
(),
created_at TIMESTAMP DEFAULT NOW
(),
CONSTRAINT orders_pk PRIMARY KEY
(
order_id
)
);
INSERT INTO orders
(
order_id
,
order_qty
,
customer_name
)
VALUES
(
1
,
10
,
'victor'
);
INSERT INTO orders
(
order_id
,
order_qty
,
customer_name
)
VALUES
(
2
,
20
,
'peter'
);
该表中orderid是主键,也是Hudi表上的主键。由于一批变更记录可以包含对同一主键的更改,因此我们还包括了updateat和created_at字段,这些字段在写入表时保持最新状态。
2.1 从MySQL中解析变更日志
在配置DMS之前,首先需要准备捕获变更的MySQL实例,即确保实例已启用备份并打开了binlog。
接着继续在DMS中创建Endpoint(端点)以捕获MySQL数据并将其作为parquet文件存储在S3中。
- 源hudi-source-db端点,指向数据库服务器并提供基本身份验证信息。
- 目标parquet-s3端点,指向s3上的存储桶和文件夹,以将变更日志记录存储为parquet文件。
然后继续创建迁移任务,如下所示。可以给它起个名字,将源连接到目标,并确保选择正确的迁移类型,如下所示,以确保将正在进行的变更连续复制到S3,还要确保指定使用哪个DMS,决定复制哪个MySQL模式/表的规则。此示例中,我们将hudi_dms模式下的order(订单)表加入了白名单。
启动DMS任务进行初始加载,会生成parquet文件,如下所示。
读取原始的初始加载的parquet文件,此时确保路径相同
scala
>
spark
.
read
.
parquet
(
"s3://hudi-dms-demo/orders/hudi_dms/orders/*"
).
sort
(
"updated_at"
).
show
+--------+---------+-------------+-------------------+-------------------+
|
order_id
|
order_qty
|
customer_name
|
updated_at
|
created_at
|
+--------+---------+-------------+-------------------+-------------------+
|
2
|
10
|
peter
|
2020
-
01
-
20
20
:
12
:
22
|
2020
-
01
-
20
20
:
12
:
22
|
|
1
|
10
|
victor
|
2020
-
01
-
20
20
:
12
:
31
|
2020
-
01
-
20
20
:
12
:
31
|
+--------+---------+-------------+-------------------+-------------------+
2.2 使用Hudi DeltaStreamer应用变更日志
现在,开始准备使用变更日志。Hudi DeltaStreamer可以用作工作流调度程序并作为Spark作业运行(它还使用--continuous标志支持连续模式,在该模式下它作为长时间运行的Spark作业),在S3(或其他DFS实现)上给定的路径新建文件,并可以向目标hudi数据集发布更新。该工具会自动检查,因此如果需要重复摄取,只需要定期执行DeltaStreamer即可。
在S3上已经有初始负载文件的情况下,可以运行以下命令(此处为DeltaStreamer命令)来消费全部负载文件,并在S3上创建Hudi数据集。
spark
-
submit
--
class
org
.
apache
.
hudi
.
utilities
.
deltastreamer
.
HoodieDeltaStreamer
\
--
packages org
.
apache
.
spark
:
spark
-
avro_2
.
11
:
2.4
.
4
\
--
master yarn
--
deploy
-
mode client \
hudi
-
utilities
-
bundle_2
.
11
-
0.5
.
1
-
SNAPSHOT
.
jar \
--
table
-
type COPY_ON_WRITE \
--
source
-
ordering
-
field updated_at \
--
source
-
class
org
.
apache
.
hudi
.
utilities
.
sources
.
ParquetDFSSource
\
--
target
-
base
-
path s3
:
//hudi-dms-demo/hudi_orders --target-table hudi_orders \
--
transformer
-
class
org
.
apache
.
hudi
.
utilities
.
transform
.
AWSDmsTransformer
\
--
payload
-
class
org
.
apache
.
hudi
.
payload
.
AWSDmsAvroPayload
\
--
hoodie
-
conf hoodie
.
datasource
.
write
.
recordkey
.
field
=
order_id
,
hoodie
.
datasource
.
write
.
partitionpath
.
field
=
customer_name
,
hoodie
.
deltastreamer
.
source
.
dfs
.
root
=
s3
:
//hudi-dms-demo/orders/hudi_dms/orders
上述命令说明如下
- --table-type指定为表类型为COPYONWRITE(写时复制)。Hudi还支持MERGEONREAD类型。
- 对于处理输入parquet文件包含多个对同一个记录的更新/删除或插入/更新的情况,我们使用updated_at作为排序字段。这样可以确保最新时间戳的变更记录在Hudi中。
- 需要指定一个目标基本路径和目标表名,这些在创建和写入Hudi表时是必需的。
- 使用特殊的有效负载类AWSDMSAvroPayload来处理不同的变更操作。生成的parquet文件具有一个Op字段,该字段指示给定的变更记录是插入(I),删除(D)还是更新(U),并且有效负载的实现使用此字段来决定如何处理给定的变更记录。
- 指定了特殊的转换器类AWSDmsTransformer。初始加载文件不包含Op字段,因此会在Hudi表schema中另外添加该字段。
- 最后为Hudi表指定记录键,使其与上游表相同。然后根据customer_name以及DMS输出的根目录来指定分区。
运行命令后,将创建Hudi表,并与上游表具有相同的记录(也包含所有_hoodie字段)。
scala
>
spark
.
read
.
format
(
"org.apache.hudi"
).
load
(
"s3://hudi-dms-demo/hudi_orders/*/*.parquet"
).
show
+-------------------+--------------------+------------------+----------------------+--------------------+--------+---------+-------------+-------------------+-------------------+---+
|
_hoodie_commit_time
|
_hoodie_commit_seqno
|
_hoodie_record_key
|
_hoodie_partition_path
|
_hoodie_file_name
|
order_id
|
order_qty
|
customer_name
|
updated_at
|
created_at
|
Op
|
+-------------------+--------------------+------------------+----------------------+--------------------+--------+---------+-------------+-------------------+-------------------+---+
|
20200120205028
|
20200120205028
_0_1
|
2
|
peter
|
af9a2525
-
a486
-
40e
...|
2
|
10
|
peter
|
2020
-
01
-
20
20
:
12
:
22
|
2020
-
01
-
20
20
:
12
:
22
|
|
|
20200120205028
|
20200120205028
_1_1
|
1
|
victor
|
8e431ece
-
d51c
-
4c7.
..|
1
|
10
|
victor
|
2020
-
01
-
20
20
:
12
:
31
|
2020
-
01
-
20
20
:
12
:
31
|
|
+-------------------+--------------------+------------------+----------------------+--------------------+--------+---------+-------------+-------------------+-------------------+---+
接着插入和更新
INSERT INTO orders
(
order_id
,
order_qty
,
customer_name
)
VALUES
(
3
,
30
,
'sandy'
);
UPDATE orders
set
order_qty
=
20
where
order_id
=
2
;
这会在DMS输出文件夹中添加新的parquet文件,并且再次运行deltastreamer命令时,它将继续并将其应用于Hudi表。
因此,现在查询Hudi表将产生3行,并且hoodiecommittime可以准确反映这些写入发生的时间。你会注意到orderid = 2的orderqty从10更新为20!
+-------------------+--------------------+------------------+----------------------+--------------------+---+--------+---------+-------------+-------------------+-------------------+
|
_hoodie_commit_time
|
_hoodie_commit_seqno
|
_hoodie_record_key
|
_hoodie_partition_path
|
_hoodie_file_name
|
Op
|
order_id
|
order_qty
|
customer_name
|
updated_at
|
created_at
|
+-------------------+--------------------+------------------+----------------------+--------------------+---+--------+---------+-------------+-------------------+-------------------+
|
20200120211526
|
20200120211526
_0_1
|
2
|
peter
|
af9a2525
-
a486
-
40e
...|
U
|
2
|
20
|
peter
|
2020
-
01
-
20
21
:
11
:
47
|
2020
-
01
-
20
20
:
12
:
22
|
|
20200120211526
|
20200120211526
_1_1
|
3
|
sandy
|
566eb34a
-
e2c5
-
44b
...|
I
|
3
|
30
|
sandy
|
2020
-
01
-
20
21
:
11
:
24
|
2020
-
01
-
20
21
:
11
:
24
|
|
20200120205028
|
20200120205028
_1_1
|
1
|
victor
|
8e431ece
-
d51c
-
4c7.
..|
|
1
|
10
|
victor
|
2020
-
01
-
20
20
:
12
:
31
|
2020
-
01
-
20
20
:
12
:
31
|
+-------------------+--------------------+------------------+----------------------+--------------------+---+--------+---------+-------------+-------------------+-------------------+
一个不错的调试辅助方法是读取所有DMS的输出并按update_at对其进行排序,这将为我们提供上游表上发生的一系列的变更。如下所示是上面Hudi表原始变更日志的压缩快照。
+----+--------+---------+-------------+-------------------+-------------------+
|
Op
|
order_id
|
order_qty
|
customer_name
|
updated_at
|
created_at
|
+----+--------+---------+-------------+-------------------+-------------------+
|
null
|
2
|
10
|
peter
|
2020
-
01
-
20
20
:
12
:
22
|
2020
-
01
-
20
20
:
12
:
22
|
|
null
|
1
|
10
|
victor
|
2020
-
01
-
20
20
:
12
:
31
|
2020
-
01
-
20
20
:
12
:
31
|
|
I
|
3
|
30
|
sandy
|
2020
-
01
-
20
21
:
11
:
24
|
2020
-
01
-
20
21
:
11
:
24
|
|
U
|
2
|
20
|
peter
|
2020
-
01
-
20
21
:
11
:
47
|
2020
-
01
-
20
20
:
12
:
22
|
+----+--------+---------+-------------+-------------------+-------------------+
开始时并未初始化Op字段值,然后进行插入和更新。
接着进行删除和插入
DELETE FROM orders WHERE order_id
=
2
;
INSERT INTO orders
(
order_id
,
order_qty
,
customer_name
)
VALUES
(
4
,
40
,
'barry'
);
INSERT INTO orders
(
order_id
,
order_qty
,
customer_name
)
VALUES
(
5
,
50
,
'nathan'
);
这会导致S3上有更多由DMS写入的parquet文件,DeltaStreamer命令将继续处理这些文件(每次仅读取新写入的文件)
再次运行DeltaStreamer命令, 你会注意到Hudi表中只有两条新记录,并且order_id = 2被删除了。
+-------------------+--------------------+------------------+----------------------+--------------------+---+--------+---------+-------------+-------------------+-------------------+
|
_hoodie_commit_time
|
_hoodie_commit_seqno
|
_hoodie_record_key
|
_hoodie_partition_path
|
_hoodie_file_name
|
Op
|
order_id
|
order_qty
|
customer_name
|
updated_at
|
created_at
|
+-------------------+--------------------+------------------+----------------------+--------------------+---+--------+---------+-------------+-------------------+-------------------+
|
20200120212522
|
20200120212522
_1_1
|
5
|
nathan
|
3da94b20
-
c70b
-
457.
..|
I
|
5
|
50
|
nathan
|
2020
-
01
-
20
21
:
23
:
00
|
2020
-
01
-
20
21
:
23
:
00
|
|
20200120212522
|
20200120212522
_2_1
|
4
|
barry
|
8cc46715
-
8f0f
-
48a
...|
I
|
4
|
40
|
barry
|
2020
-
01
-
20
21
:
22
:
49
|
2020
-
01
-
20
21
:
22
:
49
|
|
20200120211526
|
20200120211526
_1_1
|
3
|
sandy
|
566eb34a
-
e2c5
-
44b
...|
I
|
3
|
30
|
sandy
|
2020
-
01
-
20
21
:
11
:
24
|
2020
-
01
-
20
21
:
11
:
24
|
|
20200120205028
|
20200120205028
_1_1
|
1
|
victor
|
8e431ece
-
d51c
-
4c7.
..|
|
1
|
10
|
victor
|
2020
-
01
-
20
20
:
12
:
31
|
2020
-
01
-
20
20
:
12
:
31
|
+-------------------+--------------------+------------------+----------------------+--------------------+---+--------+---------+-------------+-------------------+-------------------+
同理查询变更日志如下
+----+--------+---------+-------------+-------------------+-------------------+
|
Op
|
order_id
|
order_qty
|
customer_name
|
updated_at
|
created_at
|
+----+--------+---------+-------------+-------------------+-------------------+
|
null
|
2
|
10
|
peter
|
2020
-
01
-
20
20
:
12
:
22
|
2020
-
01
-
20
20
:
12
:
22
|
|
null
|
1
|
10
|
victor
|
2020
-
01
-
20
20
:
12
:
31
|
2020
-
01
-
20
20
:
12
:
31
|
|
I
|
3
|
30
|
sandy
|
2020
-
01
-
20
21
:
11
:
24
|
2020
-
01
-
20
21
:
11
:
24
|
|
U
|
2
|
20
|
peter
|
2020
-
01
-
20
21
:
11
:
47
|
2020
-
01
-
20
20
:
12
:
22
|
|
D
|
2
|
20
|
peter
|
2020
-
01
-
20
21
:
11
:
47
|
2020
-
01
-
20
20
:
12
:
22
|
|
I
|
4
|
40
|
barry
|
2020
-
01
-
20
21
:
22
:
49
|
2020
-
01
-
20
21
:
22
:
49
|
|
I
|
5
|
50
|
nathan
|
2020
-
01
-
20
21
:
23
:
00
|
2020
-
01
-
20
21
:
23
:
00
|
+----+--------+---------+-------------+-------------------+-------------------+
请注意,删除和更新具有相同的updated_at字段值。使用这种查看变更日志的方法会有一些警告。可以使用增量查询来查询Hudi表本身的变更日志。
3. 总结
本篇博文展示了在AWS上如何利用Hudi和DMS来将Database的变更写入Hudi表中,主要借助了Hudi 0.5.1版本中DeltaStreamer的能力,这样便可无缝将变更导入Hudi中,又是一次易用性的提升。