Flink 使用之操作 Hudi 表

Flink 使用介绍相关文档目录

Flink 使用介绍相关文档目录

前言

因业务要求对采集来的数据进行统一存储,因此引入了Flink CDC - Hudi方案。Flink CDC在本人之前的博客已有介绍(参见Flink 使用之 MySQL CDC
)。本篇重点介绍Flink SQL结合Hudi的使用方法。Hudi表使用Flink SQL操作,为了便于业务人员使用,我们为其提供Zeppelin,能够以可视化的方式编写并执行Flink作业,同时还可以图形化展示数据分析结果。

软件版本

我们使用的软件和版本如下所示:

  • Flink:1.13.2
  • Hudi:0.11.1
  • Zeppelin: 0.10.0

首先我们配置Flink Hudi环境。

下载编译Hudi

找一台已经安装了maven的服务器。执行:

git clone https://github.com/apache/hudi.git

源代码clone成功之后,切换分支到origin/release-0.11.1。接着执行编译命令:

mvn clean package -Dflink1.13 -Dscala2.11 -DskipTests -T 4

等待编译完成。

编译完成之后,Flink hudi bundle的编译输出在hudi/packaging/hudi-flink-bundle/target,Flink SQL支持Hudi所需jar包就在这个目录,将其复制走备用。

使用Flink SQL Client的方法执行Hudi SQL

在这一步我们使用Flink on yarn的方式启动Flink SQL Client,然后通过它操作Hudi表。

首先我们下载Flink 1.13.2并解压。

wget https://archive.apache.org/dist/flink/flink-1.13.2/flink-1.13.2-bin-scala_2.11.tgz
tar -zxvf flink-1.13.2-bin-scala_2.11.tgz

配置Flink启用checkpoint。编辑$FLINK_HOME/conf/flink-conf.yaml文件,添加或修改如下配置。

# 启用checkpoint,间隔时间为5000毫秒
execution.checkpointing.interval: 5000
# 使用rocksdb状态后端
state.backend: rocksdb
# 配置状态后端保存checkpoint的路径
state.checkpoints.dir: hdfs://test24/flink-checkpoints
# 启用增量checkpoint,配置rocksdb的时候建议开启
state.backend.incremental: true

注意:务必启用Flink checkpoint。否则Hudi流式作业会遇到数据无法插入等问题。

然后配置HADOOP的环境变量:

export HADOOP_CLASSPATH=`hadoop classpath`

Flink SQL client支持运行于standalone集群和Yarn集群上。

运行于standalone集群

编辑conf/flink-conf.yaml,修改如下内容:

taskmanager.numberOfTaskSlots: 4

然后修改conf/worker,如下所示:

localhost
localhost
localhost
localhost

这样子配置,启动standlone集群时,会在本地启动4个TaskManager。

下面的操作需要使用具有HDFS读写权限的用户执行。

启动standalone集群:

./start-cluster.sh

接下来启动Flink SQL Client:

./sql-client.sh embedded -j /path/to/hudi-flink-bundle_xxxxxx.jar

注意,-j后面是Hudi Flink bundle jar包。如果将Hudi包复制到了Flink安装路径的lib目录,启动sql-client的时候无需添加-j 参数。

运行于Yarn集群

下面的操作需要使用具有HDFS读写权限和Yarn队列提交权限的用户执行。

启动Flink的yarn-session:

./yarn-session.sh -d -s 4 -jm 1024 -tm 1024

记得记下启动的yarn-session对应的application id。可以通过Yarn ResourceManager UI来查看application id。

接下来启动Flink SQL Client:

./sql-client.sh embedded -s yarn-session -j /path/to/hudi-flink-bundle_xxxxxx.jar

注意,-j后面是Hudi Flink bundle jar包。如果将Hudi包复制到了Flink安装路径的lib目录,启动sql-client的时候无需添加-j 参数。

操作Hudi表

成功进入Flink SQL Client之后,我们执行插入测试数据的SQL:

CREATE TABLE t1(
  uuid VARCHAR(20),
  name VARCHAR(10),
  age INT,
  ts TIMESTAMP(3),
  `partition` VARCHAR(20)
)
PARTITIONED BY (`partition`)
WITH (
  'connector' = 'hudi',
  'path' = 'hdfs:///zy/hudi/',
  'table.type' = 'MERGE_ON_READ'
);

-- insert data using values
INSERT INTO t1 VALUES
  ('id1','Danny',23,TIMESTAMP '1970-01-01 00:00:01','par1'),
  ('id2','Stephen',33,TIMESTAMP '1970-01-01 00:00:02','par1'),
  ('id3','Julian',53,TIMESTAMP '1970-01-01 00:00:03','par2'),
  ('id4','Fabian',31,TIMESTAMP '1970-01-01 00:00:04','par2'),
  ('id5','Sophia',18,TIMESTAMP '1970-01-01 00:00:05','par3'),
  ('id6','Emma',20,TIMESTAMP '1970-01-01 00:00:06','par3'),
  ('id7','Bob',44,TIMESTAMP '1970-01-01 00:00:07','par4'),
  ('id8','Han',56,TIMESTAMP '1970-01-01 00:00:08','par4');

