diff --git a/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/event/retry/BackoffRetrySupport.java b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/event/retry/BackoffRetrySupport.java new file mode 100644 index 0000000000..0046654f86 --- /dev/null +++ b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/event/retry/BackoffRetrySupport.java @@ -0,0 +1,101 @@ +/* + * 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.ranger.resource.mapper.event.retry; + +import java.util.concurrent.ThreadLocalRandom; +import lombok.extern.slf4j.Slf4j; +import org.apache.ranger.resource.mapper.utils.ThrowingRunnable; + +/** + * Retry helper that never gives up. It runs the action and, if it fails, waits and tries + * again until the action works or the thread is interrupted. The wait grows after each + * failure (exponential backoff), but it is capped at a maximum, and a small random jitter + * is added so many clients do not retry at the same moment. + */ +@Slf4j +public class BackoffRetrySupport implements RetrySupport { + + private final long baseIntervalMs; + private final long maxIntervalMs; + private final RetrySleeper retrySleeper; + + /** + * The config values are already checked in the config class. Here we only keep a safe floor + * (base at least 1 ms, max not smaller than base), so this helper can never break on a wrong + * wait time, even if it is created with bad values from some other place. + * + * @param baseIntervalMs wait time for the first retry, in milliseconds + * @param maxIntervalMs the biggest possible wait time, in milliseconds + * @param retrySleeper how to wait between the tries (usually {@code Thread::sleep}) + */ + public BackoffRetrySupport(long baseIntervalMs, long maxIntervalMs, RetrySleeper retrySleeper) { + this.baseIntervalMs = Math.max(1, baseIntervalMs); + this.maxIntervalMs = Math.max(this.baseIntervalMs, maxIntervalMs); + this.retrySleeper = retrySleeper; + } + + /** + * Runs the action. If the action throws, it waits (see {@link #backoffWithJitterMs(int)}) + * and runs the action one more time. It repeats this forever until the action does not throw. + * + * @param action the work to run; it can throw any exception + * @throws RetryException only when the thread is interrupted during the wait. Before it throws, + * it sets the interrupt flag again, so the caller can also see the interrupt. + */ + @Override + public void withRetries(ThrowingRunnable> action) throws RetryException { + int attempt = 0; + while (true) { + try { + action.run(); + return; + } catch (Exception e) { + long delayMs = backoffWithJitterMs(attempt++); + log.warn("Attempt failed, retrying in {} ms", delayMs, e); + try { + retrySleeper.sleepForMs(delayMs); + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + throw new RetryException("Interrupted while backing off before retry", interrupted); + } + } + } + } + + /** + * Calculates how long to wait before the next try. + * + *
The base value is doubled one time for every attempt (base, base*2, base*4 ...), but the + * result is never bigger than the maximum. After that a random jitter is added: the returned + * value is between the half of the capped value and the full capped value.
+ * + * @param attempt how many times we already failed (the first call uses 0) + * @return the wait time in milliseconds + */ + private long backoffWithJitterMs(int attempt) { + long delay = baseIntervalMs; + for (int i = 0; i < attempt && delay < maxIntervalMs; i++) { + delay <<= 1; + } + long capped = Math.min(delay, maxIntervalMs); + long half = capped / 2; + return half + ThreadLocalRandom.current().nextLong(half + 1); + } +} diff --git a/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/HiveResourceMappingManager.java b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/HiveResourceMappingManager.java index 7e74d11752..334aa55d4a 100644 --- a/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/HiveResourceMappingManager.java +++ b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/HiveResourceMappingManager.java @@ -36,6 +36,7 @@ import org.apache.ranger.resource.mapper.event.ResourceDiffCollector; import org.apache.ranger.resource.mapper.event.ResourceDiffHandler; import org.apache.ranger.resource.mapper.event.ResourceDiffSource; +import org.apache.ranger.resource.mapper.event.retry.BackoffRetrySupport; import org.apache.ranger.resource.mapper.event.retry.PolicyBasedRetrySupport; import org.apache.ranger.resource.mapper.event.retry.ResourceMapperRetryPolicy; import org.apache.ranger.resource.mapper.event.retry.RetryPolicyFactory; @@ -104,8 +105,9 @@ private ResourceDiffSource buildEventFetcher(RetryPolicyFactory retryPolicyFacto HiveMetastoreSnapshotFetcher snapshotFetcher = buildSnapshotEventFetcher(hiveMetaStoreClient, retrySupport, hiveAuthenticator, config); + RetrySupport eventFetcherRetrySupport = buildHiveListenerBackoffRetrySupport(config); HiveMetastoreEventFetcher eventFetcher = buildHiveMetastoreEventFetcher( - hiveMetaStoreClient, retrySupport, hiveAuthenticator, config); + hiveMetaStoreClient, eventFetcherRetrySupport, hiveAuthenticator, config); return CompositeHiveMetastoreFetcher.builder() .metaStoreClient(hiveMetaStoreClient) @@ -163,6 +165,13 @@ private RetrySupport buildHiveListenerRetrySupport( return new PolicyBasedRetrySupport(retryPolicy, Thread::sleep); } + private RetrySupport buildHiveListenerBackoffRetrySupport(HiveResourceMappingManagerConfig config) { + return new BackoffRetrySupport( + config.getHmsReconnectBaseIntervalMs(), + config.getHmsReconnectMaxIntervalMs(), + Thread::sleep); + } + private RetrySupport buildEventApplierRetrySupport( RetryPolicyFactory retryPolicyFactory, ResourceMappingManagerConfig config) { diff --git a/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/config/ConfigurationKeys.java b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/config/ConfigurationKeys.java index 679e8c90ab..52461c1de1 100644 --- a/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/config/ConfigurationKeys.java +++ b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/config/ConfigurationKeys.java @@ -37,6 +37,12 @@ public class ConfigurationKeys { public static final String HMS_MAX_RETRIES = "ranger.rmm.hms.retry.max"; public static final int HMS_MAX_RETRIES_DEFAULT = 10; + public static final String HMS_RECONNECT_BASE_INTERVAL_MS = "ranger.rmm.hms.reconnect.base.interval.ms"; + public static final long HMS_RECONNECT_BASE_INTERVAL_MS_DEFAULT = 1000L; + + public static final String HMS_RECONNECT_MAX_INTERVAL_MS = "ranger.rmm.hms.reconnect.max.interval.ms"; + public static final long HMS_RECONNECT_MAX_INTERVAL_MS_DEFAULT = 300000L; + public static final String HMS_FULL_SYNC = "ranger.rmm.hms.sync.full"; public static final boolean HMS_FULL_SYNC_DEFAULT = false; } diff --git a/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/config/HiveResourceMappingManagerConfig.java b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/config/HiveResourceMappingManagerConfig.java index fddbd25d09..2168631714 100644 --- a/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/config/HiveResourceMappingManagerConfig.java +++ b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/config/HiveResourceMappingManagerConfig.java @@ -27,14 +27,20 @@ import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_FULL_SYNC_DEFAULT; import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_MAX_RETRIES; import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_MAX_RETRIES_DEFAULT; +import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_RECONNECT_BASE_INTERVAL_MS; +import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_RECONNECT_BASE_INTERVAL_MS_DEFAULT; +import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_RECONNECT_MAX_INTERVAL_MS; +import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_RECONNECT_MAX_INTERVAL_MS_DEFAULT; import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_RETRY_INTERVAL_MS; import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_RETRY_INTERVAL_MS_DEFAULT; import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_RETRY_STRATEGY; import static org.apache.ranger.resource.mapper.hive.config.ConfigurationKeys.HMS_RETRY_STRATEGY_DEFAULT; +import lombok.extern.slf4j.Slf4j; import org.apache.ranger.resource.mapper.config.ResourceMappingManagerConfig; import org.apache.ranger.resource.mapper.event.retry.RetryStrategy; +@Slf4j public class HiveResourceMappingManagerConfig extends ResourceMappingManagerConfig { private static final String HIVE_CONFIG_FILE = "hive-site.xml"; @@ -58,6 +64,18 @@ public long getHiveListenerRetryIntervalMs() { return getLong(HMS_RETRY_INTERVAL_MS, HMS_RETRY_INTERVAL_MS_DEFAULT); } + public long getHmsReconnectBaseIntervalMs() { + return validate( + getLong(HMS_RECONNECT_BASE_INTERVAL_MS, HMS_RECONNECT_BASE_INTERVAL_MS_DEFAULT), + HMS_RECONNECT_BASE_INTERVAL_MS_DEFAULT); + } + + public long getHmsReconnectMaxIntervalMs() { + return validate( + getLong(HMS_RECONNECT_MAX_INTERVAL_MS, HMS_RECONNECT_MAX_INTERVAL_MS_DEFAULT), + HMS_RECONNECT_MAX_INTERVAL_MS_DEFAULT); + } + public int getHiveListenerMaxRetries() { return getInt(HMS_MAX_RETRIES, HMS_MAX_RETRIES_DEFAULT); } @@ -65,4 +83,12 @@ public int getHiveListenerMaxRetries() { public boolean isFullMetastoreSync() { return getBoolean(HMS_FULL_SYNC, HMS_FULL_SYNC_DEFAULT); } + + private static long validate(long value, long defaultValue) { + if (value < 1) { + log.warn("Config value {} ms is not valid, using default {} ms instead", value, defaultValue); + return defaultValue; + } + return value; + } } diff --git a/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/event/fetch/HiveMetastoreEventFetcher.java b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/event/fetch/HiveMetastoreEventFetcher.java index e2a640bea4..272e8734e0 100644 --- a/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/event/fetch/HiveMetastoreEventFetcher.java +++ b/resource-mapping-manager/src/main/java/org/apache/ranger/resource/mapper/hive/event/fetch/HiveMetastoreEventFetcher.java @@ -80,6 +80,8 @@ public class HiveMetastoreEventFetcher extends BaseHiveMetastoreFetcher { @Getter(PROTECTED) private final Long endEventId; + private boolean metaStoreClientConnected = true; + @lombok.Builder( builderClassName = "Builder", toBuilder = true @@ -117,7 +119,7 @@ public BlockingQueue