/* * Copyright 2015 Netflix, Inc. * * Licensed 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 com.netflix.discovery.shared.resolver; import java.util.List; import java.util.concurrent.atomic.AtomicReference; import com.netflix.servo.annotations.DataSourceType; import com.netflix.servo.annotations.Monitor; import com.netflix.servo.monitor.Monitors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static com.netflix.discovery.EurekaClientNames.METRIC_RESOLVER_PREFIX; /** * A cluster resolver implementation that periodically creates a new {@link ClusterResolver} instance that * swaps the previous value. If the new resolver cannot be created or contains empty server list, the previous * one is used. Failed requests are retried using exponential back-off strategy. * <br/> * It provides more insight in form of additional logging and metrics, as it is supposed to be used as a top * level resolver. * * @author Tomasz Bak */ public class ReloadingClusterResolver<T extends EurekaEndpoint> implements ClusterResolver<T> { private static final Logger logger = LoggerFactory.getLogger(ReloadingClusterResolver.class); private static final long MAX_RELOAD_INTERVAL_MULTIPLIER = 5; private final ClusterResolverFactory<T> factory; private final long reloadIntervalMs; private final long maxReloadIntervalMs; private final AtomicReference<ClusterResolver<T>> delegateRef; private volatile long lastUpdateTime; private volatile long currentReloadIntervalMs; // Metric timestamp, tracking last time when data were effectively changed. private volatile long lastReloadTimestamp = -1; public ReloadingClusterResolver(final ClusterResolverFactory<T> factory, final long reloadIntervalMs) { this.factory = factory; this.reloadIntervalMs = reloadIntervalMs; this.maxReloadIntervalMs = MAX_RELOAD_INTERVAL_MULTIPLIER * reloadIntervalMs; this.delegateRef = new AtomicReference<>(factory.createClusterResolver()); this.lastUpdateTime = System.currentTimeMillis(); this.currentReloadIntervalMs = reloadIntervalMs; List<T> clusterEndpoints = delegateRef.get().getClusterEndpoints(); if (clusterEndpoints.isEmpty()) { logger.error("Empty Eureka server endpoint list during initialization process"); throw new ClusterResolverException("Resolved to an empty endpoint list"); } if (logger.isInfoEnabled()) { logger.info("Initiated with delegate resolver of type {}; next reload in {}[sec]. Loaded endpoints={}", delegateRef.get().getClass(), currentReloadIntervalMs / 1000, clusterEndpoints); } try { Monitors.registerObject(this); } catch (Throwable e) { logger.warn("Cannot register metrics", e); } } @Override public String getRegion() { ClusterResolver delegate = delegateRef.get(); return delegate == null ? null : delegate.getRegion(); } @Override public List<T> getClusterEndpoints() { long expiryTime = lastUpdateTime + currentReloadIntervalMs; if (expiryTime <= System.currentTimeMillis()) { try { ClusterResolver<T> newDelegate = reload(); this.lastUpdateTime = System.currentTimeMillis(); this.currentReloadIntervalMs = reloadIntervalMs; if (newDelegate != null) { delegateRef.set(newDelegate); lastReloadTimestamp = System.currentTimeMillis(); if (logger.isInfoEnabled()) { logger.info("Reload endpoints differ from the original list; next reload in {}[sec], Loaded endpoints={}", currentReloadIntervalMs / 1000, newDelegate.getClusterEndpoints()); } } } catch (Exception e) { this.currentReloadIntervalMs = Math.min(maxReloadIntervalMs, currentReloadIntervalMs * 2); logger.warn("Cluster resolve error; keeping the current Eureka endpoints; next reload in " + currentReloadIntervalMs / 1000 + "[sec]", e); } } return delegateRef.get().getClusterEndpoints(); } private ClusterResolver<T> reload() { ClusterResolver<T> newDelegate = factory.createClusterResolver(); List<T> newEndpoints = newDelegate.getClusterEndpoints(); if (newEndpoints.isEmpty()) { logger.info("Tried to reload but empty endpoint list returned; keeping the current endpoints"); return null; } // If no change in the server pool, shutdown the new delegate if (ResolverUtils.identical(delegateRef.get().getClusterEndpoints(), newEndpoints)) { logger.debug("Loaded cluster server list identical to the current one; no update required"); return null; } return newDelegate; } @Monitor(name = METRIC_RESOLVER_PREFIX + "lastReloadTimestamp", description = "How much time has passed from last successful cluster configuration resolve", type = DataSourceType.GAUGE) public long getLastReloadTimestamp() { return lastReloadTimestamp < 0 ? 0 : System.currentTimeMillis() - lastReloadTimestamp; } }