稍等一段时间后执行如下SQL,检查插入的数据:

select * from t1;

如果能查询出刚才插入的数据,说明Flink Hudi运行环境配置无误。

访问元数据字段

除了用户指定的字段外,Hudi表为了管理需要和实现自身的特性,引入了一些元数据字段。这些字段默认存在于所有Hudi表中。

元数据字段名称和作用如下所示:

  • _hoodie_commit_time:对应数据的提交时间(存入Hudi表的时间,生成commit instant的时间)。
  • _hoodie_commit_seqno:每条数据都对应唯一的提交序列编号。
  • _hoodie_record_key:Hudi记录的主键,对应SQL中的primary key。
  • _hoodie_file_name:存储该条数据的文件名。

在Flink SQL create语句中如果不指定元数据字段,则无法查询这些字段的内容。Spark没有这个限制。如果需要在Flink中访问这些元数据字段,需要在create table的时候指定。例如:

CREATE TABLE t1(
  uuid VARCHAR(20),
  name VARCHAR(10),
  age INT,
  ts TIMESTAMP(3),
  _hoodie_commit_time varchar(20),
  _hoodie_commit_seqno varchar(20),
  _hoodie_record_key varchar(20),
  _hoodie_partition_path varchar(20),
  _hoodie_file_name varchar(100),
  `partition` VARCHAR(20)
)
PARTITIONED BY (`partition`)
WITH (
  'connector' = 'hudi',
  'path' = 'hdfs:///zy/hudi/',
  'table.type' = 'MERGE_ON_READ'
);

可以通过如下语句查询元数据字段:

select _hoodie_commit_time, _hoodie_commit_seqno, _hoodie_record_key, _hoodie_file_name from t1;

Streaming增量读

增量读的含义是查询一个给定的时刻(commit timestamp)之后更新或者新增的数据。Streaming读查询生成一个数据流,随着数据源的插入或更新实时输出结果。

首先变更一条数据:

insert into t1 values
  ('id1','Danny',27,TIMESTAMP '1970-01-01 00:00:01','par1');

然后创建表t2,使用相同的数据目录:

CREATE TABLE t2(
  uuid VARCHAR(20),
  name VARCHAR(10),
  age INT,
  ts TIMESTAMP(3),
  `partition` VARCHAR(20)
)
PARTITIONED BY (`partition`)
WITH (
  'connector' = 'hudi',
  'path' = 'hdfs:///zy/hudi/',
  'table.type' = 'MERGE_ON_READ',
  'read.streaming.enabled' = 'true',  -- this option enable the streaming read
  'read.streaming.start-commit' = '20220620164900', -- specifies the start commit instant time
  'read.streaming.check-interval' = '4' -- specifies the check interval for finding new source commits, default 60s.
);

-- Then query the table in stream mode
select * from t2;

我们发现新增加的数据被查询出来了。

read.streaming.start-commit的格式为yyyyMMDDhhmmss。

注意:此处可能会遇到错误:

java.lang.ClassNotFoundException: org.apache.hadoop.mapred.FileInputFormat
java.lang.ClassNotFoundException: org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat

解决办法为复制集群的hadoop-mapreduce-client-core.jarhive-exec-3.1.0.3.0.1.0-187.jar到Flink lib中。

常用配置项:

  • read.streaming.enabled: 是否启用流式读取。默认为false
  • read.start-commit: 从哪个commit time开始读取。格式为yyyyMMDDhhmmss。
  • read.streaming.skip_compaction: 是否跳过数据压缩的commit。有两个目的:防止从compaction instant上读取重复的数据,启用changelog模式的时候读取压缩前的数据。默认为false。
  • clean.retain_commits: 保留超过多少个commit会被清理。多用于changelog模式。默认为10。

参考链接:https://hudi.apache.org/docs/hoodie_deltastreamer#streaming-query

Flink Hudi作业savepoint和恢复

我们使用一个场景(Flink Kafka数据入Hudi表)来模拟作业savepoint恢复。

前提条件:

  1. Flink 配置了checkpoint。
  2. Flink Yarn session已启动。

下面开始启动Kafka入Hudi表作业。

  1. 启动Kafka console producer,用来输入实验数据。
./kafka-console-producer.sh --broker-list broker1:9092,broker2:9092,broker3:9092 --topic demo_topic
  1. 启动Flink SQL client,创建Kafka数据源表。
