All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.eureka.InstanceRegistry Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
/*
 * Copyright 2012 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.eureka;

import javax.annotation.Nullable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.google.common.cache.CacheBuilder;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.ActionType;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.appinfo.LeaseInfo;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.shared.LookupService;
import com.netflix.discovery.shared.Pair;
import com.netflix.eureka.lease.Lease;
import com.netflix.eureka.lease.LeaseManager;
import com.netflix.eureka.resources.ResponseCache;
import com.netflix.eureka.util.AwsAsgUtil;
import com.netflix.eureka.util.MeasuredRate;
import com.netflix.servo.annotations.DataSourceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.netflix.eureka.util.EurekaMonitors.*;

/**
 * Handles all registry requests from eureka clients.
 *
 * 

* Primary operations that are performed are the * Registers, Renewals, Cancels, Expirations, and Status Changes. The * registry also stores only the delta operations *

* * @author Karthik Ranganathan * */ public abstract class InstanceRegistry implements LeaseManager, LookupService { private static final Logger logger = LoggerFactory .getLogger(InstanceRegistry.class); private static final EurekaServerConfig EUREKA_CONFIG = EurekaServerConfigurationManager .getInstance().getConfiguration(); private static final String[] EMPTY_STR_ARRAY = new String[0]; private final ConcurrentHashMap>> registry = new ConcurrentHashMap>>(); private Timer evictionTimer = new Timer("Eureka-EvictionTimer", true); private volatile MeasuredRate renewsLastMin; protected ConcurrentMap overriddenInstanceStatusMap = CacheBuilder .newBuilder().initialCapacity(500) .expireAfterAccess(1, TimeUnit.HOURS) .build().asMap(); // CircularQueues here for debugging/statistics purposes only private final CircularQueue> recentRegisteredQueue; private final CircularQueue> recentCanceledQueue; private Timer deltaRetentionTimer = new Timer("Eureka-DeltaRetentionTimer", true); private ConcurrentLinkedQueue recentlyChangedQueue = new ConcurrentLinkedQueue(); private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); private final Lock read = readWriteLock.readLock(); private final Lock write = readWriteLock.writeLock(); protected Map regionNameVSRemoteRegistry = new HashMap(); protected String[] allKnownRemoteRegions = EMPTY_STR_ARRAY; protected final Object lock = new Object(); protected volatile int numberOfRenewsPerMinThreshold; protected volatile int expectedNumberOfRenewsPerMin; protected static final EurekaServerConfig EUREKA_SERVER_CONFIG = EurekaServerConfigurationManager .getInstance().getConfiguration(); private static final AtomicReference EVICTION_TASK = new AtomicReference(); /** * Create a new, empty instance registry. */ protected InstanceRegistry() { recentCanceledQueue = new CircularQueue>(1000); recentRegisteredQueue = new CircularQueue>(1000); deltaRetentionTimer.schedule(getDeltaRetentionTask(), EUREKA_CONFIG.getDeltaRetentionTimerIntervalInMs(), EUREKA_CONFIG.getDeltaRetentionTimerIntervalInMs()); } /** * Completely clear the registry. */ public void clearRegistry() { overriddenInstanceStatusMap.clear(); recentCanceledQueue.clear(); recentRegisteredQueue.clear(); recentlyChangedQueue.clear(); registry.clear(); } /** * Registers a new instance with a given duration. * * @see com.netflix.eureka.lease.LeaseManager#register(java.lang.Object, * int, boolean) */ public void register(InstanceInfo r, int leaseDuration, boolean isReplication) { try { read.lock(); Map> gMap = registry.get(r .getAppName()); REGISTER.increment(isReplication); if (gMap == null) { final ConcurrentHashMap> gNewMap = new ConcurrentHashMap>(); gMap = registry.putIfAbsent(r.getAppName(), gNewMap); if (gMap == null) { gMap = gNewMap; } } Lease existingLease = gMap.get(r.getId()); // Retain the last dirty timestamp without overwriting it, if there // is already a lease if (existingLease != null && (existingLease.getHolder() != null)) { Long existingLastDirtyTimestamp = existingLease.getHolder() .getLastDirtyTimestamp(); Long registrationLastDirtyTimestamp = r.getLastDirtyTimestamp(); if (existingLastDirtyTimestamp > registrationLastDirtyTimestamp) { logger.warn( "There is an existing lease and the existing lease's dirty timestamp {} is greater than " + "the one that is being registered {}", existingLastDirtyTimestamp, registrationLastDirtyTimestamp); r.setLastDirtyTimestamp(existingLastDirtyTimestamp); } } else { // The lease does not exist and hence it is a new registration synchronized (lock) { if (this.expectedNumberOfRenewsPerMin > 0) { // Since the client wants to cancel it, reduce the threshold // (1 // for 30 seconds, 2 for a minute) this.expectedNumberOfRenewsPerMin = this.expectedNumberOfRenewsPerMin + 2; this.numberOfRenewsPerMinThreshold = (int) (this.expectedNumberOfRenewsPerMin * EUREKA_SERVER_CONFIG.getRenewalPercentThreshold()); } } } Lease lease = new Lease(r, leaseDuration); if (existingLease != null) { lease.setServiceUpTimestamp(existingLease.getServiceUpTimestamp()); } gMap.put(r.getId(), lease); synchronized (recentRegisteredQueue) { recentRegisteredQueue.add(new Pair(System.currentTimeMillis(), r.getAppName() + "(" + r.getId() + ")")); } // This is where the initial state transfer of overridden status // happens if (!InstanceStatus.UNKNOWN.equals(r.getOverriddenStatus())) { logger.debug( "Found overridden status {} for instance {}. Checking to see if needs to be add to the " + "overrides", r.getOverriddenStatus(), r.getId()); if (!overriddenInstanceStatusMap.containsKey(r.getId())) { logger.info( "Not found overridden id {} and hence adding it", r.getId()); overriddenInstanceStatusMap.put(r.getId(), r.getOverriddenStatus()); } } InstanceStatus overriddenStatusFromMap = overriddenInstanceStatusMap.get(r.getId()); if (overriddenStatusFromMap != null) { logger.info( "Storing overridden status {} from map", overriddenStatusFromMap); r.setOverriddenStatus(overriddenStatusFromMap); } // Set the status based on the overridden status rules InstanceStatus overriddenInstanceStatus = getOverriddenInstanceStatus( r, existingLease, isReplication); r.setStatusWithoutDirty(overriddenInstanceStatus); // If the lease is registered with UP status, set lease service up timestamp if (InstanceStatus.UP.equals(r.getStatus())) { lease.serviceUp(); } r.setActionType(ActionType.ADDED); recentlyChangedQueue.add(new RecentlyChangedItem(lease)); r.setLastUpdatedTimestamp(); invalidateCache(r.getAppName(), r.getVIPAddress(), r.getSecureVipAddress()); logger.info("Registered instance id {} with status {}", r.getId(), r.getStatus().toString()); logger.debug("DS: Registry: registered " + r.getAppName() + " - " + r.getId()); } finally { read.unlock(); } } /** * Cancels the registration of an instance. * *

* This is normally invoked by a client when it shuts down informing the * server to remove the instance from traffic. *

* * @param appName * the application name of the application. * @param id * the unique identifier of the instance. * @param isReplication * true if this is a replication event from other nodes, false * otherwise. * @return true if the instance was removed from the * {@link InstanceRegistry} successfully, false otherwise. */ public boolean cancel(String appName, String id, boolean isReplication) { try { read.lock(); CANCEL.increment(isReplication); Map> gMap = registry.get(appName); Lease leaseToCancel = null; if (gMap != null) { leaseToCancel = gMap.remove(id); } synchronized (recentCanceledQueue) { recentCanceledQueue.add(new Pair(System.currentTimeMillis(), appName + "(" + id + ")")); } InstanceStatus instanceStatus = overriddenInstanceStatusMap .remove(id); if (instanceStatus != null) { logger.debug( "Removed instance id {} from the overridden map which has value {}", id, instanceStatus.name()); } if (leaseToCancel == null) { CANCEL_NOT_FOUND.increment(isReplication); logger.warn("DS: Registry: cancel failed because Lease is not registered for: " + appName + ":" + id); return false; } else { leaseToCancel.cancel(); InstanceInfo instanceInfo = leaseToCancel.getHolder(); String vip = null; String svip = null; if (instanceInfo != null) { instanceInfo.setActionType(ActionType.DELETED); recentlyChangedQueue.add(new RecentlyChangedItem( leaseToCancel)); instanceInfo.setLastUpdatedTimestamp(); vip = instanceInfo.getVIPAddress(); svip = instanceInfo.getSecureVipAddress(); } invalidateCache(appName, vip, svip); logger.debug("DS: Registry: canceled lease: " + appName + " - " + id); return true; } } finally { read.unlock(); } } /** * Marks the given instance of the given app name as renewed, and also marks whether it originated from * replication. * * @see com.netflix.eureka.lease.LeaseManager#renew(java.lang.String, * java.lang.String, boolean) */ public boolean renew(String appName, String id, boolean isReplication) { RENEW.increment(isReplication); Map> gMap = registry.get(appName); Lease leaseToRenew = null; if (gMap != null) { leaseToRenew = gMap.get(id); } if (leaseToRenew == null) { RENEW_NOT_FOUND.increment(isReplication); logger.warn("DS: Registry: lease doesn't exist, registering resource: " + appName + " - " + id); return false; } else { InstanceInfo instanceInfo = leaseToRenew.getHolder(); if (instanceInfo != null) { // touchASGCache(instanceInfo.getASGName()); InstanceStatus overriddenInstanceStatus = this .getOverriddenInstanceStatus(instanceInfo, leaseToRenew, isReplication); if (overriddenInstanceStatus == InstanceStatus.UNKNOWN) { logger.info("Instance status UNKNOWN possibly due to deleted override for instance {}" + "; re-register required", instanceInfo.getId()); RENEW_NOT_FOUND.increment(isReplication); return false; } if (!instanceInfo.getStatus().equals(overriddenInstanceStatus)) { Object[] args = {instanceInfo.getStatus().name(), instanceInfo.getOverriddenStatus().name(), instanceInfo.getId()}; logger.info( "The instance status {} is different from overridden instance status {} for instance {}. " + "Hence setting the status to overridden status", args); instanceInfo.setStatus(overriddenInstanceStatus); } } renewsLastMin.increment(); leaseToRenew.renew(); return true; } } /** * Stores overridden status if it is not already there. This happens during * a reconciliation process during renewal requests. * * @param id * the unique identifier of the instance. * @param overriddenStatus * Overridden status if any. */ public void storeOverriddenStatusIfRequired(String id, InstanceStatus overriddenStatus) { InstanceStatus instanceStatus = overriddenInstanceStatusMap.get(id); if ((instanceStatus == null) || (!overriddenStatus.equals(instanceStatus))) { // We might not have the overridden status if the server got // restarted -this will help us maintain the overridden state // from the replica logger.info( "Adding overridden status for instance id {} and the value is {}", id, overriddenStatus.name()); overriddenInstanceStatusMap.put(id, overriddenStatus); List instanceInfo = this.getInstancesById(id, false); if ((instanceInfo != null) && (!instanceInfo.isEmpty())) { instanceInfo.iterator().next().setOverriddenStatus(overriddenStatus); logger.info( "Setting the overridden status for instance id {} and the value is {} ", id, overriddenStatus.name()); } } } /** * Updates the status of an instance. Normally happens to put an instance * between {@link InstanceStatus#OUT_OF_SERVICE} and * {@link InstanceStatus#UP} to put the instance in and out of traffic. * * @param appName * the application name of the instance. * @param id * the unique identifier of the instance. * @param newStatus * the new {@link InstanceStatus}. * @param lastDirtyTimestamp * last timestamp when this instance information was updated. * @param isReplication * true if this is a replication event from other nodes, false * otherwise. * @return true if the status was successfully updated, false otherwise. */ public boolean statusUpdate(String appName, String id, InstanceStatus newStatus, String lastDirtyTimestamp, boolean isReplication) { try { read.lock(); STATUS_UPDATE.increment(isReplication); Map> gMap = registry.get(appName); Lease lease = null; if (gMap != null) { lease = gMap.get(id); } if (lease == null) { return false; } else { lease.renew(); InstanceInfo info = lease.getHolder(); if ((info != null) && !(info.getStatus().equals(newStatus))) { // Mark service as UP if needed if (InstanceStatus.UP.equals(newStatus)) { lease.serviceUp(); } // This is NAC overriden status overriddenInstanceStatusMap.put(id, newStatus); // Set it for transfer of overridden status to replica on // replica start up info.setOverriddenStatus(newStatus); long replicaDirtyTimestamp = 0; if (lastDirtyTimestamp != null) { replicaDirtyTimestamp = Long .valueOf(lastDirtyTimestamp); } // If the replication's dirty timestamp is more than the // existing one, just update // it to the replica's. if (replicaDirtyTimestamp > info.getLastDirtyTimestamp()) { info.setLastDirtyTimestamp(replicaDirtyTimestamp); info.setStatusWithoutDirty(newStatus); } else { info.setStatus(newStatus); } info.setActionType(ActionType.MODIFIED); recentlyChangedQueue .add(new RecentlyChangedItem(lease)); info.setLastUpdatedTimestamp(); invalidateCache(appName, info.getVIPAddress(), info.getSecureVipAddress()); } return true; } } finally { read.unlock(); } } /** * Removes status override for a give instance. * * @param appName * the application name of the instance. * @param id * the unique identifier of the instance. * @param newStatus * the new {@link InstanceStatus}. * @param lastDirtyTimestamp * last timestamp when this instance information was updated. * @param isReplication * true if this is a replication event from other nodes, false * otherwise. * @return true if the status was successfully updated, false otherwise. */ public boolean deleteStatusOverride(String appName, String id, InstanceStatus newStatus, String lastDirtyTimestamp, boolean isReplication) { try { read.lock(); STATUS_OVERRIDE_DELETE.increment(isReplication); Map> gMap = registry.get(appName); Lease lease = null; if (gMap != null) { lease = gMap.get(id); } if (lease == null) { return false; } else { lease.renew(); InstanceInfo info = lease.getHolder(); InstanceStatus currentOverride = overriddenInstanceStatusMap.remove(id); if (currentOverride != null && info != null) { info.setOverriddenStatus(InstanceStatus.UNKNOWN); info.setStatus(newStatus); long replicaDirtyTimestamp = 0; if (lastDirtyTimestamp != null) { replicaDirtyTimestamp = Long .valueOf(lastDirtyTimestamp); } // If the replication's dirty timestamp is more than the // existing one, just update // it to the replica's. if (replicaDirtyTimestamp > info.getLastDirtyTimestamp()) { info.setLastDirtyTimestamp(replicaDirtyTimestamp); } info.setActionType(ActionType.MODIFIED); recentlyChangedQueue .add(new RecentlyChangedItem(lease)); info.setLastUpdatedTimestamp(); invalidateCache(appName, info.getVIPAddress(), info.getSecureVipAddress()); } return true; } } finally { read.unlock(); } } /** * Evicts everything in the instance registry that has expired, if expiry is enabled. * * @see com.netflix.eureka.lease.LeaseManager#evict() */ public void evict() { if (!isLeaseExpirationEnabled()) { logger.debug("DS: lease expiration is currently disabled."); return; } logger.debug("Running the evict task"); for (Entry>> groupEntry : registry.entrySet()) { Map> leaseMap = groupEntry.getValue(); if (leaseMap != null) { for (Entry> leaseEntry : leaseMap.entrySet()) { Lease lease = leaseEntry.getValue(); if (lease.isExpired() && lease.getHolder() != null) { String appName = lease.getHolder().getAppName(); String id = lease.getHolder().getId(); EXPIRED.increment(); logger.warn("DS: Registry: expired lease for " + appName + " - " + id); cancel(appName, id, false); } } } } } /** * Returns the given app that is in this instance only, falling back to other regions transparently only * if specified in this client configuration. * * @param appName * - the application name of the application * @return the application * * @see * com.netflix.discovery.shared.LookupService#getApplication(java.lang.String) */ public Application getApplication(String appName) { boolean disableTransparentFallback = EUREKA_CONFIG.disableTransparentFallbackToOtherRegion(); return this.getApplication(appName, !disableTransparentFallback); } /** * Get application information. * * @param appName * - The name of the application * @param includeRemoteRegion * - true, if we need to include applications from remote regions * as indicated by the region {@link URL} by this property * {@link EurekaServerConfig#getRemoteRegionUrls()}, false * otherwise * @return the application */ public Application getApplication(String appName, boolean includeRemoteRegion) { Application app = null; Map> leaseMap = registry.get(appName); if (leaseMap != null && leaseMap.size() > 0) { for (Entry> entry : leaseMap.entrySet()) { if (app == null) { app = new Application(appName); } app.addInstance(decorateInstanceInfo(entry.getValue())); } } else if (includeRemoteRegion) { for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) { Application application = remoteRegistry.getApplication(appName); if (application != null) { return application; } } } return app; } /** * Get all applications in this instance registry, falling back to other regions if allowed in the Eureka config. * * @return the list of all known applications * * @see com.netflix.discovery.shared.LookupService#getApplications() */ public Applications getApplications() { boolean disableTransparentFallback = EUREKA_CONFIG.disableTransparentFallbackToOtherRegion(); if (disableTransparentFallback) { return getApplicationsFromLocalRegionOnly(); } else { return this.getApplications(true); // Behavior of falling back to remote region can be disabled. } } /** * Returns applications including instances from all remote regions.
* Same as calling {@link #getApplicationsFromMultipleRegions(String[])} with a null argument. */ public Applications getApplicationsFromAllRemoteRegions() { return getApplicationsFromMultipleRegions(allKnownRemoteRegions); } /** * Returns applications including instances from local region only.
* Same as calling {@link #getApplicationsFromMultipleRegions(String[])} with an empty array. */ public Applications getApplicationsFromLocalRegionOnly() { return getApplicationsFromMultipleRegions(EMPTY_STR_ARRAY); } /** * This method will return applications with instances from all passed remote regions as well as the current region. * Thus, this gives a union view of instances from multiple regions.
* The application instances for which this union will be done can be restricted to the names returned by * {@link EurekaServerConfig#getRemoteRegionAppWhitelist(String)} for every region. In case, there is no whitelist * defined for a region, this method will also look for a global whitelist by passing null to the * method {@link EurekaServerConfig#getRemoteRegionAppWhitelist(String)}
* If you are not selectively requesting for a remote region, use {@link #getApplicationsFromAllRemoteRegions()} * or {@link #getApplicationsFromLocalRegionOnly()} * * @param remoteRegions The remote regions for which the instances are to be queried. The instances may be limited * by a whitelist as explained above. If null or empty no remote regions are * included. * * @return The applications with instances from the passed remote regions as well as local region. The instances * from remote regions can be only for certain whitelisted apps as explained above. */ public Applications getApplicationsFromMultipleRegions(String[] remoteRegions) { boolean includeRemoteRegion = null != remoteRegions && remoteRegions.length != 0; logger.info("Fetching applications registry with remote regions: {}, Regions argument {}", includeRemoteRegion, Arrays.toString(remoteRegions)); if (includeRemoteRegion) { GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS.increment(); } else { GET_ALL_CACHE_MISS.increment(); } Applications apps = new Applications(); apps.setVersion(1L); for (Entry>> entry : registry.entrySet()) { Application app = null; if (entry.getValue() != null) { for (Entry> stringLeaseEntry : entry.getValue().entrySet()) { Lease lease = stringLeaseEntry.getValue(); if (app == null) { app = new Application(lease.getHolder().getAppName()); } app.addInstance(decorateInstanceInfo(lease)); } } if (app != null) { apps.addApplication(app); } } if (includeRemoteRegion) { for (String remoteRegion : remoteRegions) { RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion); if (null != remoteRegistry) { Applications remoteApps = remoteRegistry.getApplications(); for (Application application : remoteApps.getRegisteredApplications()) { if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) { logger.info("Application {} fetched from the remote region {}", application.getName(), remoteRegion); Application appInstanceTillNow = apps.getRegisteredApplications(application.getName()); if (appInstanceTillNow == null) { appInstanceTillNow = new Application(application.getName()); apps.addApplication(appInstanceTillNow); } for (InstanceInfo instanceInfo : application.getInstances()) { appInstanceTillNow.addInstance(instanceInfo); } } else { logger.debug("Application {} not fetched from the remote region {} as there exists a " + "whitelist and this app is not in the whitelist.", application.getName(), remoteRegion); } } } else { logger.warn("No remote registry available for the remote region {}", remoteRegion); } } } apps.setAppsHashCode(apps.getReconcileHashCode()); return apps; } private boolean shouldFetchFromRemoteRegistry(String appName, String remoteRegion) { Set whiteList = EUREKA_CONFIG.getRemoteRegionAppWhitelist(remoteRegion); if (null == whiteList) { whiteList = EUREKA_CONFIG.getRemoteRegionAppWhitelist(null); // see global whitelist. } return null == whiteList || whiteList.contains(appName); } /** * Get the registry information about all {@link Applications}. * * @param includeRemoteRegion * - true, if we need to include applications from remote regions * as indicated by the region {@link URL} by this property * {@link EurekaServerConfig#getRemoteRegionUrls()}, false * otherwise * @return applications * @deprecated Use {@link #getApplicationsFromMultipleRegions(String[])} instead. This method has a flawed behavior * of transparently falling back to a remote region if no instances for an app is available locally. The new * behavior is to explicitly specify if you need a remote region. */ @Deprecated public Applications getApplications(boolean includeRemoteRegion) { GET_ALL_CACHE_MISS.increment(); Applications apps = new Applications(); apps.setVersion(1L); for (Entry>> entry : registry.entrySet()) { Application app = null; if (entry.getValue() != null) { for (Entry> stringLeaseEntry : entry.getValue().entrySet()) { Lease lease = stringLeaseEntry.getValue(); if (app == null) { app = new Application(lease.getHolder().getAppName()); } app.addInstance(decorateInstanceInfo(lease)); } } if (app != null) { apps.addApplication(app); } } if (includeRemoteRegion) { for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) { Applications applications = remoteRegistry.getApplications(); for (Application application : applications .getRegisteredApplications()) { Application appInLocalRegistry = apps .getRegisteredApplications(application.getName()); if (appInLocalRegistry == null) { apps.addApplication(application); } } } } apps.setAppsHashCode(apps.getReconcileHashCode()); return apps; } /** * Get the registry information about the delta changes. The deltas are * cached for a window specified by * {@link EurekaServerConfig#getRetentionTimeInMSInDeltaQueue()}. Subsequent * requests for delta information may return the same information and client * must make sure this does not adversely affect them. * * @return all application deltas. * @deprecated use {@link #getApplicationDeltasFromMultipleRegions(String[])} instead. This method has a * flawed behavior of transparently falling back to a remote region if no instances for an app is available locally. * The new behavior is to explicitly specify if you need a remote region. */ @Deprecated public Applications getApplicationDeltas() { GET_ALL_CACHE_MISS_DELTA.increment(); Applications apps = new Applications(); apps.setVersion(ResponseCache.getVersionDelta().get()); Map applicationInstancesMap = new HashMap(); try { write.lock(); Iterator iter = this.recentlyChangedQueue.iterator(); logger.debug("The number of elements in the delta queue is :" + this.recentlyChangedQueue.size()); while (iter.hasNext()) { Lease lease = iter.next().getLeaseInfo(); InstanceInfo instanceInfo = lease.getHolder(); Object[] args = {instanceInfo.getId(), instanceInfo.getStatus().name(), instanceInfo.getActionType().name()}; logger.debug( "The instance id %s is found with status %s and actiontype %s", args); Application app = applicationInstancesMap.get(instanceInfo .getAppName()); if (app == null) { app = new Application(instanceInfo.getAppName()); applicationInstancesMap.put(instanceInfo.getAppName(), app); apps.addApplication(app); } app.addInstance(decorateInstanceInfo(lease)); } boolean disableTransparentFallback = EUREKA_CONFIG.disableTransparentFallbackToOtherRegion(); if (!disableTransparentFallback) { Applications allAppsInLocalRegion = getApplications(false); for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) { Applications applications = remoteRegistry.getApplicationDeltas(); for (Application application : applications.getRegisteredApplications()) { Application appInLocalRegistry = allAppsInLocalRegion.getRegisteredApplications(application.getName()); if (appInLocalRegistry == null) { apps.addApplication(application); } } } } Applications allApps = getApplications(!disableTransparentFallback); apps.setAppsHashCode(allApps.getReconcileHashCode()); return apps; } finally { write.unlock(); } } /** * Gets the application delta also including instances from the passed remote regions, with the instances from the * local region.
* * The remote regions from where the instances will be chosen can further be restricted if this application does not * appear in the whitelist specified for the region as returned by * {@link EurekaServerConfig#getRemoteRegionAppWhitelist(String)} for a region. In case, there is no whitelist * defined for a region, this method will also look for a global whitelist by passing null to the * method {@link EurekaServerConfig#getRemoteRegionAppWhitelist(String)}
* * @param remoteRegions The remote regions for which the instances are to be queried. The instances may be limited * by a whitelist as explained above. If null all remote regions are included. * If empty list then no remote region is included. * * @return The delta with instances from the passed remote regions as well as local region. The instances * from remote regions can be further be restricted as explained above. null if the application does * not exist locally or in remote regions. */ public Applications getApplicationDeltasFromMultipleRegions(String[] remoteRegions) { if (null == remoteRegions) { remoteRegions = allKnownRemoteRegions; // null means all remote regions. } boolean includeRemoteRegion = remoteRegions.length != 0; if (includeRemoteRegion) { GET_ALL_WITH_REMOTE_REGIONS_CACHE_MISS_DELTA.increment(); } else { GET_ALL_CACHE_MISS_DELTA.increment(); } Applications apps = new Applications(); apps.setVersion(ResponseCache.getVersionDeltaWithRegions().get()); Map applicationInstancesMap = new HashMap(); try { write.lock(); Iterator iter = this.recentlyChangedQueue.iterator(); logger.debug("The number of elements in the delta queue is :" + this.recentlyChangedQueue.size()); while (iter.hasNext()) { Lease lease = iter.next().getLeaseInfo(); InstanceInfo instanceInfo = lease.getHolder(); Object[] args = {instanceInfo.getId(), instanceInfo.getStatus().name(), instanceInfo.getActionType().name()}; logger.debug( "The instance id %s is found with status %s and actiontype %s", args); Application app = applicationInstancesMap.get(instanceInfo .getAppName()); if (app == null) { app = new Application(instanceInfo.getAppName()); applicationInstancesMap.put(instanceInfo.getAppName(), app); apps.addApplication(app); } app.addInstance(decorateInstanceInfo(lease)); } if (includeRemoteRegion) { for (String remoteRegion : remoteRegions) { RemoteRegionRegistry remoteRegistry = regionNameVSRemoteRegistry.get(remoteRegion); if (null != remoteRegistry) { Applications remoteAppsDelta = remoteRegistry.getApplicationDeltas(); if (null != remoteAppsDelta) { for (Application application : remoteAppsDelta.getRegisteredApplications()) { if (shouldFetchFromRemoteRegistry(application.getName(), remoteRegion)) { Application appInstanceTillNow = apps.getRegisteredApplications(application.getName()); if (appInstanceTillNow == null) { appInstanceTillNow = new Application(application.getName()); apps.addApplication(appInstanceTillNow); } for (InstanceInfo instanceInfo : application.getInstances()) { appInstanceTillNow.addInstance(instanceInfo); } } } } } } } Applications allApps = getApplicationsFromMultipleRegions(remoteRegions); apps.setAppsHashCode(allApps.getReconcileHashCode()); return apps; } finally { write.unlock(); } } /** * Gets the {@link InstanceInfo} information. * * @param appName * the application name for which the information is requested. * @param id * the unique identifier of the instance. * @return the information about the instance. */ public InstanceInfo getInstanceByAppAndId(String appName, String id) { return this.getInstanceByAppAndId(appName, id, true); } /** * Gets the {@link InstanceInfo} information. * * @param appName * the application name for which the information is requested. * @param id * the unique identifier of the instance. * @param includeRemoteRegions * - true, if we need to include applications from remote regions * as indicated by the region {@link URL} by this property * {@link EurekaServerConfig#getRemoteRegionUrls()}, false * otherwise * @return the information about the instance. */ public InstanceInfo getInstanceByAppAndId(String appName, String id, boolean includeRemoteRegions) { Map> leaseMap = registry.get(appName); Lease lease = null; if (leaseMap != null) { lease = leaseMap.get(id); } if (lease != null && (!isLeaseExpirationEnabled() || !lease.isExpired())) { return decorateInstanceInfo(lease); } else if (includeRemoteRegions) { for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) { Application application = remoteRegistry.getApplication(appName); if (application != null) { return application.getByInstanceId(id); } } } return null; } /** * Get all instances by ID, including automatically asking other regions if the ID is unknown. * * @see com.netflix.discovery.shared.LookupService#getInstancesById(String) */ public List getInstancesById(String id) { return this.getInstancesById(id, true); } /** * Get the list of instances by its unique id. * * @param id * - the unique id of the instance * @param includeRemoteRegions * - true, if we need to include applications from remote regions * as indicated by the region {@link URL} by this property * {@link EurekaServerConfig#getRemoteRegionUrls()}, false * otherwise * @return list of InstanceInfo objects. */ public List getInstancesById(String id, boolean includeRemoteRegions) { List list = new ArrayList(); for (Iterator>>> iter = registry .entrySet().iterator(); iter.hasNext(); ) { Map> leaseMap = iter.next().getValue(); if (leaseMap != null) { Lease lease = leaseMap.get(id); if (lease == null || (isLeaseExpirationEnabled() && lease.isExpired())) { continue; } if (list == Collections.EMPTY_LIST) { list = new ArrayList(); } list.add(decorateInstanceInfo(lease)); } } if (list.isEmpty() && includeRemoteRegions) { for (RemoteRegionRegistry remoteRegistry : this.regionNameVSRemoteRegistry.values()) { for (Application application : remoteRegistry.getApplications() .getRegisteredApplications()) { InstanceInfo instanceInfo = application.getByInstanceId(id); if (instanceInfo != null) { list.add(instanceInfo); return list; } } } } return list; } /** * Checks whether lease expiration is enabled. * * @return true if enabled */ public abstract boolean isLeaseExpirationEnabled(); private InstanceInfo decorateInstanceInfo(Lease lease) { InstanceInfo info = lease.getHolder(); // client app settings int renewalInterval = LeaseInfo.DEFAULT_LEASE_RENEWAL_INTERVAL; int leaseDuration = LeaseInfo.DEFAULT_LEASE_DURATION; // TODO: clean this up if (info.getLeaseInfo() != null) { renewalInterval = info.getLeaseInfo().getRenewalIntervalInSecs(); leaseDuration = info.getLeaseInfo().getDurationInSecs(); } info.setLeaseInfo(LeaseInfo.Builder.newBuilder() .setRegistrationTimestamp(lease.getRegistrationTimestamp()) .setRenewalTimestamp(lease.getLastRenewalTimestamp()) .setServiceUpTimestamp(lease.getServiceUpTimestamp()) .setRenewalIntervalInSecs(renewalInterval) .setDurationInSecs(leaseDuration) .setEvictionTimestamp(lease.getEvictionTimestamp()).build()); info.setIsCoordinatingDiscoveryServer(); return info; } /** * Servo route; do not call. * * @return servo data */ @com.netflix.servo.annotations.Monitor(name = "numOfRenewsInLastMin", description = "Number of total heartbeats received in the last minute", type = DataSourceType.GAUGE) public long getNumOfRenewsInLastMin() { if (renewsLastMin != null) { return renewsLastMin.getCount(); } else { return 0; } } /** * Get the N instances that are most recently registered. * * @return */ public List> getLastNRegisteredInstances() { List> list = new ArrayList>(); synchronized (recentRegisteredQueue) { for (Pair aRecentRegisteredQueue : recentRegisteredQueue) { list.add(aRecentRegisteredQueue); } } Collections.reverse(list); return list; } /** * Get the N instances that have most recently canceled. * * @return */ public List> getLastNCanceledInstances() { List> list = new ArrayList>(); synchronized (recentCanceledQueue) { for (Pair aRecentCanceledQueue : recentCanceledQueue) { list.add(aRecentCanceledQueue); } } Collections.reverse(list); return list; } private void invalidateCache(String appName, @Nullable String vipAddress, @Nullable String secureVipAddress) { // invalidate cache ResponseCache.getInstance().invalidate(appName, vipAddress, secureVipAddress); } private static final class RecentlyChangedItem { private long lastUpdateTime; private Lease leaseInfo; public RecentlyChangedItem(Lease lease) { this.leaseInfo = lease; lastUpdateTime = System.currentTimeMillis(); } public long getLastUpdateTime() { return this.lastUpdateTime; } public Lease getLeaseInfo() { return this.leaseInfo; } } protected void postInit() { renewsLastMin = new MeasuredRate(1000 * 60 * 1); if (EVICTION_TASK.get() != null) { EVICTION_TASK.get().cancel(); } EVICTION_TASK.set(new EvictionTask()); evictionTimer.schedule(EVICTION_TASK.get(), EUREKA_CONFIG.getEvictionIntervalTimerInMs(), EUREKA_CONFIG.getEvictionIntervalTimerInMs()); } @com.netflix.servo.annotations.Monitor(name = "numOfElementsinInstanceCache", description = "Number of elements in the instance Cache", type = DataSourceType.GAUGE) public long getNumberofElementsininstanceCache() { return overriddenInstanceStatusMap.size(); } private final class EvictionTask extends TimerTask { @Override public void run() { try { evict(); } catch (Throwable e) { logger.error("Could not run the evict task", e); } } } private class CircularQueue extends ConcurrentLinkedQueue { private int size = 0; public CircularQueue(int size) { this.size = size; } @Override public boolean add(E e) { this.makeSpaceIfNotAvailable(); return super.add(e); } private void makeSpaceIfNotAvailable() { if (this.size() == size) { this.remove(); } } public boolean offer(E e) { this.makeSpaceIfNotAvailable(); return super.offer(e); } } private InstanceStatus getOverriddenInstanceStatus(InstanceInfo r, Lease existingLease, boolean isReplication) { // ReplicationInstance is DOWN or STARTING - believe that, but when the instance // says UP, question that // The client instance sends STARTING or DOWN (because of heartbeat // failures), then we accept what // the client says. The same is the case with replica as well. // The OUT_OF_SERVICE from the client or replica needs to be confirmed // as well since the service may be // currently in SERVICE if ((!InstanceStatus.UP.equals(r.getStatus())) && (!InstanceStatus.OUT_OF_SERVICE.equals(r.getStatus()))) { logger.debug( "Trusting the instance status {} from replica or instance for instance", r.getStatus(), r.getId()); return r.getStatus(); } // Overrides are the status like OUT_OF_SERVICE and UP set by NAC InstanceStatus overridden = overriddenInstanceStatusMap.get(r.getId()); // If there are instance specific overrides, then they win - otherwise // the ASG status if (overridden != null) { logger.debug( "The instance specific override for instance {} and the value is {}", r.getId(), overridden.name()); return overridden; } // If the ASGName is present- check for its status boolean isASGDisabled = false; if (r.getASGName() != null) { isASGDisabled = !AwsAsgUtil.getInstance().isASGEnabled(r); logger.debug("The ASG name is specified {} and the value is {}", r.getASGName(), isASGDisabled); if (isASGDisabled) { return InstanceStatus.OUT_OF_SERVICE; } else { return InstanceStatus.UP; } } // This is for backward compatibility until all applications have ASG // names, otherwise while starting up // the client status may override status replicated from other servers if (!isReplication) { InstanceStatus existingStatus = null; if (existingLease != null) { existingStatus = existingLease.getHolder().getStatus(); } // Allow server to have its way when the status is UP or // OUT_OF_SERVICE if ((existingStatus != null) && (InstanceStatus.OUT_OF_SERVICE.equals(existingStatus) || InstanceStatus.UP.equals(existingStatus))) { logger.debug( "There is already an existing lease with status {} for instance {}", existingLease.getHolder().getStatus().name(), existingLease.getHolder().getId()); return existingLease.getHolder().getStatus(); } } logger.debug( "Returning the default instance status {} for instance {},", r.getStatus(), r.getId()); return r.getStatus(); } private TimerTask getDeltaRetentionTask() { return new TimerTask() { @Override public void run() { Iterator it = recentlyChangedQueue.iterator(); while (it.hasNext()) { if (it.next().getLastUpdateTime() < System.currentTimeMillis() - EUREKA_CONFIG.getRetentionTimeInMSInDeltaQueue()) { it.remove(); } else { break; } } } }; } protected void initRemoteRegionRegistry() throws MalformedURLException { Map remoteRegionUrlsWithName = EUREKA_CONFIG.getRemoteRegionUrlsWithName(); if (remoteRegionUrlsWithName != null) { allKnownRemoteRegions = new String[remoteRegionUrlsWithName.size()]; int remoteRegionArrayIndex = 0; for (Entry remoteRegionUrlWithName : remoteRegionUrlsWithName.entrySet()) { RemoteRegionRegistry remoteRegionRegistry = new RemoteRegionRegistry(remoteRegionUrlWithName.getKey(), new URL(remoteRegionUrlWithName.getValue())); regionNameVSRemoteRegistry.put(remoteRegionUrlWithName.getKey(), remoteRegionRegistry); allKnownRemoteRegions[remoteRegionArrayIndex++] = remoteRegionUrlWithName.getKey(); } } logger.info("Finished initializing remote region registries. All known remote regions: {}", Arrays.toString(allKnownRemoteRegions)); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy