[HUDI-1729] Asynchronous Hive sync and commits cleaning for Flink writer (#2732)
This commit is contained in:
@@ -22,6 +22,7 @@ import org.apache.hudi.common.model.HoodieTableType;
|
||||
import org.apache.hudi.common.model.OverwriteWithLatestAvroPayload;
|
||||
import org.apache.hudi.config.HoodieWriteConfig;
|
||||
import org.apache.hudi.exception.HoodieException;
|
||||
import org.apache.hudi.hive.SlashEncodedDayPartitionValueExtractor;
|
||||
import org.apache.hudi.keygen.SimpleAvroKeyGenerator;
|
||||
import org.apache.hudi.keygen.constant.KeyGeneratorOptions;
|
||||
import org.apache.hudi.streamer.FlinkStreamerConfig;
|
||||
@@ -64,7 +65,7 @@ public class FlinkOptions {
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
public static final ConfigOption<String> PARTITION_DEFAULT_NAME = ConfigOptions
|
||||
.key("partition.default.name")
|
||||
.key("partition.default_name")
|
||||
.stringType()
|
||||
.defaultValue("__DEFAULT_PARTITION__")
|
||||
.withDescription("The default partition name in case the dynamic partition"
|
||||
@@ -233,6 +234,12 @@ public class FlinkOptions {
|
||||
.withDescription("Partition path field. Value to be used at the `partitionPath` component of `HoodieKey`.\n"
|
||||
+ "Actual value obtained by invoking .toString()");
|
||||
|
||||
public static final ConfigOption<Boolean> PARTITION_PATH_URL_ENCODE = ConfigOptions
|
||||
.key("write.partition.url_encode")
|
||||
.booleanType()
|
||||
.defaultValue(false)
|
||||
.withDescription("Whether to encode the partition path url, default false");
|
||||
|
||||
public static final ConfigOption<String> KEYGEN_CLASS = ConfigOptions
|
||||
.key(HoodieWriteConfig.KEYGENERATOR_CLASS_PROP)
|
||||
.stringType()
|
||||
@@ -287,6 +294,114 @@ public class FlinkOptions {
|
||||
.defaultValue(3600) // default 1 hour
|
||||
.withDescription("Max delta seconds time needed to trigger compaction, default 1 hour");
|
||||
|
||||
public static final ConfigOption<Boolean> CLEAN_ASYNC_ENABLED = ConfigOptions
|
||||
.key("clean.async.enabled")
|
||||
.booleanType()
|
||||
.defaultValue(true)
|
||||
.withDescription("Whether to cleanup the old commits immediately on new commits, enabled by default");
|
||||
|
||||
public static final ConfigOption<Integer> CLEAN_RETAIN_COMMITS = ConfigOptions
|
||||
.key("clean.retain_commits")
|
||||
.intType()
|
||||
.defaultValue(10)// default 10 commits
|
||||
.withDescription("Number of commits to retain. So data will be retained for num_of_commits * time_between_commits (scheduled).\n"
|
||||
+ "This also directly translates into how much you can incrementally pull on this table, default 10");
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Hive Sync Options
|
||||
// ------------------------------------------------------------------------
|
||||
public static final ConfigOption<Boolean> HIVE_SYNC_ENABLED = ConfigOptions
|
||||
.key("hive_sync.enable")
|
||||
.booleanType()
|
||||
.defaultValue(false)
|
||||
.withDescription("Asynchronously sync Hive meta to HMS, default false");
|
||||
|
||||
public static final ConfigOption<String> HIVE_SYNC_DB = ConfigOptions
|
||||
.key("hive_sync.db")
|
||||
.stringType()
|
||||
.defaultValue("default")
|
||||
.withDescription("Database name for hive sync, default 'default'");
|
||||
|
||||
public static final ConfigOption<String> HIVE_SYNC_TABLE = ConfigOptions
|
||||
.key("hive_sync.table")
|
||||
.stringType()
|
||||
.defaultValue("unknown")
|
||||
.withDescription("Table name for hive sync, default 'unknown'");
|
||||
|
||||
public static final ConfigOption<String> HIVE_SYNC_FILE_FORMAT = ConfigOptions
|
||||
.key("hive_sync.file_format")
|
||||
.stringType()
|
||||
.defaultValue("PARQUET")
|
||||
.withDescription("File format for hive sync, default 'PARQUET'");
|
||||
|
||||
public static final ConfigOption<String> HIVE_SYNC_USERNAME = ConfigOptions
|
||||
.key("hive_sync.username")
|
||||
.stringType()
|
||||
.defaultValue("hive")
|
||||
.withDescription("Username for hive sync, default 'hive'");
|
||||
|
||||
public static final ConfigOption<String> HIVE_SYNC_PASSWORD = ConfigOptions
|
||||
.key("hive_sync.password")
|
||||
.stringType()
|
||||
.defaultValue("hive")
|
||||
.withDescription("Password for hive sync, default 'hive'");
|
||||
|
||||
public static final ConfigOption<String> HIVE_SYNC_JDBC_URL = ConfigOptions
|
||||
.key("hive_sync.jdbc_url")
|
||||
.stringType()
|
||||
.defaultValue("jdbc:hive2://localhost:10000")
|
||||
.withDescription("Jdbc URL for hive sync, default 'jdbc:hive2://localhost:10000'");
|
||||
|
||||
public static final ConfigOption<String> HIVE_SYNC_PARTITION_FIELDS = ConfigOptions
|
||||
.key("hive_sync.partition_fields")
|
||||
.stringType()
|
||||
.defaultValue("")
|
||||
.withDescription("Partition fields for hive sync, default ''");
|
||||
|
||||
public static final ConfigOption<String> HIVE_SYNC_PARTITION_EXTRACTOR_CLASS = ConfigOptions
|
||||
.key("hive_sync.partition_extractor_class")
|
||||
.stringType()
|
||||
.defaultValue(SlashEncodedDayPartitionValueExtractor.class.getCanonicalName())
|
||||
.withDescription("Tool to extract the partition value from HDFS path, "
|
||||
+ "default 'SlashEncodedDayPartitionValueExtractor'");
|
||||
|
||||
public static final ConfigOption<Boolean> HIVE_SYNC_ASSUME_DATE_PARTITION = ConfigOptions
|
||||
.key("hive_sync.assume_date_partitioning")
|
||||
.booleanType()
|
||||
.defaultValue(false)
|
||||
.withDescription("Assume partitioning is yyyy/mm/dd, default false");
|
||||
|
||||
public static final ConfigOption<Boolean> HIVE_SYNC_USE_JDBC = ConfigOptions
|
||||
.key("hive_sync.use_jdbc")
|
||||
.booleanType()
|
||||
.defaultValue(true)
|
||||
.withDescription("Use JDBC when hive synchronization is enabled, default true");
|
||||
|
||||
public static final ConfigOption<Boolean> HIVE_SYNC_AUTO_CREATE_DB = ConfigOptions
|
||||
.key("hive_sync.auto_create_db")
|
||||
.booleanType()
|
||||
.defaultValue(true)
|
||||
.withDescription("Auto create hive database if it does not exists, default true");
|
||||
|
||||
public static final ConfigOption<Boolean> HIVE_SYNC_IGNORE_EXCEPTIONS = ConfigOptions
|
||||
.key("hive_sync.ignore_exceptions")
|
||||
.booleanType()
|
||||
.defaultValue(false)
|
||||
.withDescription("Ignore exceptions during hive synchronization, default false");
|
||||
|
||||
public static final ConfigOption<Boolean> HIVE_SYNC_SKIP_RO_SUFFIX = ConfigOptions
|
||||
.key("hive_sync.skip_ro_suffix")
|
||||
.booleanType()
|
||||
.defaultValue(false)
|
||||
.withDescription("Skip the _ro suffix for Read optimized table when registering, default false");
|
||||
|
||||
public static final ConfigOption<Boolean> HIVE_SYNC_SUPPORT_TIMESTAMP = ConfigOptions
|
||||
.key("hive_sync.support_timestamp")
|
||||
.booleanType()
|
||||
.defaultValue(false)
|
||||
.withDescription("INT64 with original type TIMESTAMP_MICROS is converted to hive timestamp type.\n"
|
||||
+ "Disabled by default for backward compatibility.");
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Utilities
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hudi.sink;
|
||||
|
||||
import org.apache.hudi.client.HoodieFlinkWriteClient;
|
||||
import org.apache.hudi.configuration.FlinkOptions;
|
||||
import org.apache.hudi.sink.utils.NonThrownExecutor;
|
||||
import org.apache.hudi.util.StreamerUtil;
|
||||
|
||||
import org.apache.flink.api.common.functions.AbstractRichFunction;
|
||||
import org.apache.flink.api.common.state.CheckpointListener;
|
||||
import org.apache.flink.configuration.Configuration;
|
||||
import org.apache.flink.runtime.state.FunctionInitializationContext;
|
||||
import org.apache.flink.runtime.state.FunctionSnapshotContext;
|
||||
import org.apache.flink.streaming.api.checkpoint.CheckpointedFunction;
|
||||
import org.apache.flink.streaming.api.functions.sink.SinkFunction;
|
||||
|
||||
/**
|
||||
* Sink function that cleans the old commits.
|
||||
*
|
||||
* <p>It starts a cleaning task on new checkpoints, there is only one cleaning task
|
||||
* at a time, a new task can not be scheduled until the last task finished(fails or normally succeed).
|
||||
* The cleaning task never expects to throw but only log.
|
||||
*/
|
||||
public class CleanFunction<T> extends AbstractRichFunction
|
||||
implements SinkFunction<T>, CheckpointedFunction, CheckpointListener {
|
||||
private final Configuration conf;
|
||||
|
||||
private HoodieFlinkWriteClient writeClient;
|
||||
private NonThrownExecutor executor;
|
||||
|
||||
private volatile boolean isCleaning;
|
||||
|
||||
public CleanFunction(Configuration conf) {
|
||||
this.conf = conf;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void open(Configuration parameters) throws Exception {
|
||||
super.open(parameters);
|
||||
if (conf.getBoolean(FlinkOptions.CLEAN_ASYNC_ENABLED)) {
|
||||
this.writeClient = StreamerUtil.createWriteClient(conf, getRuntimeContext());
|
||||
this.executor = new NonThrownExecutor();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyCheckpointComplete(long l) throws Exception {
|
||||
if (conf.getBoolean(FlinkOptions.CLEAN_ASYNC_ENABLED) && isCleaning) {
|
||||
executor.execute(() -> {
|
||||
try {
|
||||
this.writeClient.waitForCleaningFinish();
|
||||
} finally {
|
||||
// ensure to switch the isCleaning flag
|
||||
this.isCleaning = false;
|
||||
}
|
||||
}, "wait for cleaning finish", "");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void snapshotState(FunctionSnapshotContext context) throws Exception {
|
||||
if (conf.getBoolean(FlinkOptions.CLEAN_ASYNC_ENABLED) && !isCleaning) {
|
||||
this.writeClient.startAsyncCleaning();
|
||||
this.isCleaning = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeState(FunctionInitializationContext context) throws Exception {
|
||||
// no operation
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,8 @@ import org.apache.hudi.common.util.Option;
|
||||
import org.apache.hudi.configuration.FlinkOptions;
|
||||
import org.apache.hudi.exception.HoodieException;
|
||||
import org.apache.hudi.sink.event.BatchWriteSuccessEvent;
|
||||
import org.apache.hudi.sink.utils.HiveSyncContext;
|
||||
import org.apache.hudi.sink.utils.NonThrownExecutor;
|
||||
import org.apache.hudi.util.StreamerUtil;
|
||||
|
||||
import org.apache.flink.annotation.VisibleForTesting;
|
||||
@@ -107,6 +109,16 @@ public class StreamWriteOperatorCoordinator
|
||||
*/
|
||||
private final boolean needsScheduleCompaction;
|
||||
|
||||
/**
|
||||
* A single-thread executor to handle all the asynchronous jobs of the coordinator.
|
||||
*/
|
||||
private NonThrownExecutor executor;
|
||||
|
||||
/**
|
||||
* Context that holds variables for asynchronous hive sync.
|
||||
*/
|
||||
private HiveSyncContext hiveSyncContext;
|
||||
|
||||
/**
|
||||
* Constructs a StreamingSinkOperatorCoordinator.
|
||||
*
|
||||
@@ -131,14 +143,21 @@ public class StreamWriteOperatorCoordinator
|
||||
initTableIfNotExists(this.conf);
|
||||
// start a new instant
|
||||
startInstant();
|
||||
// start the executor if required
|
||||
if (conf.getBoolean(FlinkOptions.HIVE_SYNC_ENABLED)) {
|
||||
initHiveSync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
public void close() throws Exception {
|
||||
// teardown the resource
|
||||
if (writeClient != null) {
|
||||
writeClient.close();
|
||||
}
|
||||
if (executor != null) {
|
||||
executor.close();
|
||||
}
|
||||
this.eventBuffer = null;
|
||||
}
|
||||
|
||||
@@ -164,10 +183,25 @@ public class StreamWriteOperatorCoordinator
|
||||
if (needsScheduleCompaction) {
|
||||
writeClient.scheduleCompaction(Option.empty());
|
||||
}
|
||||
// sync Hive if is enabled
|
||||
syncHiveIfEnabled();
|
||||
// start new instant.
|
||||
startInstant();
|
||||
}
|
||||
|
||||
private void syncHiveIfEnabled() {
|
||||
if (conf.getBoolean(FlinkOptions.HIVE_SYNC_ENABLED)) {
|
||||
this.executor.execute(this::syncHive, "sync hive metadata", this.instant);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync hoodie table metadata to Hive metastore.
|
||||
*/
|
||||
public void syncHive() {
|
||||
hiveSyncContext.hiveSyncTool().syncHoodieTable();
|
||||
}
|
||||
|
||||
private void startInstant() {
|
||||
this.instant = this.writeClient.startCommit();
|
||||
this.writeClient.transitionRequestedToInflight(conf.getString(FlinkOptions.TABLE_TYPE), this.instant);
|
||||
@@ -232,6 +266,11 @@ public class StreamWriteOperatorCoordinator
|
||||
true);
|
||||
}
|
||||
|
||||
private void initHiveSync() {
|
||||
this.executor = new NonThrownExecutor();
|
||||
this.hiveSyncContext = HiveSyncContext.create(conf);
|
||||
}
|
||||
|
||||
static byte[] readBytes(DataInputStream in, int size) throws IOException {
|
||||
byte[] bytes = new byte[size];
|
||||
in.readFully(bytes);
|
||||
@@ -299,6 +338,7 @@ public class StreamWriteOperatorCoordinator
|
||||
doCommit();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void checkAndCommitWithRetry() {
|
||||
int retryTimes = this.conf.getInteger(FlinkOptions.RETRY_TIMES);
|
||||
if (retryTimes < 0) {
|
||||
@@ -378,6 +418,7 @@ public class StreamWriteOperatorCoordinator
|
||||
|
||||
boolean success = writeClient.commit(this.instant, writeResults, Option.of(checkpointCommitMetadata));
|
||||
if (success) {
|
||||
writeClient.postCommit(this.instant);
|
||||
reset();
|
||||
LOG.info("Commit instant [{}] success!", this.instant);
|
||||
} else {
|
||||
@@ -415,6 +456,10 @@ public class StreamWriteOperatorCoordinator
|
||||
return writeClient;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Inner Class
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Provider for {@link StreamWriteOperatorCoordinator}.
|
||||
*/
|
||||
|
||||
@@ -30,10 +30,10 @@ import org.apache.hudi.common.table.timeline.HoodieInstant;
|
||||
import org.apache.hudi.common.table.timeline.HoodieTimeline;
|
||||
import org.apache.hudi.common.util.CompactionUtils;
|
||||
import org.apache.hudi.common.util.Option;
|
||||
import org.apache.hudi.sink.CleanFunction;
|
||||
import org.apache.hudi.util.StreamerUtil;
|
||||
|
||||
import org.apache.flink.configuration.Configuration;
|
||||
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -51,8 +51,11 @@ import java.util.stream.Collectors;
|
||||
* it loads and checks the compaction plan {@link HoodieCompactionPlan},
|
||||
* if all the compaction operations {@link org.apache.hudi.common.model.CompactionOperation}
|
||||
* of the plan are finished, tries to commit the compaction action.
|
||||
*
|
||||
* <p>It also inherits the {@link CleanFunction} cleaning ability. This is needed because
|
||||
* the SQL API does not allow multiple sinks in one table sink provider.
|
||||
*/
|
||||
public class CompactionCommitSink extends RichSinkFunction<CompactionCommitEvent> {
|
||||
public class CompactionCommitSink extends CleanFunction<CompactionCommitEvent> {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CompactionCommitSink.class);
|
||||
|
||||
/**
|
||||
@@ -76,6 +79,7 @@ public class CompactionCommitSink extends RichSinkFunction<CompactionCommitEvent
|
||||
private String compactionInstantTime;
|
||||
|
||||
public CompactionCommitSink(Configuration conf) {
|
||||
super(conf);
|
||||
this.conf = conf;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,6 +123,12 @@ public class BucketAssignFunction<K, I, O extends HoodieRecord<?>>
|
||||
*/
|
||||
private boolean allPartitionsLoaded = false;
|
||||
|
||||
/**
|
||||
* Flag saying whether to check that all the partitions are loaded.
|
||||
* So that there is chance that flag {@code allPartitionsLoaded} becomes true.
|
||||
*/
|
||||
private boolean checkPartition = true;
|
||||
|
||||
public BucketAssignFunction(Configuration conf) {
|
||||
this.conf = conf;
|
||||
this.isChangingRecords = WriteOperationType.isChangingRecords(
|
||||
@@ -174,6 +180,13 @@ public class BucketAssignFunction<K, I, O extends HoodieRecord<?>>
|
||||
final HoodieKey hoodieKey = record.getKey();
|
||||
final BucketInfo bucketInfo;
|
||||
final HoodieRecordLocation location;
|
||||
|
||||
// Checks whether all the partitions are loaded first.
|
||||
if (checkPartition && !allPartitionsLoaded) {
|
||||
checkPartitionsLoaded();
|
||||
checkPartition = false;
|
||||
}
|
||||
|
||||
if (!allPartitionsLoaded
|
||||
&& initialPartitionsToLoad.contains(hoodieKey.getPartitionPath()) // this is an existing partition
|
||||
&& !partitionLoadState.contains(hoodieKey.getPartitionPath())) {
|
||||
@@ -213,7 +226,9 @@ public class BucketAssignFunction<K, I, O extends HoodieRecord<?>>
|
||||
public void notifyCheckpointComplete(long l) {
|
||||
// Refresh the table state when there are new commits.
|
||||
this.bucketAssigner.refreshTable();
|
||||
checkPartitionsLoaded();
|
||||
if (!allPartitionsLoaded) {
|
||||
checkPartition = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hudi.sink.utils;
|
||||
|
||||
import org.apache.hudi.common.fs.FSUtils;
|
||||
import org.apache.hudi.configuration.FlinkOptions;
|
||||
import org.apache.hudi.hive.HiveSyncConfig;
|
||||
import org.apache.hudi.hive.HiveSyncTool;
|
||||
import org.apache.hudi.util.StreamerUtil;
|
||||
|
||||
import org.apache.flink.configuration.Configuration;
|
||||
import org.apache.hadoop.fs.FileSystem;
|
||||
import org.apache.hadoop.hive.conf.HiveConf;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Hive synchronization context.
|
||||
*
|
||||
* <p>Use this context to create the {@link HiveSyncTool} for synchronization.
|
||||
*/
|
||||
public class HiveSyncContext {
|
||||
private final HiveSyncConfig syncConfig;
|
||||
private final HiveConf hiveConf;
|
||||
private final FileSystem fs;
|
||||
|
||||
private HiveSyncContext(HiveSyncConfig syncConfig, HiveConf hiveConf, FileSystem fs) {
|
||||
this.syncConfig = syncConfig;
|
||||
this.hiveConf = hiveConf;
|
||||
this.fs = fs;
|
||||
}
|
||||
|
||||
public HiveSyncTool hiveSyncTool() {
|
||||
return new HiveSyncTool(this.syncConfig, this.hiveConf, this.fs);
|
||||
}
|
||||
|
||||
public static HiveSyncContext create(Configuration conf) {
|
||||
HiveSyncConfig syncConfig = buildSyncConfig(conf);
|
||||
org.apache.hadoop.conf.Configuration hadoopConf = StreamerUtil.getHadoopConf();
|
||||
String path = conf.getString(FlinkOptions.PATH);
|
||||
FileSystem fs = FSUtils.getFs(path, hadoopConf);
|
||||
HiveConf hiveConf = new HiveConf();
|
||||
hiveConf.addResource(fs.getConf());
|
||||
return new HiveSyncContext(syncConfig, hiveConf, fs);
|
||||
}
|
||||
|
||||
private static HiveSyncConfig buildSyncConfig(Configuration conf) {
|
||||
HiveSyncConfig hiveSyncConfig = new HiveSyncConfig();
|
||||
hiveSyncConfig.basePath = conf.getString(FlinkOptions.PATH);
|
||||
hiveSyncConfig.baseFileFormat = conf.getString(FlinkOptions.HIVE_SYNC_FILE_FORMAT);
|
||||
hiveSyncConfig.usePreApacheInputFormat = false;
|
||||
hiveSyncConfig.databaseName = conf.getString(FlinkOptions.HIVE_SYNC_DB);
|
||||
hiveSyncConfig.tableName = conf.getString(FlinkOptions.HIVE_SYNC_TABLE);
|
||||
hiveSyncConfig.hiveUser = conf.getString(FlinkOptions.HIVE_SYNC_USERNAME);
|
||||
hiveSyncConfig.hivePass = conf.getString(FlinkOptions.HIVE_SYNC_PASSWORD);
|
||||
hiveSyncConfig.jdbcUrl = conf.getString(FlinkOptions.HIVE_SYNC_JDBC_URL);
|
||||
hiveSyncConfig.partitionFields = Arrays.stream(conf.getString(FlinkOptions.HIVE_SYNC_PARTITION_FIELDS)
|
||||
.split(",")).map(String::trim).collect(Collectors.toList());
|
||||
hiveSyncConfig.partitionValueExtractorClass = conf.getString(FlinkOptions.HIVE_SYNC_PARTITION_EXTRACTOR_CLASS);
|
||||
hiveSyncConfig.useJdbc = conf.getBoolean(FlinkOptions.HIVE_SYNC_USE_JDBC);
|
||||
// needs to support metadata table for flink
|
||||
hiveSyncConfig.useFileListingFromMetadata = false;
|
||||
hiveSyncConfig.verifyMetadataFileListing = false;
|
||||
hiveSyncConfig.ignoreExceptions = conf.getBoolean(FlinkOptions.HIVE_SYNC_IGNORE_EXCEPTIONS);
|
||||
hiveSyncConfig.supportTimestamp = conf.getBoolean(FlinkOptions.HIVE_SYNC_SUPPORT_TIMESTAMP);
|
||||
hiveSyncConfig.autoCreateDatabase = conf.getBoolean(FlinkOptions.HIVE_SYNC_AUTO_CREATE_DB);
|
||||
hiveSyncConfig.decodePartition = conf.getBoolean(FlinkOptions.PARTITION_PATH_URL_ENCODE);
|
||||
hiveSyncConfig.skipROSuffix = conf.getBoolean(FlinkOptions.HIVE_SYNC_SKIP_RO_SUFFIX);
|
||||
hiveSyncConfig.assumeDatePartitioning = conf.getBoolean(FlinkOptions.HIVE_SYNC_ASSUME_DATE_PARTITION);
|
||||
return hiveSyncConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.apache.hudi.sink.utils;
|
||||
|
||||
import org.apache.flink.util.ExceptionUtils;
|
||||
import org.apache.flink.util.function.ThrowingRunnable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* An executor service that catches all the throwable with logging.
|
||||
*/
|
||||
public class NonThrownExecutor implements AutoCloseable {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NonThrownExecutor.class);
|
||||
|
||||
/**
|
||||
* A single-thread executor to handle all the asynchronous jobs.
|
||||
*/
|
||||
private final ExecutorService executor;
|
||||
|
||||
public NonThrownExecutor() {
|
||||
this.executor = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the action in a loop.
|
||||
*/
|
||||
public void execute(
|
||||
final ThrowingRunnable<Throwable> action,
|
||||
final String actionName,
|
||||
final String instant) {
|
||||
|
||||
executor.execute(
|
||||
() -> {
|
||||
try {
|
||||
action.run();
|
||||
LOG.info("Executor executes action [{}] for instant [{}] success!", actionName, instant);
|
||||
} catch (Throwable t) {
|
||||
// if we have a JVM critical error, promote it immediately, there is a good
|
||||
// chance the
|
||||
// logging or job failing will not succeed any more
|
||||
ExceptionUtils.rethrowIfFatalErrorOrOOM(t);
|
||||
final String errMsg = String.format("Executor executes action [%s] error", actionName);
|
||||
LOG.error(errMsg, t);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (executor != null) {
|
||||
executor.shutdownNow();
|
||||
// We do not expect this to actually block for long. At this point, there should
|
||||
// be very few task running in the executor, if any.
|
||||
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ package org.apache.hudi.streamer;
|
||||
|
||||
import org.apache.hudi.common.model.HoodieRecord;
|
||||
import org.apache.hudi.configuration.FlinkOptions;
|
||||
import org.apache.hudi.sink.CleanFunction;
|
||||
import org.apache.hudi.sink.StreamWriteOperatorFactory;
|
||||
import org.apache.hudi.sink.partitioner.BucketAssignFunction;
|
||||
import org.apache.hudi.sink.transform.RowDataToHoodieFunction;
|
||||
@@ -32,7 +33,6 @@ import org.apache.flink.configuration.Configuration;
|
||||
import org.apache.flink.formats.json.JsonRowDataDeserializationSchema;
|
||||
import org.apache.flink.formats.json.TimestampFormat;
|
||||
import org.apache.flink.runtime.state.filesystem.FsStateBackend;
|
||||
import org.apache.flink.streaming.api.datastream.DataStream;
|
||||
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
|
||||
import org.apache.flink.streaming.api.operators.KeyedProcessOperator;
|
||||
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
|
||||
@@ -76,7 +76,7 @@ public class HoodieFlinkStreamerV2 {
|
||||
StreamWriteOperatorFactory<HoodieRecord> operatorFactory =
|
||||
new StreamWriteOperatorFactory<>(conf);
|
||||
|
||||
DataStream<Object> dataStream = env.addSource(new FlinkKafkaConsumer<>(
|
||||
env.addSource(new FlinkKafkaConsumer<>(
|
||||
cfg.kafkaTopic,
|
||||
new JsonRowDataDeserializationSchema(
|
||||
rowType,
|
||||
@@ -99,9 +99,11 @@ public class HoodieFlinkStreamerV2 {
|
||||
.keyBy(record -> record.getCurrentLocation().getFileId())
|
||||
.transform("hoodie_stream_write", null, operatorFactory)
|
||||
.uid("uid_hoodie_stream_write")
|
||||
.setParallelism(numWriteTask);
|
||||
|
||||
env.addOperator(dataStream.getTransformation());
|
||||
.setParallelism(numWriteTask)
|
||||
.addSink(new CleanFunction<>(conf))
|
||||
.setParallelism(1)
|
||||
.name("clean_commits")
|
||||
.uid("uid_clean_commits");
|
||||
|
||||
env.execute(cfg.targetTableName);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ package org.apache.hudi.table;
|
||||
|
||||
import org.apache.hudi.common.model.HoodieRecord;
|
||||
import org.apache.hudi.configuration.FlinkOptions;
|
||||
import org.apache.hudi.sink.CleanFunction;
|
||||
import org.apache.hudi.sink.StreamWriteOperatorFactory;
|
||||
import org.apache.hudi.sink.compact.CompactFunction;
|
||||
import org.apache.hudi.sink.compact.CompactionCommitEvent;
|
||||
@@ -34,7 +35,6 @@ import org.apache.flink.annotation.VisibleForTesting;
|
||||
import org.apache.flink.api.common.typeinfo.TypeInformation;
|
||||
import org.apache.flink.configuration.Configuration;
|
||||
import org.apache.flink.streaming.api.datastream.DataStream;
|
||||
import org.apache.flink.streaming.api.functions.sink.SinkFunction;
|
||||
import org.apache.flink.streaming.api.operators.KeyedProcessOperator;
|
||||
import org.apache.flink.table.api.TableSchema;
|
||||
import org.apache.flink.table.connector.ChangelogMode;
|
||||
@@ -95,9 +95,9 @@ public class HoodieTableSink implements DynamicTableSink, SupportsPartitioning {
|
||||
.name("compact_commit")
|
||||
.setParallelism(1); // compaction commit should be singleton
|
||||
} else {
|
||||
return pipeline.addSink(new DummySinkFunction<>())
|
||||
return pipeline.addSink(new CleanFunction<>(conf))
|
||||
.setParallelism(1)
|
||||
.name("dummy").uid("uid_dummy");
|
||||
.name("clean_commits").uid("uid_clean_commits");
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -131,7 +131,4 @@ public class HoodieTableSink implements DynamicTableSink, SupportsPartitioning {
|
||||
public void applyStaticPartition(Map<String, String> map) {
|
||||
// no operation
|
||||
}
|
||||
|
||||
// Dummy sink function that does nothing.
|
||||
private static class DummySinkFunction<T> implements SinkFunction<T> {}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
|
||||
package org.apache.hudi.util;
|
||||
|
||||
import org.apache.hudi.client.FlinkTaskContextSupplier;
|
||||
import org.apache.hudi.client.HoodieFlinkWriteClient;
|
||||
import org.apache.hudi.client.common.HoodieFlinkEngineContext;
|
||||
import org.apache.hudi.common.config.DFSPropertiesConfiguration;
|
||||
import org.apache.hudi.common.config.SerializableConfiguration;
|
||||
import org.apache.hudi.common.config.TypedProperties;
|
||||
import org.apache.hudi.common.engine.EngineType;
|
||||
import org.apache.hudi.common.fs.FSUtils;
|
||||
@@ -42,6 +46,7 @@ import org.apache.hudi.table.action.compact.CompactionTriggerStrategy;
|
||||
|
||||
import org.apache.avro.Schema;
|
||||
import org.apache.avro.generic.GenericRecord;
|
||||
import org.apache.flink.api.common.functions.RuntimeContext;
|
||||
import org.apache.flink.configuration.ConfigOption;
|
||||
import org.apache.flink.configuration.Configuration;
|
||||
import org.apache.flink.util.Preconditions;
|
||||
@@ -204,6 +209,11 @@ public class StreamerUtil {
|
||||
CompactionTriggerStrategy.valueOf(conf.getString(FlinkOptions.COMPACTION_TRIGGER_STRATEGY).toUpperCase(Locale.ROOT)))
|
||||
.withMaxNumDeltaCommitsBeforeCompaction(conf.getInteger(FlinkOptions.COMPACTION_DELTA_COMMITS))
|
||||
.withMaxDeltaSecondsBeforeCompaction(conf.getInteger(FlinkOptions.COMPACTION_DELTA_SECONDS))
|
||||
.withAsyncClean(conf.getBoolean(FlinkOptions.CLEAN_ASYNC_ENABLED))
|
||||
.retainCommits(conf.getInteger(FlinkOptions.CLEAN_RETAIN_COMMITS))
|
||||
// override and hardcode to 20,
|
||||
// actually Flink cleaning is always with parallelism 1 now
|
||||
.withCleanerParallelism(20)
|
||||
.build())
|
||||
.forTable(conf.getString(FlinkOptions.TABLE_NAME))
|
||||
.withAutoCommit(false)
|
||||
@@ -302,4 +312,16 @@ public class StreamerUtil {
|
||||
.equals(FlinkOptions.TABLE_TYPE_MERGE_ON_READ)
|
||||
&& conf.getBoolean(FlinkOptions.COMPACTION_ASYNC_ENABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the Flink write client.
|
||||
*/
|
||||
public static HoodieFlinkWriteClient createWriteClient(Configuration conf, RuntimeContext runtimeContext) {
|
||||
HoodieFlinkEngineContext context =
|
||||
new HoodieFlinkEngineContext(
|
||||
new SerializableConfiguration(getHadoopConf()),
|
||||
new FlinkTaskContextSupplier(runtimeContext));
|
||||
|
||||
return new HoodieFlinkWriteClient<>(context, getHoodieClientConfig(conf));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user