CREATE TABLE kafka_t1(
  uuid VARCHAR(20),
  name VARCHAR(10),
  age INT,
  ts TIMESTAMP(3),
  `partition` VARCHAR(20)
)
WITH (
'connector' = 'kafka',
'properties.bootstrap.servers' = 'broker1:9092,broker2:9092,broker3:9092',
'topic' = 'demo_topic',
'format' = 'csv',
'csv.ignore-parse-errors' = 'true'
);
  1. 创建Hudi表。
CREATE TABLE t1(
  uuid VARCHAR(20),
  name VARCHAR(10),
  age INT,
  ts TIMESTAMP(3),
  `partition` VARCHAR(20)
)
PARTITIONED BY (`partition`)
WITH (
  'connector' = 'hudi',
  'path' = 'hdfs:///path/to/hudi_kafka/',
  'table.type' = 'MERGE_ON_READ'
);
  1. 启动Kafka写入Hudi表任务。
insert into t1 select * from kafka_t1;

到此位置Flink从Kafka如Hudi作业能够正常运行。可以在Kafka console consumer插入CSV数据验证:

id1,Danny,23,1970-01-01 00:00:01,par1
id2,Stephen,33,1970-01-01 00:00:02,par1
id3,Julian,53,1970-01-01 00:00:03,par2
id4,Fabian,31,1970-01-01 00:00:04,par2
id5,Sophia,18,1970-01-01 00:00:05,par3
id6,Emma,20,1970-01-01 00:00:06,par3
id7,Bob,44,1970-01-01 00:00:07,par4
id8,Han,56,1970-01-01 00:00:08,par4

接下来将作业保存savepoint并退出。

flink stop --savepointPath hdfs://ip:9000/path/to/savepoint/ jobID -yid application_id

其中:

  • hdfs://ip:9000/path/to/savepoint/为Flink savepoint保存目录。
  • JobIDinsert into语句创建的入Hudi数据湖任务。JobID可通过Flink Dashboard的Running Jobs页面查到。
  • application_id为Flink yarn session的application ID。

执行Flink stop的时候我们可以看到如下日志;

Suspending job "74c7fc1f08ad533071a137b813429de8" with a savepoint.
...
Savepoint completed. Path: hdfs://test24/savepoint/savepoint-74c7fc-babb7853052b

从中我们可以得知savepoint全路径为hdfs://test24/savepoint/savepoint-74c7fc-babb7853052b,下面恢复作业的时候需要用到。

到现在Flink作业已经停机。我们可以开始作业恢复过程。

首先启动Flink SQL client。然后执行SET execution.savepoint.path语句,例如:

SET execution.savepoint.path=hdfs://test24/savepoint/savepoint-74c7fc-babb7853052b;

注意,这种方式既可以从savepoint又可以从checkpoint恢复数据。从checkpoint恢复数据需要路径需要精确到chk-xxx目录,例如:
/flink-checkpoints/015c2ed83fdbc66b3fe3193d09fed915/chk-1803
其中015c2ed83fdbc66b3fe3193d09fed915为JobID,chk-1803为停机时候最后一次成功的checkpoint id。

最后我们重新执行create tableinsert into语句,确保和之前作业的相同。作业即可从savepoint/checkpoint恢复。

Flink Hudi参数

详细的配置请参考链接:https://hudi.apache.org/docs/flink_configuration/

Flink级别常用的配置如下:

  • taskmanager.numberOfTaskSlots: Task Manager的slot个数。如果使用yarn session,通过yarn session启动命令控制。
  • parallelism.default: 默认的并行度。
  • execution.checkpointing.interval: checkpoint间隔时间,单位毫秒。
  • state.backend: 使用什么状态后端。Hudi建议使用rocksdb状态后端。
  • state.backend.rocksdb.localdir: TM本地存放rocksdb文件的位置。
  • state.checkpoints.dir: checkpoint数据存储目录,要求所有的Flink节点都能够访问到,建议使用HDFS等分布式文件系统。
  • state.backend.incremental: 是否启用增量的checkpoint。增量的checkpoint每次只保存和上一次的差异信息,减少数据存储量和checkpoint耗时。如果使用rocksdb为状态后端,建议开启增量checkpoint。

除了Flink常用的配置外,还有些表级别的参数可用于Hudi微调。表级别参数需要添加到SQL的with语句中。

  • write.precombine.field: 版本字段,基于此字段的大小来判断消息是否进行更新。默认为ts
  • write.task.max.size: 写任务的最大内存限制,默认为1024MB。如果超出会强制flush最大的bucket(批量数据)。
  • write.rate.limit: 限制写入速率。默认为0不加任何限制。
  • write.tasks: 写入任务并行度。默认是4。
  • write.merge.max_memory: COP类型表需要合并base file和增量数据,增量数据会被缓存然后溢写到磁盘。该配置项为此过程最大可使用的堆内存大小。默认为100MB,内存充裕时建议增大。
  • read.tasks: 读取操作并发度。默认是4。
  • compaction.tasks: MOR表online compaction的并行度。默认是4。Online compaction会占用写操作的资源。建议使用offline compaction。
bin/flink run -c org.apache.hudi.sink.compact.HoodieFlinkCompactor lib/hudi-flink1.13-bundle_2.11-0.11.1.jar --path hdfs://xxx:9000/table --schedule
  • compaction.schedule.enabled: 是否启用周期性online compaction。默认开启。
  • compaction.async.enabled: MOR表是否启用异步compaction。默认开启。
  • compaction.trigger.strategy: 触发压缩的策略。可配置num_commits(多少次提交后触发),time_elapsed(多久之后触发,单位秒),num_and_time(提交数和时间都要满足)或者num_or_time(提交数或时间满足一个条件)。默认为num_commits。
  • compaction.delta_commits: 上面配置中提到的提交数。默认为5。
  • compaction.delta_seconds: 上面配置中提到的compaction间隔时间。单位为秒。默认值是3600。
  • compaction.max_memory: 压缩使用的最大内存数。默认为100MB,内存充裕时建议增大。
  • hoodie.cleaner.commits.retained: COW表最多保留最近多少个commit。默认为24。避免数据的无限膨胀。

MOR 表的增量log和base file合并通过compaction相关配置项来配置。
COW 表的历史版本清除(避免数据膨胀)可参考https://blog.csdn.net/bluishglc/article/details/115531015

Bulk Insert

Bulk Insert(批量插入)适合导入大量数据到Hudi中。比正常写入数据的方式快很多。使用batch运行模式进行bulk insert效率更高。Bulk insert没有序列化和数据合并去重过程。

涉及到的配置项有:

  • write.operation: 默认是upsert,可以设置为bulk_insert用来启用bulk insert模式。
  • write.tasks: 写入任务并行度。默认为4。
  • write.bulk_insert.shuffle_input: 写入之前是否shuffle数据。可以减少小文件数量,但是增加了数据倾斜的风险。默认为true。
  • write.bulk_insert.sort_input: 写入之前是否排序。写入多个分区的时候可以减少小文件数量。默认为true。
  • write.sort.memory: 排序使用的内存大小。默认为128MB。

参考链接:https://hudi.apache.org/docs/hoodie_deltastreamer#bulk-insert

Index Bootstrap

Index Bootstrap在创建表的时候读取表的索引到Flink的state。这样执行upsert操作的时候就可以直接从state读取索引,从而判断新数据是新增还是修改。加载索引的过程可能会非常耗时。

涉及到的配置项有:

  • index.bootstrap.enabled: 启用index bootstrap功能时,会将Hudi表的索引一次性加载到Flink状态中。默认为false。
  • index.partition.regex: 决定加载哪些分区的索引到state中。默认为*,即加载所有分区。

使用建议:

  1. 可以配置flink-conf.yaml中的execution.checkpointing.tolerable-failed-checkpoints = n配置项,确定能容忍的checkpoint失败次数。这个取决于Flink checkpoint的周期。如果Index bootstrap耗时长,建议调大这个值。
  2. Flink第一次checkpoint成功,说明Hudi index bootstrap过程结束。结束之后用户可以退出或保存savepoint。下次启动的时候可以直接加载savepoint,无需再重新index bootstrap。

使用flink stop --savepointPath hdfs://ip:9000/path/to/savepoint/ JobID -yid application-id命令保存savepoint并退出。
恢复作业在sql client中执行SET execution.savepoint.path=hdfs://ip:9000/path/to/savepoint/xxx;,然后重新建表执行insert重新启动任务(需要和之前任务的SQL语句相同)。

  1. 再次启动作业的时候,设置index.bootstrap.enabledfalse

参考链接:https://hudi.apache.org/docs/hoodie_deltastreamer#index-bootstrap

Changelog 模式

Hudi可以记录数据的中间状态(I / -U / U / D) ,类似于Flink的changelog stream。Hudi MOR表以行的形式存储,支持保留变更状态信息。

启用changelog模式需要在表中开启changelog.enabled=true配置项。开启之后数据变更的中间结果都会被保留下来。

注意:

  1. 批量读方式任然会合并中间结果,无论是否启用changelog。
  2. 启用changelog模式Hudi也只是尽力去保留中间变更数据。异步压缩会将changelog数据合并为最终结果。所以说如果数据没有被及时消费掉,那么这条数据只能读取到它的最终状态。为了缓解这种情况,可以配合设置compaction.delta_commits 和/或 compaction.delta_seconds,让compaction间隔时间加大,从而增加中间变更数据的保留时间。

参考链接:https://hudi.apache.org/docs/hoodie_deltastreamer#changelog-mode

Append 模式

如果insert操作用于摄入数据,COW表默认不会合并小文件。MOR表会追加数据到delta log中,然后定期执行压缩。

小文件合并策略会导致性能下降。可以通过write.insert.cluster来控制COW表写入数据时是否合并小文件。默认为false,即不启用。该喧嚣仅仅对COW表生效。

参考链接:https://hudi.apache.org/docs/hoodie_deltastreamer#append-mode

通过Zeppelin使用Flink Hudi

Flink SQL Client可以让用户直接通过SQL方式创建流处理作业,不再需要编写Java/Scala代码,但是它仍然基于命令行,对业务人员不够友好。为了方便业务人员使用,我们引入了Zeppelin。Zeppelin提供了交互数据分析和可视化功能,易用性能够满足业务人员的需求。

注意:Zeppelin,Flink和Hudi三者之间存在版本兼容问题。本人目前验证了Zeppelin 0.10.0,Flink 1.13.2和Hudi 0.11.1。试用其他版本过程遇到了些奇怪的问题。所以说其他版本组件请谨慎操作。

下面我们开始部署和使用Zeppelin。

安装和配置Zeppelin

在之前安装Flink的服务器上,下载Zeppelin 0.10.0版本:

wget https://dlcdn.apache.org/zeppelin/zeppelin-0.10.0/zeppelin-0.10.0-bin-all.tgz

下载完毕后将其解压,由于Zeppelin必须使用Java8 151版本之后的JDK,如果系统自带的JDK不满足要求,需要专门为Zeppelin指定JDK。JDK满足要求的可以略过此步骤。

cd zeppelin-0.10.0-bin-all/conf/
cp zeppelin-env.sh.template zeppelin-env.sh

然后编辑zeppelin-env.sh,加入一行:

export JAVA_HOME=/path/to/jdk8u302-b08

指定Zeppelin专属的JDK。

Zeppelin默认的端口号是8080,如果需要修改的话,先创建一个zeppelin-site.xml文件:

cd zeppelin-0.10.0-bin-all/conf/
cp zeppelin-site.xml.template zeppelin-site.xml

修改如下内容:


  zeppelin.server.addr
  0.0.0.0
  Server binding address



  zeppelin.server.port
  9999
  Server port.

例子中我们没有绑定IP,端口号修改成了9999。

最后我们通过如下命令启动Zeppelin服务:

cd zeppelin-0.10.0-bin-all/bin
./zeppelin-daemon.sh start

紧接着打开浏览器输入http://目标IP:9999,如果能够打开Zeppelin页面,说明配置无误。如果无法打开,说明Zeppelin配置或者环境出现了问题。可以查看Zeppelin的运行日志,Zeppelin的运行日志位于zeppelin-0.10.0-bin-all/logs目录。

Zeppelin主界面

配置Zeppelin的interpreter

Zeppelin具有非常多的interpreter。interpreter为Zeppelin的插件,用于支持各种各样的编程语言和数据处理后端,例如Hive,Spark和Flink等。

在Zeppelin中使用Flink,就必须依赖Flink interpreter,自然也离不开配置。我们重点关注几个核心的配置项。点击Zeppelin右上角的菜单,选择interpreter,在新页面的搜索框处输入flink,可以很方便的找到flink interpreter和他的配置。

flink interpreter 配置页面

最为重要的几个配置项为:

  • FLINK_HOME:Flink的安装目录,必须要配置。
  • HADOOP_CONF_DIR:Hadoop配置文件目录,如果Flink作业运行使用yarn模式,或者是使用HDFS,必须配置此项。
  • flink.execution.mode:Flink的运行模式,可以选择local,remote或者yarn。local为本地运行,remote需要连接远端集群(需要配置flink.execution.remote.hostflink.execution.remote.port,即远端Flink集群JobManager所在的host和port),yarn为提交作业到yarn集群。本例子中我们使用yarn模式。
  • flink.execution.jars:执行Flink作业依赖的其他jar包,可以配置本地文件路径或者是HDFS上的路径,多个文件使用逗号分隔。在这个例子中我们需要使用hudi,因此需要配置hudi-flink-bundle_xxxxxx.jar的全路径。

配置完毕之后,我们点击flink interpreter栏右上方的saverestart,使配置项生效。

验证Zeppelin Flink interpreter是否配置正确

接下来是验证步骤,我们打开Notebook菜单,选择Flink Tutorial -> Flink Basics。找到下方的Batch WordCount,点击右侧的运行按钮。如果下方能看到WordCount运行结果,说明Flink interpreter配置无误。

Flink WordCount运行成功页面

创建Note并通过Flink SQL操作Hudi表

点击Notebook菜单,选择Create new note,在弹出的对话框中填写note的名称,选择默认的interpreter为Flink,点击create按钮。

在编写Flink SQl之前,我们需要先写提示符,用来告诉Zeppelin需要怎样解析我们的编程脚本,提示符共有以下5种:

  • %flink - 创建ExecutionEnvironment/StreamExecutionEnvironment/BatchTableEnvironment/StreamTableEnvironment 并且提供Scala环境
  • %flink.pyflink - 提供Python环境
  • %flink.ipyflink - 提供ipython环境
  • %flink.ssql - 提供流处理SQL环境
  • %flink.bsql - 提供批处理SQL环境

这里我们使用%flink.ssql提示符,填写入前面Hudi的测试SQL并执行,如果能够正常创建Hudi表,插入数据并查询出。说明配置无误。Zeppelin可以正常使用。

Flink 操作Hudi表

使用Hive metastore

前面例子中表的元数据是在内存中保存的,如果Flink yarn session退出,表的元数据会丢失。下次使用的时候需要再次创建表,非常不便于使用。在这一节我们打算使用Hive的metastore作为元数据容器。表元数据保存在Hive的metastore中是一种方便的多的方案。表元数据不会因为Flink session的停止而丢失。

首先我们需要检查配合使用的hive的版本。在Flink安装目录的lib中添加对应的依赖。Hive版本和对应依赖请参考官方文档:https://nightlies.apache.org/flink/flink-docs-release-1.14/docs/connectors/table/hive/overview/

本例子中我们使用Hive 3.1.0 配合Flink 1.13.2使用。需要准备如下文件:

  • flink-connector-hive_2.11-1.13.2.jar
  • hive-exec-3.1.0.jar
  • libfb303-0.9.3.jar
  • antlr-runtime-3.5.2.jar

第一个文件我们可以从中央仓库下载,后面3个文件在hive安装目录能够找到。

接下来我们将这4个文件放置在Flink的lib目录中:

cd /path/to/flink-1.13.2/lib
wget https://repo1.maven.org/maven2/org/apache/flink/flink-connector-hive_2.11/1.13.2/flink-connector-hive_2.11-1.13.2.jar

cd /path/to/hive/lib
cp hive-exec-3.1.0.jar /path/to/flink-1.13.2/lib/
cp libfb303-0.9.3.jar /path/to/flink-1.13.2/lib/
cp antlr-runtime-3.5.2.jar /path/to/flink-1.13.2/lib/

然后重启Zeppelin的Flink interpreter。

接下来我们创建Hive catalog。打开前一节创建的Flink note,执行如下SQL语句:

%flink.ssql
CREATE CATALOG myhive WITH (
    'type' = 'hive',
    'default-database' = 'default',
    'hive-conf-dir' = '/path/to/hive/conf'
);

这条SQL语句创建出一个Hive catalog,具体使用Hive的哪个database我们可以提前使用beeline查询好。其中hive-conf-dir最为重要,必须要指定Hive配置文件所在的目录(一般是Hive安装路径下的conf目录)。创建Hive Catalog成功的截图如下:

创建Hive Catalog

然后我们执行下面的SQL,测试下Flink能否获取到Hive中default数据库下的表。

%flink.ssql
show catalogs;
use catalog myhive;
show tables;

执行成功的输出如下图所示(table部分未截图):


Show Catalogs

如果到这一步能够列出Hive的catalog和管理的tables,说明前面步骤操作无误,可以进行下一步,使用将Hudi表交给Hive catalog管理。

我们再次执行第一节中的测试SQL。观察Zeppelin的输出。


创建Hudi表

虽然这里执行成功了,但是本人清理环境后反复测试这条SQL的时候遇到了错误:


错误信息

比较诡异。本人使用Flink SQL client执行均无问题,检查Zeppelin Flink Session的classpath,发现hive-exec包已经加载,应该不存在问题才对。怀疑是Zeppelin和Flink版本兼容存在问题。待得到初步解决方案后本人将更新此博客。

更新内容:作者已调通Zeppelin Flink Hive Hudi环境,参见博客:Flink Zeppelin Hudi Hive 整合环境配置和使用

使用 Hive sync

Hive sync模式Flink会使用Hive的metastore,同时还保持同步,通过Flink维护的Hudi表也能够通过Hive查询。

启用Hive sync需要重新编译Hudi,因为Hudi默认编译参数是不包含Hive相关依赖的。在编译之前我们必须要确定配合使用的Hive的版本。这里以Hive3.1.0为例。修改hudi/packaging/hudi-flink-bundle/pom.xml文件,找到如下部分:


  flink-bundle-shade-hive3
  
    3.1.0
    compile
  
  
    
      ${hive.groupid}
      hive-service-rpc
      ${hive.version}
      ${flink.bundle.hive.scope}
    
  

在这一段中,修改hive的版本为实际使用的版本。

然后进入hudi项目根目录,执行如下命令编译

mvn clean package -DskipTests -Drat.skip=true -Pflink-bundle-shade-hive3 -Pinclude-flink-sql-connector-hive

最后复制编译输出到Flink的lib目录:

cp /opt/zy/hudi/packaging/hudi-flink-bundle/target/hudi-flink-bundle_2.11-0.11.1.jar /flink-1.13.2/lib/

注意:如果Flink目录中已经有flink-connector-hive的jar包,请务必移除,否则会出现依赖冲突。

整理好的Flink lib目录如下所示:

flink-connector-kafka_2.11-1.13.2.jar
flink-csv-1.13.2.jar
flink-dist_2.11-1.13.2.jar
flink-json-1.13.2.jar
flink-shaded-zookeeper-3.4.14.jar
flink-table_2.11-1.13.2.jar
flink-table-blink_2.11-1.13.2.jar
hadoop-mapreduce-client-core-3.1.1.3.0.1.0-187.jar
hive-exec-3.1.0.3.0.1.0-187.jar
hudi-flink1.13-bundle_2.11-0.11.1.jar
kafka-clients-2.4.1.jar
log4j-1.2-api-2.12.1.jar
log4j-api-2.12.1.jar
log4j-core-2.12.1.jar
log4j-slf4j-impl-2.12.1.jar

接着我们需要处理Hive的依赖。这一步如果忘了做,后面使用Hive查询Hudi表的时候,会报如下错误:

Error: Error while compiling statement: FAILED: RuntimeException java.lang.ClassNotFoundException: org.apache.hudi.hadoop.HoodieParquetInputFormat (state=42000,code=40000)

我们进入Hudi的packaging/hudi-hadoop-mr-bundle/target目录,复制hudi-hadoop-mr-bundle-0.11.1.jar到Hive安装目录的auxlib下。记得重启Hive所有服务。

注意:如果使用HDP,请务必复制hudi-hadoop-mr-bundle-0.11.1.jar到hiveserver2所在机器的auxlib目录,否则仍然会报ClassNotFoundException。

到这一步Hive sync已经配置完毕,接下来我们验证Hive sync的功能。

首先进入Flink SQL client,启动之前记得执行下面脚本:

export HADOOP_CLASSPATH=`hadoop classpath`

然后执行如下SQL:

CREATE TABLE t1(
  uuid VARCHAR(20),
  name VARCHAR(10),
  age INT,
  ts TIMESTAMP(3),
  `partition` VARCHAR(20)
)
PARTITIONED BY (`partition`)
WITH (
  'connector' = 'hudi',
  'path' = 'hdfs:///zy/hudi',
  'table.type' = 'COPY_ON_WRITE',
  'hive_sync.enable' = 'true',
  'hive_sync.mode' = 'hms',
  'hive_sync.metastore.uris' = 'thrift://sizu02:9083',
  'hive_sync.table'='t1', 
  'hive_sync.db'='default'
);

增加的几个重要配置项的解释如下:

  • table.type: 测试中我们使用COPY_ON_WRITE。如果使用MERGE_ON_READ,在生成parquet文件之前,Hive查询不到数据
  • hive_sync.enable: 是否启用hive同步
  • hive_sync.mode: hive同步模式,包含hms和jdbc两种,这里使用hms模式
  • hive_sync.metastore.uris: 配置hive metastore的URI
  • hive_sync.table: 同步到hive中的表名称
  • hive_sync.db: 同步到hive的哪个数据库中

然后插入测试数据:

INSERT INTO t1 VALUES
  ('id1','Danny',23,TIMESTAMP '1970-01-01 00:00:01','par1'),
  ('id2','Stephen',33,TIMESTAMP '1970-01-01 00:00:02','par1'),
  ('id3','Julian',53,TIMESTAMP '1970-01-01 00:00:03','par2'),
  ('id4','Fabian',31,TIMESTAMP '1970-01-01 00:00:04','par2'),
  ('id5','Sophia',18,TIMESTAMP '1970-01-01 00:00:05','par3'),
  ('id6','Emma',20,TIMESTAMP '1970-01-01 00:00:06','par3'),
  ('id7','Bob',44,TIMESTAMP '1970-01-01 00:00:07','par4'),
  ('id8','Han',56,TIMESTAMP '1970-01-01 00:00:08','par4');

然后执行

select * from t1;

可以成功查询出数据。

最后我们测试下通过Hive的beeline查询数据:

set hive.input.format = org.apache.hudi.hadoop.hive.HoodieCombineHiveInputFormat;

select * from t1;

结果如下:

+-------------------------+--------------------------+------------------------+----------------------------+----------------------------------------------------+----------+----------+---------+--------+---------------+
| t1._hoodie_commit_time  | t1._hoodie_commit_seqno  | t1._hoodie_record_key  | t1._hoodie_partition_path  |                t1._hoodie_file_name                | t1.uuid  | t1.name  | t1.age  | t1.ts  | t1.partition  |
+-------------------------+--------------------------+------------------------+----------------------------+----------------------------------------------------+----------+----------+---------+--------+---------------+
| 20211019164113          | 20211019164113_3_1       | id1                    | par1                       | 4c10d680-7b18-40c9-952e-75435101cb55_3-4-0_20211019164113.parquet | id1      | Danny    | 23      | 1000   | par1          |
| 20211019164113          | 20211019164113_3_2       | id2                    | par1                       | 4c10d680-7b18-40c9-952e-75435101cb55_3-4-0_20211019164113.parquet | id2      | Stephen  | 33      | 2000   | par1          |
| 20211019164113          | 20211019164113_1_3       | id3                    | par2                       | 9e94999f-0ce5-4741-b579-3aa0d5ac5f1b_1-4-0_20211019164113.parquet | id3      | Julian   | 53      | 3000   | par2          |
| 20211019164113          | 20211019164113_1_4       | id4                    | par2                       | 9e94999f-0ce5-4741-b579-3aa0d5ac5f1b_1-4-0_20211019164113.parquet | id4      | Fabian   | 31      | 4000   | par2          |
| 20211019164113          | 20211019164113_0_1       | id5                    | par3                       | fa2c57a6-6573-477d-af06-6b8f3a34f8da_0-4-0_20211019164113.parquet | id5      | Sophia   | 18      | 5000   | par3          |
| 20211019164113          | 20211019164113_0_2       | id6                    | par3                       | fa2c57a6-6573-477d-af06-6b8f3a34f8da_0-4-0_20211019164113.parquet | id6      | Emma     | 20      | 6000   | par3          |
| 20211019164113          | 20211019164113_2_1       | id7                    | par4                       | 2162e217-8d2e-4f2c-bd8b-7d76e213a1f1_2-4-0_20211019164113.parquet | id7      | Bob      | 44      | 7000   | par4          |
| 20211019164113          | 20211019164113_2_2       | id8                    | par4                       | 2162e217-8d2e-4f2c-bd8b-7d76e213a1f1_2-4-0_20211019164113.parquet | id8      | Han      | 56      | 8000   | par4          |
+-------------------------+--------------------------+------------------------+----------------------------+----------------------------------------------------+----------+----------+---------+--------+---------------+

在Zeppelin中使用Flink Hudi Hive Sync

上面的环境如果直接在Zeppelin中运行会出现很多报错,首先需要处理依赖问题。复制hadoop mapreduce的相关jar包到flink的lib目录中。这里以HDP的为例:

cp hadoop-mapreduce-client-jobclient-3.1.1.3.0.1.0-187.jar hadoop-mapreduce-client-core-3.1.1.3.0.1.0-187.jar hadoop-mapreduce-client-common-3.1.1.3.0.1.0-187.jar /path/to/flink-1.13.2/lib/

然后配置Zeppelin的flink interpreter,不要勾选zeppelin.flink.module.enableHive配置项,否则会出现插入的数据无法在Hive查询到的问题。意思也就是说,不需在flink interpreter中配置任何hive相关的内容。接下来按照上一节的操作,可以在Zeppelin中完美使用。

Hudi表权限管理

权限的分配和鉴权我们使用Ranger组件。Ranger组件支持Hive数据库,表,甚至是字段级别的权限控制。 Spark和Flink SQL可以使用Hive的Metastore。那么是否意味着可以通过Ranger控制Hive权限的方法去控制Spark/Flink操作Hudi表的权限呢?答案是否定的。Ranger的Hive插件只能控制用户通过Hive操作表的权限,Spark/Flink完全可以绕过这些权限控制。测试表明,Ranger Hive权限可以控制用户使用beeline查询。用户看不到自己无权操作的数据库和表。但是通过Spark/Flink SQL操作,Ranger Hive表权限无法控制。用户可以操作Hive metastore中任意表。

如果我们使用Spark/Flink操作Hudi表,权限问题应该怎么处理呢?

答案是使用Ranger HDFS数据目录读写权限和Yarn队列提交任务权限结合配置。我们可以使用Ranger HDFS控制目标用户对于表数据存储目录的权限,使用Ranger Yarn插件控制目标用户使用Spark/Flink提交任务到Yarn指定队列的权限。从而实现对Hudi表权限的管理。

注意:Yarn中有一个配置项叫做ranger.add-yarn-authorization。如果启用,Yarn队列的acl和Ranger acl会同时生效。多数情况下我们仅希望通过Ranger来管理Yarn队列权限。在这种情况下,需要关闭该配置。

你可能感兴趣的:(Flink 使用之操作 Hudi 表)