eureka(一)
1.概述
eureka是一个服务注册中心,可以注册和发现服务,eureka可以搭建一个集群,当一台eureka挂掉后客户端会通过renew操作重新连接一个eureka来完成故障转移。
2.服务端
集群模式下eureka服务端启动时会首先启动一个discoverClient来发现别的eureka,如果发现则一次执行sync来完成数据的拷贝,以后通过Replication来完成数据同步。
首先构建discoverClient,然后发现所有peerNodes,每个peerNode代表一个eureka节点,然后全量同步一次数据。这些功能在EurekaBootstrap中完成
/* * 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.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import java.util.Date; import com.netflix.appinfo.ApplicationInfoManager; import com.netflix.appinfo.CloudInstanceConfig; import com.netflix.appinfo.DataCenterInfo; import com.netflix.appinfo.EurekaInstanceConfig; import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.MyDataCenterInstanceConfig; import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider; import com.netflix.config.ConfigurationManager; import com.netflix.config.DeploymentContext; import com.netflix.discovery.DefaultEurekaClientConfig; import com.netflix.discovery.DiscoveryClient; import com.netflix.discovery.EurekaClient; import com.netflix.discovery.EurekaClientConfig; import com.netflix.discovery.converters.JsonXStream; import com.netflix.discovery.converters.XmlXStream; import com.netflix.eureka.aws.AwsBinder; import com.netflix.eureka.aws.AwsBinderDelegate; import com.netflix.eureka.cluster.PeerEurekaNodes; import com.netflix.eureka.registry.AwsInstanceRegistry; import com.netflix.eureka.registry.PeerAwareInstanceRegistry; import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl; import com.netflix.eureka.resources.DefaultServerCodecs; import com.netflix.eureka.resources.ServerCodecs; import com.netflix.eureka.util.EurekaMonitors; import com.thoughtworks.xstream.XStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The class that kick starts the eureka server. * * <p> * The eureka server is configured by using the configuration * {@link EurekaServerConfig} specified by <em>eureka.server.props</em> in the * classpath. The eureka client component is also initialized by using the * configuration {@link EurekaInstanceConfig} specified by * <em>eureka.client.props</em>. If the server runs in the AWS cloud, the eureka * server binds it to the elastic ip as specified. * </p> * * @author Karthik Ranganathan, Greg Kim, David Liu * */ public class EurekaBootStrap implements ServletContextListener { private static final Logger logger = LoggerFactory.getLogger(EurekaBootStrap.class); private static final String TEST = "test"; private static final String ARCHAIUS_DEPLOYMENT_ENVIRONMENT = "archaius.deployment.environment"; private static final String EUREKA_ENVIRONMENT = "eureka.environment"; private static final String CLOUD = "cloud"; private static final String DEFAULT = "default"; private static final String ARCHAIUS_DEPLOYMENT_DATACENTER = "archaius.deployment.datacenter"; private static final String EUREKA_DATACENTER = "eureka.datacenter"; protected volatile EurekaServerContext serverContext; protected volatile AwsBinder awsBinder; private EurekaClient eurekaClient; /** * Construct a default instance of Eureka boostrap */ public EurekaBootStrap() { this(null); } /** * Construct an instance of eureka bootstrap with the supplied eureka client * * @param eurekaClient the eureka client to bootstrap */ public EurekaBootStrap(EurekaClient eurekaClient) { this.eurekaClient = eurekaClient; } /** * Initializes Eureka, including syncing up with other Eureka peers and publishing the registry. * * @see * javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent) */ @Override public void contextInitialized(ServletContextEvent event) { try { initEurekaEnvironment(); initEurekaServerContext(); ServletContext sc = event.getServletContext(); sc.setAttribute(EurekaServerContext.class.getName(), serverContext); } catch (Throwable e) { logger.error("Cannot bootstrap eureka server :", e); throw new RuntimeException("Cannot bootstrap eureka server :", e); } } /** * Users can override to initialize the environment themselves. */ protected void initEurekaEnvironment() throws Exception { logger.info("Setting the eureka configuration.."); String dataCenter = ConfigurationManager.getConfigInstance().getString(EUREKA_DATACENTER); if (dataCenter == null) { logger.info("Eureka data center value eureka.datacenter is not set, defaulting to default"); ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, DEFAULT); } else { ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_DATACENTER, dataCenter); } String environment = ConfigurationManager.getConfigInstance().getString(EUREKA_ENVIRONMENT); if (environment == null) { ConfigurationManager.getConfigInstance().setProperty(ARCHAIUS_DEPLOYMENT_ENVIRONMENT, TEST); logger.info("Eureka environment value eureka.environment is not set, defaulting to test"); } } /** * init hook for server context. Override for custom logic. */ protected void initEurekaServerContext() throws Exception { EurekaServerConfig eurekaServerConfig = new DefaultEurekaServerConfig(); // For backward compatibility JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); logger.info("Initializing the eureka client..."); logger.info(eurekaServerConfig.getJsonCodecName()); ServerCodecs serverCodecs = new DefaultServerCodecs(eurekaServerConfig); ApplicationInfoManager applicationInfoManager = null; if (eurekaClient == null) { EurekaInstanceConfig instanceConfig = isCloud(ConfigurationManager.getDeploymentContext()) ? new CloudInstanceConfig() : new MyDataCenterInstanceConfig(); applicationInfoManager = new ApplicationInfoManager( instanceConfig, new EurekaConfigBasedInstanceInfoProvider(instanceConfig).get()); EurekaClientConfig eurekaClientConfig = new DefaultEurekaClientConfig(); eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig); } else { applicationInfoManager = eurekaClient.getApplicationInfoManager(); } PeerAwareInstanceRegistry registry; if (isAws(applicationInfoManager.getInfo())) { registry = new AwsInstanceRegistry( eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, eurekaClient ); awsBinder = new AwsBinderDelegate(eurekaServerConfig, eurekaClient.getEurekaClientConfig(), registry, applicationInfoManager); awsBinder.start(); } else { registry = new PeerAwareInstanceRegistryImpl( eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, eurekaClient ); } PeerEurekaNodes peerEurekaNodes = getPeerEurekaNodes( registry, eurekaServerConfig, eurekaClient.getEurekaClientConfig(), serverCodecs, applicationInfoManager ); serverContext = new DefaultEurekaServerContext( eurekaServerConfig, serverCodecs, registry, peerEurekaNodes, applicationInfoManager ); EurekaServerContextHolder.initialize(serverContext); serverContext.initialize(); logger.info("Initialized server context"); // Copy registry from neighboring eureka node int registryCount = registry.syncUp(); registry.openForTraffic(applicationInfoManager, registryCount); // Register all monitoring statistics. EurekaMonitors.registerAllStats(); } protected PeerEurekaNodes getPeerEurekaNodes(PeerAwareInstanceRegistry registry, EurekaServerConfig eurekaServerConfig, EurekaClientConfig eurekaClientConfig, ServerCodecs serverCodecs, ApplicationInfoManager applicationInfoManager) { PeerEurekaNodes peerEurekaNodes = new PeerEurekaNodes( registry, eurekaServerConfig, eurekaClientConfig, serverCodecs, applicationInfoManager ); return peerEurekaNodes; } /** * Handles Eureka cleanup, including shutting down all monitors and yielding all EIPs. * * @see javax.servlet.ServletContextListener#contextDestroyed(javax.servlet.ServletContextEvent) */ @Override public void contextDestroyed(ServletContextEvent event) { try { logger.info("{} Shutting down Eureka Server..", new Date()); ServletContext sc = event.getServletContext(); sc.removeAttribute(EurekaServerContext.class.getName()); destroyEurekaServerContext(); destroyEurekaEnvironment(); } catch (Throwable e) { logger.error("Error shutting down eureka", e); } logger.info("{} Eureka Service is now shutdown...", new Date()); } /** * Server context shutdown hook. Override for custom logic */ protected void destroyEurekaServerContext() throws Exception { EurekaMonitors.shutdown(); if (awsBinder != null) { awsBinder.shutdown(); } if (serverContext != null) { serverContext.shutdown(); } } /** * Users can override to clean up the environment themselves. */ protected void destroyEurekaEnvironment() throws Exception { } protected boolean isAws(InstanceInfo selfInstanceInfo) { boolean result = DataCenterInfo.Name.Amazon == selfInstanceInfo.getDataCenterInfo().getName(); logger.info("isAws returned {}", result); return result; } protected boolean isCloud(DeploymentContext deploymentContext) { logger.info("Deployment datacenter is {}", deploymentContext.getDeploymentDatacenter()); return CLOUD.equals(deploymentContext.getDeploymentDatacenter()); } }
以后会通过Replication来完成数据同步。
package com.netflix.eureka.cluster.protocol; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.netflix.appinfo.InstanceInfo; import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl.Action; /** * The jersey resource class that generates a particular replication event */ public class ReplicationInstance { private String appName; private String id; private Long lastDirtyTimestamp; private String overriddenStatus; private String status; private InstanceInfo instanceInfo; private Action action; @JsonCreator public ReplicationInstance(@JsonProperty("appName") String appName, @JsonProperty("id") String id, @JsonProperty("lastDirtyTimestamp") Long lastDirtyTimestamp, @JsonProperty("overriddenStatus") String overriddenStatus, @JsonProperty("status") String status, @JsonProperty("instanceInfo") InstanceInfo instanceInfo, @JsonProperty("action") Action action) { this.appName = appName; this.id = id; this.lastDirtyTimestamp = lastDirtyTimestamp; this.overriddenStatus = overriddenStatus; this.status = status; this.instanceInfo = instanceInfo; this.action = action; } public String getAppName() { return appName; } public String getId() { return id; } public Long getLastDirtyTimestamp() { return lastDirtyTimestamp; } public String getOverriddenStatus() { return overriddenStatus; } public String getStatus() { return status; } public InstanceInfo getInstanceInfo() { return instanceInfo; } public Action getAction() { return action; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ReplicationInstance that = (ReplicationInstance) o; if (appName != null ? !appName.equals(that.appName) : that.appName != null) return false; if (id != null ? !id.equals(that.id) : that.id != null) return false; if (lastDirtyTimestamp != null ? !lastDirtyTimestamp.equals(that.lastDirtyTimestamp) : that.lastDirtyTimestamp != null) return false; if (overriddenStatus != null ? !overriddenStatus.equals(that.overriddenStatus) : that.overriddenStatus != null) return false; if (status != null ? !status.equals(that.status) : that.status != null) return false; if (instanceInfo != null ? !instanceInfo.equals(that.instanceInfo) : that.instanceInfo != null) return false; return action == that.action; } @Override public int hashCode() { int result = appName != null ? appName.hashCode() : 0; result = 31 * result + (id != null ? id.hashCode() : 0); result = 31 * result + (lastDirtyTimestamp != null ? lastDirtyTimestamp.hashCode() : 0); result = 31 * result + (overriddenStatus != null ? overriddenStatus.hashCode() : 0); result = 31 * result + (status != null ? status.hashCode() : 0); result = 31 * result + (instanceInfo != null ? instanceInfo.hashCode() : 0); result = 31 * result + (action != null ? action.hashCode() : 0); return result; } public static ReplicationInstanceBuilder replicationInstance() { return ReplicationInstanceBuilder.aReplicationInstance(); } public static class ReplicationInstanceBuilder { private String appName; private String id; private Long lastDirtyTimestamp; private String overriddenStatus; private String status; private InstanceInfo instanceInfo; private Action action; private ReplicationInstanceBuilder() { } public static ReplicationInstanceBuilder aReplicationInstance() { return new ReplicationInstanceBuilder(); } public ReplicationInstanceBuilder withAppName(String appName) { this.appName = appName; return this; } public ReplicationInstanceBuilder withId(String id) { this.id = id; return this; } public ReplicationInstanceBuilder withLastDirtyTimestamp(Long lastDirtyTimestamp) { this.lastDirtyTimestamp = lastDirtyTimestamp; return this; } public ReplicationInstanceBuilder withOverriddenStatus(String overriddenStatus) { this.overriddenStatus = overriddenStatus; return this; } public ReplicationInstanceBuilder withStatus(String status) { this.status = status; return this; } public ReplicationInstanceBuilder withInstanceInfo(InstanceInfo instanceInfo) { this.instanceInfo = instanceInfo; return this; } public ReplicationInstanceBuilder withAction(Action action) { this.action = action; return this; } public ReplicationInstanceBuilder but() { return aReplicationInstance().withAppName(appName).withId(id).withLastDirtyTimestamp(lastDirtyTimestamp).withOverriddenStatus(overriddenStatus).withStatus(status).withInstanceInfo(instanceInfo).withAction(action); } public ReplicationInstance build() { return new ReplicationInstance( appName, id, lastDirtyTimestamp, overriddenStatus, status, instanceInfo, action ); } } }
3.客户端
客户端主要是服务发现功能,客户端启动时会连接一个eureka节点,然后封装本地信息进行注册, 然后不断定时发送心跳包来保证eureka是可用的,当心跳失败是会执行renew操作,然后重新registry。
/* * 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.discovery; import static com.netflix.discovery.EurekaClientNames.METRIC_REGISTRATION_PREFIX; import static com.netflix.discovery.EurekaClientNames.METRIC_REGISTRY_PREFIX; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.annotation.Nullable; import javax.annotation.PreDestroy; import javax.inject.Provider; import javax.inject.Singleton; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.ws.rs.core.Response.Status; import com.netflix.discovery.shared.resolver.EndpointRandomizer; import com.netflix.discovery.shared.resolver.ResolverUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.inject.Inject; import com.netflix.appinfo.ApplicationInfoManager; import com.netflix.appinfo.HealthCheckCallback; import com.netflix.appinfo.HealthCheckCallbackToHandlerBridge; import com.netflix.appinfo.HealthCheckHandler; import com.netflix.appinfo.InstanceInfo; import com.netflix.appinfo.InstanceInfo.ActionType; import com.netflix.appinfo.InstanceInfo.InstanceStatus; import com.netflix.discovery.endpoint.EndpointUtils; import com.netflix.discovery.shared.Application; import com.netflix.discovery.shared.Applications; import com.netflix.discovery.shared.resolver.ClosableResolver; import com.netflix.discovery.shared.resolver.aws.ApplicationsResolver; import com.netflix.discovery.shared.transport.EurekaHttpClient; import com.netflix.discovery.shared.transport.EurekaHttpClientFactory; import com.netflix.discovery.shared.transport.EurekaHttpClients; import com.netflix.discovery.shared.transport.EurekaHttpResponse; import com.netflix.discovery.shared.transport.EurekaTransportConfig; import com.netflix.discovery.shared.transport.TransportClientFactory; import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClient; import com.netflix.discovery.shared.transport.jersey.Jersey1DiscoveryClientOptionalArgs; import com.netflix.discovery.shared.transport.jersey.Jersey1TransportClientFactories; import com.netflix.discovery.shared.transport.jersey.TransportClientFactories; import com.netflix.discovery.util.ThresholdLevelsMetric; import com.netflix.servo.annotations.DataSourceType; import com.netflix.servo.monitor.Counter; import com.netflix.servo.monitor.Monitors; import com.netflix.servo.monitor.Stopwatch; /** * The class that is instrumental for interactions with <tt>Eureka Server</tt>. * * <p> * <tt>Eureka Client</tt> is responsible for a) <em>Registering</em> the * instance with <tt>Eureka Server</tt> b) <em>Renewal</em>of the lease with * <tt>Eureka Server</tt> c) <em>Cancellation</em> of the lease from * <tt>Eureka Server</tt> during shutdown * <p> * d) <em>Querying</em> the list of services/instances registered with * <tt>Eureka Server</tt> * <p> * * <p> * <tt>Eureka Client</tt> needs a configured list of <tt>Eureka Server</tt> * {@link java.net.URL}s to talk to.These {@link java.net.URL}s are typically amazon elastic eips * which do not change. All of the functions defined above fail-over to other * {@link java.net.URL}s specified in the list in the case of failure. * </p> * * @author Karthik Ranganathan, Greg Kim * @author Spencer Gibb * */ @Singleton public class DiscoveryClient implements EurekaClient { private static final Logger logger = LoggerFactory.getLogger(DiscoveryClient.class); // Constants public static final String HTTP_X_DISCOVERY_ALLOW_REDIRECT = "X-Discovery-AllowRedirect"; private static final String VALUE_DELIMITER = ","; private static final String COMMA_STRING = VALUE_DELIMITER; /** * @deprecated here for legacy support as the client config has moved to be an instance variable */ @Deprecated private static EurekaClientConfig staticClientConfig; // Timers private static final String PREFIX = "DiscoveryClient_"; private final Counter RECONCILE_HASH_CODES_MISMATCH = Monitors.newCounter(PREFIX + "ReconcileHashCodeMismatch"); private final com.netflix.servo.monitor.Timer FETCH_REGISTRY_TIMER = Monitors .newTimer(PREFIX + "FetchRegistry"); private final Counter REREGISTER_COUNTER = Monitors.newCounter(PREFIX + "Reregister"); // instance variables /** * A scheduler to be used for the following 3 tasks: * - updating service urls * - scheduling a TimedSuperVisorTask */ private final ScheduledExecutorService scheduler; // additional executors for supervised subtasks private final ThreadPoolExecutor heartbeatExecutor; private final ThreadPoolExecutor cacheRefreshExecutor; private TimedSupervisorTask cacheRefreshTask; private TimedSupervisorTask heartbeatTask; private final Provider<HealthCheckHandler> healthCheckHandlerProvider; private final Provider<HealthCheckCallback> healthCheckCallbackProvider; private final PreRegistrationHandler preRegistrationHandler; private final AtomicReference<Applications> localRegionApps = new AtomicReference<Applications>(); private final Lock fetchRegistryUpdateLock = new ReentrantLock(); // monotonically increasing generation counter to ensure stale threads do not reset registry to an older version private final AtomicLong fetchRegistryGeneration; private final ApplicationInfoManager applicationInfoManager; private final InstanceInfo instanceInfo; private final AtomicReference<String> remoteRegionsToFetch; private final AtomicReference<String[]> remoteRegionsRef; private final InstanceRegionChecker instanceRegionChecker; private final EndpointUtils.ServiceUrlRandomizer urlRandomizer; private final EndpointRandomizer endpointRandomizer; private final Provider<BackupRegistry> backupRegistryProvider; private final EurekaTransport eurekaTransport; private final AtomicReference<HealthCheckHandler> healthCheckHandlerRef = new AtomicReference<>(); private volatile Map<String, Applications> remoteRegionVsApps = new ConcurrentHashMap<>(); private volatile InstanceInfo.InstanceStatus lastRemoteInstanceStatus = InstanceInfo.InstanceStatus.UNKNOWN; private final CopyOnWriteArraySet<EurekaEventListener> eventListeners = new CopyOnWriteArraySet<>(); private String appPathIdentifier; private ApplicationInfoManager.StatusChangeListener statusChangeListener; private InstanceInfoReplicator instanceInfoReplicator; private volatile int registrySize = 0; private volatile long lastSuccessfulRegistryFetchTimestamp = -1; private volatile long lastSuccessfulHeartbeatTimestamp = -1; private final ThresholdLevelsMetric heartbeatStalenessMonitor; private final ThresholdLevelsMetric registryStalenessMonitor; private final AtomicBoolean isShutdown = new AtomicBoolean(false); protected final EurekaClientConfig clientConfig; protected final EurekaTransportConfig transportConfig; private final long initTimestampMs; private final int initRegistrySize; private final Stats stats = new Stats(); private static final class EurekaTransport { private ClosableResolver bootstrapResolver; private TransportClientFactory transportClientFactory; private EurekaHttpClient registrationClient; private EurekaHttpClientFactory registrationClientFactory; private EurekaHttpClient queryClient; private EurekaHttpClientFactory queryClientFactory; void shutdown() { if (registrationClientFactory != null) { registrationClientFactory.shutdown(); } if (queryClientFactory != null) { queryClientFactory.shutdown(); } if (registrationClient != null) { registrationClient.shutdown(); } if (queryClient != null) { queryClient.shutdown(); } if (transportClientFactory != null) { transportClientFactory.shutdown(); } if (bootstrapResolver != null) { bootstrapResolver.shutdown(); } } } public static class DiscoveryClientOptionalArgs extends Jersey1DiscoveryClientOptionalArgs { } /** * Assumes applicationInfoManager is already initialized * * @deprecated use constructor that takes ApplicationInfoManager instead of InstanceInfo directly */ @Deprecated public DiscoveryClient(InstanceInfo myInfo, EurekaClientConfig config) { this(myInfo, config, null); } /** * Assumes applicationInfoManager is already initialized * * @deprecated use constructor that takes ApplicationInfoManager instead of InstanceInfo directly */ @Deprecated public DiscoveryClient(InstanceInfo myInfo, EurekaClientConfig config, DiscoveryClientOptionalArgs args) { this(ApplicationInfoManager.getInstance(), config, args); } /** * @deprecated use constructor that takes ApplicationInfoManager instead of InstanceInfo directly */ @Deprecated public DiscoveryClient(InstanceInfo myInfo, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) { this(ApplicationInfoManager.getInstance(), config, args); } public DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config) { this(applicationInfoManager, config, null); } /** * @deprecated use the version that take {@link com.netflix.discovery.AbstractDiscoveryClientOptionalArgs} instead */ @Deprecated public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, DiscoveryClientOptionalArgs args) { this(applicationInfoManager, config, (AbstractDiscoveryClientOptionalArgs) args); } public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args) { this(applicationInfoManager, config, args, ResolverUtils::randomize); } public DiscoveryClient(ApplicationInfoManager applicationInfoManager, final EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, EndpointRandomizer randomizer) { this(applicationInfoManager, config, args, new Provider<BackupRegistry>() { private volatile BackupRegistry backupRegistryInstance; @Override public synchronized BackupRegistry get() { if (backupRegistryInstance == null) { String backupRegistryClassName = config.getBackupRegistryImpl(); if (null != backupRegistryClassName) { try { backupRegistryInstance = (BackupRegistry) Class.forName(backupRegistryClassName).newInstance(); logger.info("Enabled backup registry of type {}", backupRegistryInstance.getClass()); } catch (InstantiationException e) { logger.error("Error instantiating BackupRegistry.", e); } catch (IllegalAccessException e) { logger.error("Error instantiating BackupRegistry.", e); } catch (ClassNotFoundException e) { logger.error("Error instantiating BackupRegistry.", e); } } if (backupRegistryInstance == null) { logger.warn("Using default backup registry implementation which does not do anything."); backupRegistryInstance = new NotImplementedRegistryImpl(); } } return backupRegistryInstance; } }, randomizer); } /** * @deprecated Use {@link #DiscoveryClient(ApplicationInfoManager, EurekaClientConfig, AbstractDiscoveryClientOptionalArgs, Provider<BackupRegistry>, EndpointRandomizer)} */ @Deprecated DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider) { this(applicationInfoManager, config, args, backupRegistryProvider, ResolverUtils::randomize); } @Inject DiscoveryClient(ApplicationInfoManager applicationInfoManager, EurekaClientConfig config, AbstractDiscoveryClientOptionalArgs args, Provider<BackupRegistry> backupRegistryProvider, EndpointRandomizer endpointRandomizer) { if (args != null) { this.healthCheckHandlerProvider = args.healthCheckHandlerProvider; this.healthCheckCallbackProvider = args.healthCheckCallbackProvider; this.eventListeners.addAll(args.getEventListeners()); this.preRegistrationHandler = args.preRegistrationHandler; } else { this.healthCheckCallbackProvider = null; this.healthCheckHandlerProvider = null; this.preRegistrationHandler = null; } this.applicationInfoManager = applicationInfoManager; InstanceInfo myInfo = applicationInfoManager.getInfo(); clientConfig = config; staticClientConfig = clientConfig; transportConfig = config.getTransportConfig(); instanceInfo = myInfo; if (myInfo != null) { appPathIdentifier = instanceInfo.getAppName() + "/" + instanceInfo.getId(); } else { logger.warn("Setting instanceInfo to a passed in null value"); } this.backupRegistryProvider = backupRegistryProvider; this.endpointRandomizer = endpointRandomizer; this.urlRandomizer = new EndpointUtils.InstanceInfoBasedUrlRandomizer(instanceInfo); localRegionApps.set(new Applications()); fetchRegistryGeneration = new AtomicLong(0); remoteRegionsToFetch = new AtomicReference<String>(clientConfig.fetchRegistryForRemoteRegions()); remoteRegionsRef = new AtomicReference<>(remoteRegionsToFetch.get() == null ? null : remoteRegionsToFetch.get().split(",")); if (config.shouldFetchRegistry()) { this.registryStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRY_PREFIX + "lastUpdateSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L}); } else { this.registryStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC; } if (config.shouldRegisterWithEureka()) { this.heartbeatStalenessMonitor = new ThresholdLevelsMetric(this, METRIC_REGISTRATION_PREFIX + "lastHeartbeatSec_", new long[]{15L, 30L, 60L, 120L, 240L, 480L}); } else { this.heartbeatStalenessMonitor = ThresholdLevelsMetric.NO_OP_METRIC; } logger.info("Initializing Eureka in region {}", clientConfig.getRegion()); if (!config.shouldRegisterWithEureka() && !config.shouldFetchRegistry()) { logger.info("Client configured to neither register nor query for data."); scheduler = null; heartbeatExecutor = null; cacheRefreshExecutor = null; eurekaTransport = null; instanceRegionChecker = new InstanceRegionChecker(new PropertyBasedAzToRegionMapper(config), clientConfig.getRegion()); // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance() // to work with DI'd DiscoveryClient DiscoveryManager.getInstance().setDiscoveryClient(this); DiscoveryManager.getInstance().setEurekaClientConfig(config); initTimestampMs = System.currentTimeMillis(); initRegistrySize = this.getApplications().size(); registrySize = initRegistrySize; logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", initTimestampMs, initRegistrySize); return; // no need to setup up an network tasks and we are done } try { // default size of 2 - 1 each for heartbeat and cacheRefresh scheduler = Executors.newScheduledThreadPool(2, new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-%d") .setDaemon(true) .build()); heartbeatExecutor = new ThreadPoolExecutor( 1, clientConfig.getHeartbeatExecutorThreadPoolSize(), 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-HeartbeatExecutor-%d") .setDaemon(true) .build() ); // use direct handoff cacheRefreshExecutor = new ThreadPoolExecutor( 1, clientConfig.getCacheRefreshExecutorThreadPoolSize(), 0, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), new ThreadFactoryBuilder() .setNameFormat("DiscoveryClient-CacheRefreshExecutor-%d") .setDaemon(true) .build() ); // use direct handoff eurekaTransport = new EurekaTransport(); scheduleServerEndpointTask(eurekaTransport, args); AzToRegionMapper azToRegionMapper; if (clientConfig.shouldUseDnsForFetchingServiceUrls()) { azToRegionMapper = new DNSBasedAzToRegionMapper(clientConfig); } else { azToRegionMapper = new PropertyBasedAzToRegionMapper(clientConfig); } if (null != remoteRegionsToFetch.get()) { azToRegionMapper.setRegionsToFetch(remoteRegionsToFetch.get().split(",")); } instanceRegionChecker = new InstanceRegionChecker(azToRegionMapper, clientConfig.getRegion()); } catch (Throwable e) { throw new RuntimeException("Failed to initialize DiscoveryClient!", e); } if (clientConfig.shouldFetchRegistry()) { try { boolean primaryFetchRegistryResult = fetchRegistry(false); if (!primaryFetchRegistryResult) { logger.info("Initial registry fetch from primary servers failed"); } boolean backupFetchRegistryResult = true; if (!primaryFetchRegistryResult && !fetchRegistryFromBackup()) { backupFetchRegistryResult = false; logger.info("Initial registry fetch from backup servers failed"); } if (!primaryFetchRegistryResult && !backupFetchRegistryResult && clientConfig.shouldEnforceFetchRegistryAtInit()) { throw new IllegalStateException("Fetch registry error at startup. Initial fetch failed."); } } catch (Throwable th) { logger.error("Fetch registry error at startup: {}", th.getMessage()); throw new IllegalStateException(th); } } // call and execute the pre registration handler before all background tasks (inc registration) is started if (this.preRegistrationHandler != null) { this.preRegistrationHandler.beforeRegistration(); } if (clientConfig.shouldRegisterWithEureka() && clientConfig.shouldEnforceRegistrationAtInit()) { try { if (!register() ) { throw new IllegalStateException("Registration error at startup. Invalid server response."); } } catch (Throwable th) { logger.error("Registration error at startup: {}", th.getMessage()); throw new IllegalStateException(th); } } // finally, init the schedule tasks (e.g. cluster resolvers, heartbeat, instanceInfo replicator, fetch initScheduledTasks(); try { Monitors.registerObject(this); } catch (Throwable e) { logger.warn("Cannot register timers", e); } // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance() // to work with DI'd DiscoveryClient DiscoveryManager.getInstance().setDiscoveryClient(this); DiscoveryManager.getInstance().setEurekaClientConfig(config); initTimestampMs = System.currentTimeMillis(); initRegistrySize = this.getApplications().size(); registrySize = initRegistrySize; logger.info("Discovery Client initialized at timestamp {} with initial instances count: {}", initTimestampMs, initRegistrySize); } private void scheduleServerEndpointTask(EurekaTransport eurekaTransport, AbstractDiscoveryClientOptionalArgs args) { Collection<?> additionalFilters = args == null ? Collections.emptyList() : args.additionalFilters; EurekaJerseyClient providedJerseyClient = args == null ? null : args.eurekaJerseyClient; TransportClientFactories argsTransportClientFactories = null; if (args != null && args.getTransportClientFactories() != null) { argsTransportClientFactories = args.getTransportClientFactories(); } // Ignore the raw types warnings since the client filter interface changed between jersey 1/2 @SuppressWarnings("rawtypes") TransportClientFactories transportClientFactories = argsTransportClientFactories == null ? new Jersey1TransportClientFactories() : argsTransportClientFactories; Optional<SSLContext> sslContext = args == null ? Optional.empty() : args.getSSLContext(); Optional<HostnameVerifier> hostnameVerifier = args == null ? Optional.empty() : args.getHostnameVerifier(); // If the transport factory was not supplied with args, assume they are using jersey 1 for passivity eurekaTransport.transportClientFactory = providedJerseyClient == null ? transportClientFactories.newTransportClientFactory(clientConfig, additionalFilters, applicationInfoManager.getInfo(), sslContext, hostnameVerifier) : transportClientFactories.newTransportClientFactory(additionalFilters, providedJerseyClient); ApplicationsResolver.ApplicationsSource applicationsSource = new ApplicationsResolver.ApplicationsSource() { @Override public Applications getApplications(int stalenessThreshold, TimeUnit timeUnit) { long thresholdInMs = TimeUnit.MILLISECONDS.convert(stalenessThreshold, timeUnit); long delay = getLastSuccessfulRegistryFetchTimePeriod(); if (delay > thresholdInMs) { logger.info("Local registry is too stale for local lookup. Threshold:{}, actual:{}", thresholdInMs, delay); return null; } else { return localRegionApps.get(); } } }; eurekaTransport.bootstrapResolver = EurekaHttpClients.newBootstrapResolver( clientConfig, transportConfig, eurekaTransport.transportClientFactory, applicationInfoManager.getInfo(), applicationsSource, endpointRandomizer ); if (clientConfig.shouldRegisterWithEureka()) { EurekaHttpClientFactory newRegistrationClientFactory = null; EurekaHttpClient newRegistrationClient = null; try { newRegistrationClientFactory = EurekaHttpClients.registrationClientFactory( eurekaTransport.bootstrapResolver, eurekaTransport.transportClientFactory, transportConfig ); newRegistrationClient = newRegistrationClientFactory.newClient(); } catch (Exception e) { logger.warn("Transport initialization failure", e); } eurekaTransport.registrationClientFactory = newRegistrationClientFactory; eurekaTransport.registrationClient = newRegistrationClient; } // new method (resolve from primary servers for read) // Configure new transport layer (candidate for injecting in the future) if (clientConfig.shouldFetchRegistry()) { EurekaHttpClientFactory newQueryClientFactory = null; EurekaHttpClient newQueryClient = null; try { newQueryClientFactory = EurekaHttpClients.queryClientFactory( eurekaTransport.bootstrapResolver, eurekaTransport.transportClientFactory, clientConfig, transportConfig, applicationInfoManager.getInfo(), applicationsSource, endpointRandomizer ); newQueryClient = newQueryClientFactory.newClient(); } catch (Exception e) { logger.warn("Transport initialization failure", e); } eurekaTransport.queryClientFactory = newQueryClientFactory; eurekaTransport.queryClient = newQueryClient; } } @Override public EurekaClientConfig getEurekaClientConfig() { return clientConfig; } @Override public ApplicationInfoManager getApplicationInfoManager() { return applicationInfoManager; } /* * (non-Javadoc) * @see com.netflix.discovery.shared.LookupService#getApplication(java.lang.String) */ @Override public Application getApplication(String appName) { return getApplications().getRegisteredApplications(appName); } /* * (non-Javadoc) * * @see com.netflix.discovery.shared.LookupService#getApplications() */ @Override public Applications getApplications() { return localRegionApps.get(); } @Override public Applications getApplicationsForARegion(@Nullable String region) { if (instanceRegionChecker.isLocalRegion(region)) { return localRegionApps.get(); } else { return remoteRegionVsApps.get(region); } } public Set<String> getAllKnownRegions() { String localRegion = instanceRegionChecker.getLocalRegion(); if (!remoteRegionVsApps.isEmpty()) { Set<String> regions = remoteRegionVsApps.keySet(); Set<String> toReturn = new HashSet<String>(regions); toReturn.add(localRegion); return toReturn; } else { return Collections.singleton(localRegion); } } /* * (non-Javadoc) * @see com.netflix.discovery.shared.LookupService#getInstancesById(java.lang.String) */ @Override public List<InstanceInfo> getInstancesById(String id) { List<InstanceInfo> instancesList = new ArrayList<>(); for (Application app : this.getApplications() .getRegisteredApplications()) { InstanceInfo instanceInfo = app.getByInstanceId(id); if (instanceInfo != null) { instancesList.add(instanceInfo); } } return instancesList; } /** * Register {@link HealthCheckCallback} with the eureka client. * * Once registered, the eureka client will invoke the * {@link HealthCheckCallback} in intervals specified by * {@link EurekaClientConfig#getInstanceInfoReplicationIntervalSeconds()}. * * @param callback app specific healthcheck. * * @deprecated Use */ @Deprecated @Override public void registerHealthCheckCallback(HealthCheckCallback callback) { if (instanceInfo == null) { logger.error("Cannot register a listener for instance info since it is null!"); } if (callback != null) { healthCheckHandlerRef.set(new HealthCheckCallbackToHandlerBridge(callback)); } } @Override public void registerHealthCheck(HealthCheckHandler healthCheckHandler) { if (instanceInfo == null) { logger.error("Cannot register a healthcheck handler when instance info is null!"); } if (healthCheckHandler != null) { this.healthCheckHandlerRef.set(healthCheckHandler); // schedule an onDemand update of the instanceInfo when a new healthcheck handler is registered if (instanceInfoReplicator != null) { instanceInfoReplicator.onDemandUpdate(); } } } @Override public void registerEventListener(EurekaEventListener eventListener) { this.eventListeners.add(eventListener); } @Override public boolean unregisterEventListener(EurekaEventListener eventListener) { return this.eventListeners.remove(eventListener); } /** * Gets the list of instances matching the given VIP Address. * * @param vipAddress * - The VIP address to match the instances for. * @param secure * - true if it is a secure vip address, false otherwise * @return - The list of {@link InstanceInfo} objects matching the criteria */ @Override public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure) { return getInstancesByVipAddress(vipAddress, secure, instanceRegionChecker.getLocalRegion()); } /** * Gets the list of instances matching the given VIP Address in the passed region. * * @param vipAddress - The VIP address to match the instances for. * @param secure - true if it is a secure vip address, false otherwise * @param region - region from which the instances are to be fetched. If <code>null</code> then local region is * assumed. * * @return - The list of {@link InstanceInfo} objects matching the criteria, empty list if not instances found. */ @Override public List<InstanceInfo> getInstancesByVipAddress(String vipAddress, boolean secure, @Nullable String region) { if (vipAddress == null) { throw new IllegalArgumentException( "Supplied VIP Address cannot be null"); } Applications applications; if (instanceRegionChecker.isLocalRegion(region)) { applications = this.localRegionApps.get(); } else { applications = remoteRegionVsApps.get(region); if (null == applications) { logger.debug("No applications are defined for region {}, so returning an empty instance list for vip " + "address {}.", region, vipAddress); return Collections.emptyList(); } } if (!secure) { return applications.getInstancesByVirtualHostName(vipAddress); } else { return applications.getInstancesBySecureVirtualHostName(vipAddress); } } /** * Gets the list of instances matching the given VIP Address and the given * application name if both of them are not null. If one of them is null, * then that criterion is completely ignored for matching instances. * * @param vipAddress * - The VIP address to match the instances for. * @param appName * - The applicationName to match the instances for. * @param secure * - true if it is a secure vip address, false otherwise. * @return - The list of {@link InstanceInfo} objects matching the criteria. */ @Override public List<InstanceInfo> getInstancesByVipAddressAndAppName( String vipAddress, String appName, boolean secure) { List<InstanceInfo> result = new ArrayList<InstanceInfo>(); if (vipAddress == null && appName == null) { throw new IllegalArgumentException( "Supplied VIP Address and application name cannot both be null"); } else if (vipAddress != null && appName == null) { return getInstancesByVipAddress(vipAddress, secure); } else if (vipAddress == null && appName != null) { Application application = getApplication(appName); if (application != null) { result = application.getInstances(); } return result; } String instanceVipAddress; for (Application app : getApplications().getRegisteredApplications()) { for (InstanceInfo instance : app.getInstances()) { if (secure) { instanceVipAddress = instance.getSecureVipAddress(); } else { instanceVipAddress = instance.getVIPAddress(); } if (instanceVipAddress == null) { continue; } String[] instanceVipAddresses = instanceVipAddress .split(COMMA_STRING); // If the VIP Address is delimited by a comma, then consider to // be a list of VIP Addresses. // Try to match at least one in the list, if it matches then // return the instance info for the same for (String vipAddressFromList : instanceVipAddresses) { if (vipAddress.equalsIgnoreCase(vipAddressFromList.trim()) && appName.equalsIgnoreCase(instance.getAppName())) { result.add(instance); break; } } } } return result; } /* * (non-Javadoc) * * @see * com.netflix.discovery.shared.LookupService#getNextServerFromEureka(java * .lang.String, boolean) */ @Override public InstanceInfo getNextServerFromEureka(String virtualHostname, boolean secure) { List<InstanceInfo> instanceInfoList = this.getInstancesByVipAddress( virtualHostname, secure); if (instanceInfoList == null || instanceInfoList.isEmpty()) { throw new RuntimeException("No matches for the virtual host name :" + virtualHostname); } Applications apps = this.localRegionApps.get(); int index = (int) (apps.getNextIndex(virtualHostname, secure).incrementAndGet() % instanceInfoList.size()); return instanceInfoList.get(index); } /** * Get all applications registered with a specific eureka service. * * @param serviceUrl * - The string representation of the service url. * @return - The registry information containing all applications. */ @Override public Applications getApplications(String serviceUrl) { try { EurekaHttpResponse<Applications> response = clientConfig.getRegistryRefreshSingleVipAddress() == null ? eurekaTransport.queryClient.getApplications() : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress()); if (response.getStatusCode() == Status.OK.getStatusCode()) { logger.debug(PREFIX + "{} - refresh status: {}", appPathIdentifier, response.getStatusCode()); return response.getEntity(); } logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, response.getStatusCode()); } catch (Throwable th) { logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, th.getMessage(), th); } return null; } /** * Register with the eureka service by making the appropriate REST call. */ boolean register() throws Throwable { logger.info(PREFIX + "{}: registering service...", appPathIdentifier); EurekaHttpResponse<Void> httpResponse; try { httpResponse = eurekaTransport.registrationClient.register(instanceInfo); } catch (Exception e) { logger.warn(PREFIX + "{} - registration failed {}", appPathIdentifier, e.getMessage(), e); throw e; } if (logger.isInfoEnabled()) { logger.info(PREFIX + "{} - registration status: {}", appPathIdentifier, httpResponse.getStatusCode()); } return httpResponse.getStatusCode() == Status.NO_CONTENT.getStatusCode(); } /** * Renew with the eureka service by making the appropriate REST call */ boolean renew() { EurekaHttpResponse<InstanceInfo> httpResponse; try { httpResponse = eurekaTransport.registrationClient.sendHeartBeat(instanceInfo.getAppName(), instanceInfo.getId(), instanceInfo, null); logger.debug(PREFIX + "{} - Heartbeat status: {}", appPathIdentifier, httpResponse.getStatusCode()); if (httpResponse.getStatusCode() == Status.NOT_FOUND.getStatusCode()) { REREGISTER_COUNTER.increment(); logger.info(PREFIX + "{} - Re-registering apps/{}", appPathIdentifier, instanceInfo.getAppName()); long timestamp = instanceInfo.setIsDirtyWithTime(); boolean success = register(); if (success) { instanceInfo.unsetIsDirty(timestamp); } return success; } return httpResponse.getStatusCode() == Status.OK.getStatusCode(); } catch (Throwable e) { logger.error(PREFIX + "{} - was unable to send heartbeat!", appPathIdentifier, e); return false; } } /** * @deprecated see replacement in {@link com.netflix.discovery.endpoint.EndpointUtils} * * Get the list of all eureka service urls from properties file for the eureka client to talk to. * * @param instanceZone The zone in which the client resides * @param preferSameZone true if we have to prefer the same zone as the client, false otherwise * @return The list of all eureka service urls for the eureka client to talk to */ @Deprecated @Override public List<String> getServiceUrlsFromConfig(String instanceZone, boolean preferSameZone) { return EndpointUtils.getServiceUrlsFromConfig(clientConfig, instanceZone, preferSameZone); } /** * Shuts down Eureka Client. Also sends a deregistration request to the * eureka server. */ @PreDestroy @Override public synchronized void shutdown() { if (isShutdown.compareAndSet(false, true)) { logger.info("Shutting down DiscoveryClient ..."); if (statusChangeListener != null && applicationInfoManager != null) { applicationInfoManager.unregisterStatusChangeListener(statusChangeListener.getId()); } cancelScheduledTasks(); // If APPINFO was registered if (applicationInfoManager != null && clientConfig.shouldRegisterWithEureka() && clientConfig.shouldUnregisterOnShutdown()) { applicationInfoManager.setInstanceStatus(InstanceStatus.DOWN); unregister(); } if (eurekaTransport != null) { eurekaTransport.shutdown(); } heartbeatStalenessMonitor.shutdown(); registryStalenessMonitor.shutdown(); Monitors.unregisterObject(this); logger.info("Completed shut down of DiscoveryClient"); } } /** * unregister w/ the eureka service. */ void unregister() { // It can be null if shouldRegisterWithEureka == false if(eurekaTransport != null && eurekaTransport.registrationClient != null) { try { logger.info("Unregistering ..."); EurekaHttpResponse<Void> httpResponse = eurekaTransport.registrationClient.cancel(instanceInfo.getAppName(), instanceInfo.getId()); logger.info(PREFIX + "{} - deregister status: {}", appPathIdentifier, httpResponse.getStatusCode()); } catch (Exception e) { logger.error(PREFIX + "{} - de-registration failed{}", appPathIdentifier, e.getMessage(), e); } } } /** * Fetches the registry information. * * <p> * This method tries to get only deltas after the first fetch unless there * is an issue in reconciling eureka server and client registry information. * </p> * * @param forceFullRegistryFetch Forces a full registry fetch. * * @return true if the registry was fetched */ private boolean fetchRegistry(boolean forceFullRegistryFetch) { Stopwatch tracer = FETCH_REGISTRY_TIMER.start(); try { // If the delta is disabled or if it is the first time, get all // applications Applications applications = getApplications(); if (clientConfig.shouldDisableDelta() || (!Strings.isNullOrEmpty(clientConfig.getRegistryRefreshSingleVipAddress())) || forceFullRegistryFetch || (applications == null) || (applications.getRegisteredApplications().size() == 0) || (applications.getVersion() == -1)) //Client application does not have latest library supporting delta { logger.info("Disable delta property : {}", clientConfig.shouldDisableDelta()); logger.info("Single vip registry refresh property : {}", clientConfig.getRegistryRefreshSingleVipAddress()); logger.info("Force full registry fetch : {}", forceFullRegistryFetch); logger.info("Application is null : {}", (applications == null)); logger.info("Registered Applications size is zero : {}", (applications.getRegisteredApplications().size() == 0)); logger.info("Application version is -1: {}", (applications.getVersion() == -1)); getAndStoreFullRegistry(); } else { getAndUpdateDelta(applications); } applications.setAppsHashCode(applications.getReconcileHashCode()); logTotalInstances(); } catch (Throwable e) { logger.error(PREFIX + "{} - was unable to refresh its cache! status = {}", appPathIdentifier, e.getMessage(), e); return false; } finally { if (tracer != null) { tracer.stop(); } } // Notify about cache refresh before updating the instance remote status onCacheRefreshed(); // Update remote status based on refreshed data held in the cache updateInstanceRemoteStatus(); // registry was fetched successfully, so return true return true; } private synchronized void updateInstanceRemoteStatus() { // Determine this instance's status for this app and set to UNKNOWN if not found InstanceInfo.InstanceStatus currentRemoteInstanceStatus = null; if (instanceInfo.getAppName() != null) { Application app = getApplication(instanceInfo.getAppName()); if (app != null) { InstanceInfo remoteInstanceInfo = app.getByInstanceId(instanceInfo.getId()); if (remoteInstanceInfo != null) { currentRemoteInstanceStatus = remoteInstanceInfo.getStatus(); } } } if (currentRemoteInstanceStatus == null) { currentRemoteInstanceStatus = InstanceInfo.InstanceStatus.UNKNOWN; } // Notify if status changed if (lastRemoteInstanceStatus != currentRemoteInstanceStatus) { onRemoteStatusChanged(lastRemoteInstanceStatus, currentRemoteInstanceStatus); lastRemoteInstanceStatus = currentRemoteInstanceStatus; } } /** * @return Return he current instance status as seen on the Eureka server. */ @Override public InstanceInfo.InstanceStatus getInstanceRemoteStatus() { return lastRemoteInstanceStatus; } private String getReconcileHashCode(Applications applications) { TreeMap<String, AtomicInteger> instanceCountMap = new TreeMap<String, AtomicInteger>(); if (isFetchingRemoteRegionRegistries()) { for (Applications remoteApp : remoteRegionVsApps.values()) { remoteApp.populateInstanceCountMap(instanceCountMap); } } applications.populateInstanceCountMap(instanceCountMap); return Applications.getReconcileHashCode(instanceCountMap); } /** * Gets the full registry information from the eureka server and stores it locally. * When applying the full registry, the following flow is observed: * * if (update generation have not advanced (due to another thread)) * atomically set the registry to the new registry * fi * * @return the full registry information. * @throws Throwable * on error. */ private void getAndStoreFullRegistry() throws Throwable { long currentUpdateGeneration = fetchRegistryGeneration.get(); logger.info("Getting all instance registry info from the eureka server"); Applications apps = null; EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get()) : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get()); if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) { apps = httpResponse.getEntity(); } logger.info("The response status is {}", httpResponse.getStatusCode()); if (apps == null) { logger.error("The application is null for some reason. Not storing this information"); } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) { localRegionApps.set(this.filterAndShuffle(apps)); logger.debug("Got full registry with apps hashcode {}", apps.getAppsHashCode()); } else { logger.warn("Not updating applications as another thread is updating it already"); } } /** * Get the delta registry information from the eureka server and update it locally. * When applying the delta, the following flow is observed: * * if (update generation have not advanced (due to another thread)) * atomically try to: update application with the delta and get reconcileHashCode * abort entire processing otherwise * do reconciliation if reconcileHashCode clash * fi * * @return the client response * @throws Throwable on error */ private void getAndUpdateDelta(Applications applications) throws Throwable { long currentUpdateGeneration = fetchRegistryGeneration.get(); Applications delta = null; EurekaHttpResponse<Applications> httpResponse = eurekaTransport.queryClient.getDelta(remoteRegionsRef.get()); if (httpResponse.getStatusCode() == Status.OK.getStatusCode()) { delta = httpResponse.getEntity(); } if (delta == null) { logger.warn("The server does not allow the delta revision to be applied because it is not safe. " + "Hence got the full registry."); getAndStoreFullRegistry(); } else if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) { logger.debug("Got delta update with apps hashcode {}", delta.getAppsHashCode()); String reconcileHashCode = ""; if (fetchRegistryUpdateLock.tryLock()) { try { updateDelta(delta); reconcileHashCode = getReconcileHashCode(applications); } finally { fetchRegistryUpdateLock.unlock(); } } else { logger.warn("Cannot acquire update lock, aborting getAndUpdateDelta"); } // There is a diff in number of instances for some reason if (!reconcileHashCode.equals(delta.getAppsHashCode()) || clientConfig.shouldLogDeltaDiff()) { reconcileAndLogDifference(delta, reconcileHashCode); // this makes a remoteCall } } else { logger.warn("Not updating application delta as another thread is updating it already"); logger.debug("Ignoring delta update with apps hashcode {}, as another thread is updating it already", delta.getAppsHashCode()); } } /** * Logs the total number of non-filtered instances stored locally. */ private void logTotalInstances() { if (logger.isDebugEnabled()) { int totInstances = 0; for (Application application : getApplications().getRegisteredApplications()) { totInstances += application.getInstancesAsIsFromEureka().size(); } logger.debug("The total number of all instances in the client now is {}", totInstances); } } /** * Reconcile the eureka server and client registry information and logs the differences if any. * When reconciling, the following flow is observed: * * make a remote call to the server for the full registry * calculate and log differences * if (update generation have not advanced (due to another thread)) * atomically set the registry to the new registry * fi * * @param delta * the last delta registry information received from the eureka * server. * @param reconcileHashCode * the hashcode generated by the server for reconciliation. * @return ClientResponse the HTTP response object. * @throws Throwable * on any error. */ private void reconcileAndLogDifference(Applications delta, String reconcileHashCode) throws Throwable { logger.debug("The Reconcile hashcodes do not match, client : {}, server : {}. Getting the full registry", reconcileHashCode, delta.getAppsHashCode()); RECONCILE_HASH_CODES_MISMATCH.increment(); long currentUpdateGeneration = fetchRegistryGeneration.get(); EurekaHttpResponse<Applications> httpResponse = clientConfig.getRegistryRefreshSingleVipAddress() == null ? eurekaTransport.queryClient.getApplications(remoteRegionsRef.get()) : eurekaTransport.queryClient.getVip(clientConfig.getRegistryRefreshSingleVipAddress(), remoteRegionsRef.get()); Applications serverApps = httpResponse.getEntity(); if (serverApps == null) { logger.warn("Cannot fetch full registry from the server; reconciliation failure"); return; } if (fetchRegistryGeneration.compareAndSet(currentUpdateGeneration, currentUpdateGeneration + 1)) { localRegionApps.set(this.filterAndShuffle(serverApps)); getApplications().setVersion(delta.getVersion()); logger.debug( "The Reconcile hashcodes after complete sync up, client : {}, server : {}.", getApplications().getReconcileHashCode(), delta.getAppsHashCode()); } else { logger.warn("Not setting the applications map as another thread has advanced the update generation"); } } /** * Updates the delta information fetches from the eureka server into the * local cache. * * @param delta * the delta information received from eureka server in the last * poll cycle. */ private void updateDelta(Applications delta) { int deltaCount = 0; for (Application app : delta.getRegisteredApplications()) { for (InstanceInfo instance : app.getInstances()) { Applications applications = getApplications(); String instanceRegion = instanceRegionChecker.getInstanceRegion(instance); if (!instanceRegionChecker.isLocalRegion(instanceRegion)) { Applications remoteApps = remoteRegionVsApps.get(instanceRegion); if (null == remoteApps) { remoteApps = new Applications(); remoteRegionVsApps.put(instanceRegion, remoteApps); } applications = remoteApps; } ++deltaCount; if (ActionType.ADDED.equals(instance.getActionType())) { Application existingApp = applications.getRegisteredApplications(instance.getAppName()); if (existingApp == null) { applications.addApplication(app); } logger.debug("Added instance {} to the existing apps in region {}", instance.getId(), instanceRegion); applications.getRegisteredApplications(instance.getAppName()).addInstance(instance); } else if (ActionType.MODIFIED.equals(instance.getActionType())) { Application existingApp = applications.getRegisteredApplications(instance.getAppName()); if (existingApp == null) { applications.addApplication(app); } logger.debug("Modified instance {} to the existing apps ", instance.getId()); applications.getRegisteredApplications(instance.getAppName()).addInstance(instance); } else if (ActionType.DELETED.equals(instance.getActionType())) { Application existingApp = applications.getRegisteredApplications(instance.getAppName()); if (existingApp != null) { logger.debug("Deleted instance {} to the existing apps ", instance.getId()); existingApp.removeInstance(instance); /* * We find all instance list from application(The status of instance status is not only the status is UP but also other status) * if instance list is empty, we remove the application. */ if (existingApp.getInstancesAsIsFromEureka().isEmpty()) { applications.removeApplication(existingApp); } } } } } logger.debug("The total number of instances fetched by the delta processor : {}", deltaCount); getApplications().setVersion(delta.getVersion()); getApplications().shuffleInstances(clientConfig.shouldFilterOnlyUpInstances()); for (Applications applications : remoteRegionVsApps.values()) { applications.setVersion(delta.getVersion()); applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances()); } } /** * Initializes all scheduled tasks. */ private void initScheduledTasks() { if (clientConfig.shouldFetchRegistry()) { // registry cache refresh timer int registryFetchIntervalSeconds = clientConfig.getRegistryFetchIntervalSeconds(); int expBackOffBound = clientConfig.getCacheRefreshExecutorExponentialBackOffBound(); cacheRefreshTask = new TimedSupervisorTask( "cacheRefresh", scheduler, cacheRefreshExecutor, registryFetchIntervalSeconds, TimeUnit.SECONDS, expBackOffBound, new CacheRefreshThread() ); scheduler.schedule( cacheRefreshTask, registryFetchIntervalSeconds, TimeUnit.SECONDS); } if (clientConfig.shouldRegisterWithEureka()) { int renewalIntervalInSecs = instanceInfo.getLeaseInfo().getRenewalIntervalInSecs(); int expBackOffBound = clientConfig.getHeartbeatExecutorExponentialBackOffBound(); logger.info("Starting heartbeat executor: " + "renew interval is: {}", renewalIntervalInSecs); // Heartbeat timer heartbeatTask = new TimedSupervisorTask( "heartbeat", scheduler, heartbeatExecutor, renewalIntervalInSecs, TimeUnit.SECONDS, expBackOffBound, new HeartbeatThread() ); scheduler.schedule( heartbeatTask, renewalIntervalInSecs, TimeUnit.SECONDS); // InstanceInfo replicator instanceInfoReplicator = new InstanceInfoReplicator( this, instanceInfo, clientConfig.getInstanceInfoReplicationIntervalSeconds(), 2); // burstSize statusChangeListener = new ApplicationInfoManager.StatusChangeListener() { @Override public String getId() { return "statusChangeListener"; } @Override public void notify(StatusChangeEvent statusChangeEvent) { if (InstanceStatus.DOWN == statusChangeEvent.getStatus() || InstanceStatus.DOWN == statusChangeEvent.getPreviousStatus()) { // log at warn level if DOWN was involved logger.warn("Saw local status change event {}", statusChangeEvent); } else { logger.info("Saw local status change event {}", statusChangeEvent); } instanceInfoReplicator.onDemandUpdate(); } }; if (clientConfig.shouldOnDemandUpdateStatusChange()) { applicationInfoManager.registerStatusChangeListener(statusChangeListener); } instanceInfoReplicator.start(clientConfig.getInitialInstanceInfoReplicationIntervalSeconds()); } else { logger.info("Not registering with Eureka server per configuration"); } } private void cancelScheduledTasks() { if (instanceInfoReplicator != null) { instanceInfoReplicator.stop(); } if (heartbeatExecutor != null) { heartbeatExecutor.shutdownNow(); } if (cacheRefreshExecutor != null) { cacheRefreshExecutor.shutdownNow(); } if (scheduler != null) { scheduler.shutdownNow(); } if (cacheRefreshTask != null) { cacheRefreshTask.cancel(); } if (heartbeatTask != null) { heartbeatTask.cancel(); } } /** * @deprecated see replacement in {@link com.netflix.discovery.endpoint.EndpointUtils} * * Get the list of all eureka service urls from DNS for the eureka client to * talk to. The client picks up the service url from its zone and then fails over to * other zones randomly. If there are multiple servers in the same zone, the client once * again picks one randomly. This way the traffic will be distributed in the case of failures. * * @param instanceZone The zone in which the client resides. * @param preferSameZone true if we have to prefer the same zone as the client, false otherwise. * @return The list of all eureka service urls for the eureka client to talk to. */ @Deprecated @Override public List<String> getServiceUrlsFromDNS(String instanceZone, boolean preferSameZone) { return EndpointUtils.getServiceUrlsFromDNS(clientConfig, instanceZone, preferSameZone, urlRandomizer); } /** * @deprecated see replacement in {@link com.netflix.discovery.endpoint.EndpointUtils} */ @Deprecated @Override public List<String> getDiscoveryServiceUrls(String zone) { return EndpointUtils.getDiscoveryServiceUrls(clientConfig, zone, urlRandomizer); } /** * @deprecated see replacement in {@link com.netflix.discovery.endpoint.EndpointUtils} * * Get the list of EC2 URLs given the zone name. * * @param dnsName The dns name of the zone-specific CNAME * @param type CNAME or EIP that needs to be retrieved * @return The list of EC2 URLs associated with the dns name */ @Deprecated public static Set<String> getEC2DiscoveryUrlsFromZone(String dnsName, EndpointUtils.DiscoveryUrlType type) { return EndpointUtils.getEC2DiscoveryUrlsFromZone(dnsName, type); } /** * Refresh the current local instanceInfo. Note that after a valid refresh where changes are observed, the * isDirty flag on the instanceInfo is set to true */ void refreshInstanceInfo() { applicationInfoManager.refreshDataCenterInfoIfRequired(); applicationInfoManager.refreshLeaseInfoIfRequired(); InstanceStatus status; try { status = getHealthCheckHandler().getStatus(instanceInfo.getStatus()); } catch (Exception e) { logger.warn("Exception from healthcheckHandler.getStatus, setting status to DOWN", e); status = InstanceStatus.DOWN; } if (null != status) { applicationInfoManager.setInstanceStatus(status); } } /** * The heartbeat task that renews the lease in the given intervals. */ private class HeartbeatThread implements Runnable { public void run() { if (renew()) { lastSuccessfulHeartbeatTimestamp = System.currentTimeMillis(); } } } @VisibleForTesting InstanceInfoReplicator getInstanceInfoReplicator() { return instanceInfoReplicator; } @VisibleForTesting InstanceInfo getInstanceInfo() { return instanceInfo; } @Override public HealthCheckHandler getHealthCheckHandler() { HealthCheckHandler healthCheckHandler = this.healthCheckHandlerRef.get(); if (healthCheckHandler == null) { if (null != healthCheckHandlerProvider) { healthCheckHandler = healthCheckHandlerProvider.get(); } else if (null != healthCheckCallbackProvider) { healthCheckHandler = new HealthCheckCallbackToHandlerBridge(healthCheckCallbackProvider.get()); } if (null == healthCheckHandler) { healthCheckHandler = new HealthCheckCallbackToHandlerBridge(null); } this.healthCheckHandlerRef.compareAndSet(null, healthCheckHandler); } return this.healthCheckHandlerRef.get(); } /** * The task that fetches the registry information at specified intervals. * */ class CacheRefreshThread implements Runnable { public void run() { refreshRegistry(); } } @VisibleForTesting void refreshRegistry() { try { boolean isFetchingRemoteRegionRegistries = isFetchingRemoteRegionRegistries(); boolean remoteRegionsModified = false; // This makes sure that a dynamic change to remote regions to fetch is honored. String latestRemoteRegions = clientConfig.fetchRegistryForRemoteRegions(); if (null != latestRemoteRegions) { String currentRemoteRegions = remoteRegionsToFetch.get(); if (!latestRemoteRegions.equals(currentRemoteRegions)) { // Both remoteRegionsToFetch and AzToRegionMapper.regionsToFetch need to be in sync synchronized (instanceRegionChecker.getAzToRegionMapper()) { if (remoteRegionsToFetch.compareAndSet(currentRemoteRegions, latestRemoteRegions)) { String[] remoteRegions = latestRemoteRegions.split(","); remoteRegionsRef.set(remoteRegions); instanceRegionChecker.getAzToRegionMapper().setRegionsToFetch(remoteRegions); remoteRegionsModified = true; } else { logger.info("Remote regions to fetch modified concurrently," + " ignoring change from {} to {}", currentRemoteRegions, latestRemoteRegions); } } } else { // Just refresh mapping to reflect any DNS/Property change instanceRegionChecker.getAzToRegionMapper().refreshMapping(); } } boolean success = fetchRegistry(remoteRegionsModified); if (success) { registrySize = localRegionApps.get().size(); lastSuccessfulRegistryFetchTimestamp = System.currentTimeMillis(); } if (logger.isDebugEnabled()) { StringBuilder allAppsHashCodes = new StringBuilder(); allAppsHashCodes.append("Local region apps hashcode: "); allAppsHashCodes.append(localRegionApps.get().getAppsHashCode()); allAppsHashCodes.append(", is fetching remote regions? "); allAppsHashCodes.append(isFetchingRemoteRegionRegistries); for (Map.Entry<String, Applications> entry : remoteRegionVsApps.entrySet()) { allAppsHashCodes.append(", Remote region: "); allAppsHashCodes.append(entry.getKey()); allAppsHashCodes.append(" , apps hashcode: "); allAppsHashCodes.append(entry.getValue().getAppsHashCode()); } logger.debug("Completed cache refresh task for discovery. All Apps hash code is {} ", allAppsHashCodes); } } catch (Throwable e) { logger.error("Cannot fetch registry from server", e); } } /** * Fetch the registry information from back up registry if all eureka server * urls are unreachable. * * @return true if the registry was fetched */ private boolean fetchRegistryFromBackup() { try { @SuppressWarnings("deprecation") BackupRegistry backupRegistryInstance = newBackupRegistryInstance(); if (null == backupRegistryInstance) { // backward compatibility with the old protected method, in case it is being used. backupRegistryInstance = backupRegistryProvider.get(); } if (null != backupRegistryInstance) { Applications apps = null; if (isFetchingRemoteRegionRegistries()) { String remoteRegionsStr = remoteRegionsToFetch.get(); if (null != remoteRegionsStr) { apps = backupRegistryInstance.fetchRegistry(remoteRegionsStr.split(",")); } } else { apps = backupRegistryInstance.fetchRegistry(); } if (apps != null) { final Applications applications = this.filterAndShuffle(apps); applications.setAppsHashCode(applications.getReconcileHashCode()); localRegionApps.set(applications); logTotalInstances(); logger.info("Fetched registry successfully from the backup"); return true; } } else { logger.warn("No backup registry instance defined & unable to find any discovery servers."); } } catch (Throwable e) { logger.warn("Cannot fetch applications from apps although backup registry was specified", e); } return false; } /** * @deprecated Use injection to provide {@link BackupRegistry} implementation. */ @Deprecated @Nullable protected BackupRegistry newBackupRegistryInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException { return null; } /** * Gets the <em>applications</em> after filtering the applications for * instances with only UP states and shuffling them. * * <p> * The filtering depends on the option specified by the configuration * {@link EurekaClientConfig#shouldFilterOnlyUpInstances()}. Shuffling helps * in randomizing the applications list there by avoiding the same instances * receiving traffic during start ups. * </p> * * @param apps * The applications that needs to be filtered and shuffled. * @return The applications after the filter and the shuffle. */ private Applications filterAndShuffle(Applications apps) { if (apps != null) { if (isFetchingRemoteRegionRegistries()) { Map<String, Applications> remoteRegionVsApps = new ConcurrentHashMap<String, Applications>(); apps.shuffleAndIndexInstances(remoteRegionVsApps, clientConfig, instanceRegionChecker); for (Applications applications : remoteRegionVsApps.values()) { applications.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances()); } this.remoteRegionVsApps = remoteRegionVsApps; } else { apps.shuffleInstances(clientConfig.shouldFilterOnlyUpInstances()); } } return apps; } private boolean isFetchingRemoteRegionRegistries() { return null != remoteRegionsToFetch.get(); } /** * Invoked when the remote status of this client has changed. * Subclasses may override this method to implement custom behavior if needed. * * @param oldStatus the previous remote {@link InstanceStatus} * @param newStatus the new remote {@link InstanceStatus} */ protected void onRemoteStatusChanged(InstanceInfo.InstanceStatus oldStatus, InstanceInfo.InstanceStatus newStatus) { fireEvent(new StatusChangeEvent(oldStatus, newStatus)); } /** * Invoked every time the local registry cache is refreshed (whether changes have * been detected or not). * * Subclasses may override this method to implement custom behavior if needed. */ protected void onCacheRefreshed() { fireEvent(new CacheRefreshedEvent()); } /** * Send the given event on the EventBus if one is available * * @param event the event to send on the eventBus */ protected void fireEvent(final EurekaEvent event) { for (EurekaEventListener listener : eventListeners) { try { listener.onEvent(event); } catch (Exception e) { logger.info("Event {} throw an exception for listener {}", event, listener, e.getMessage()); } } } /** * @deprecated see {@link com.netflix.appinfo.InstanceInfo#getZone(String[], com.netflix.appinfo.InstanceInfo)} * * Get the zone that a particular instance is in. * * @param myInfo * - The InstanceInfo object of the instance. * @return - The zone in which the particular instance belongs to. */ @Deprecated public static String getZone(InstanceInfo myInfo) { String[] availZones = staticClientConfig.getAvailabilityZones(staticClientConfig.getRegion()); return InstanceInfo.getZone(availZones, myInfo); } /** * @deprecated see replacement in {@link com.netflix.discovery.endpoint.EndpointUtils} * * Get the region that this particular instance is in. * * @return - The region in which the particular instance belongs to. */ @Deprecated public static String getRegion() { String region = staticClientConfig.getRegion(); if (region == null) { region = "default"; } region = region.trim().toLowerCase(); return region; } /** * @deprecated use {@link #getServiceUrlsFromConfig(String, boolean)} instead. */ @Deprecated public static List<String> getEurekaServiceUrlsFromConfig(String instanceZone, boolean preferSameZone) { return EndpointUtils.getServiceUrlsFromConfig(staticClientConfig, instanceZone, preferSameZone); } public long getLastSuccessfulHeartbeatTimePeriod() { return lastSuccessfulHeartbeatTimestamp < 0 ? lastSuccessfulHeartbeatTimestamp : System.currentTimeMillis() - lastSuccessfulHeartbeatTimestamp; } public long getLastSuccessfulRegistryFetchTimePeriod() { return lastSuccessfulRegistryFetchTimestamp < 0 ? lastSuccessfulRegistryFetchTimestamp : System.currentTimeMillis() - lastSuccessfulRegistryFetchTimestamp; } @com.netflix.servo.annotations.Monitor(name = METRIC_REGISTRATION_PREFIX + "lastSuccessfulHeartbeatTimePeriod", description = "How much time has passed from last successful heartbeat", type = DataSourceType.GAUGE) private long getLastSuccessfulHeartbeatTimePeriodInternal() { final long delay = (!clientConfig.shouldRegisterWithEureka() || isShutdown.get()) ? 0 : getLastSuccessfulHeartbeatTimePeriod(); heartbeatStalenessMonitor.update(computeStalenessMonitorDelay(delay)); return delay; } // for metrics only @com.netflix.servo.annotations.Monitor(name = METRIC_REGISTRY_PREFIX + "lastSuccessfulRegistryFetchTimePeriod", description = "How much time has passed from last successful local registry update", type = DataSourceType.GAUGE) private long getLastSuccessfulRegistryFetchTimePeriodInternal() { final long delay = (!clientConfig.shouldFetchRegistry() || isShutdown.get()) ? 0 : getLastSuccessfulRegistryFetchTimePeriod(); registryStalenessMonitor.update(computeStalenessMonitorDelay(delay)); return delay; } @com.netflix.servo.annotations.Monitor(name = METRIC_REGISTRY_PREFIX + "localRegistrySize", description = "Count of instances in the local registry", type = DataSourceType.GAUGE) public int localRegistrySize() { return registrySize; } private long computeStalenessMonitorDelay(long delay) { if (delay < 0) { return System.currentTimeMillis() - initTimestampMs; } else { return delay; } } /** * Gets stats for the DiscoveryClient. * * @return The DiscoveryClientStats instance. */ public Stats getStats() { return stats; } /** * Stats is used to track useful attributes of the DiscoveryClient. It includes helpers that can aid * debugging and log analysis. */ public class Stats { private Stats() {} public int initLocalRegistrySize() { return initRegistrySize; } public long initTimestampMs() { return initTimestampMs; } public int localRegistrySize() { return registrySize; } public long lastSuccessfulRegistryFetchTimestampMs() { return lastSuccessfulRegistryFetchTimestamp; } public long lastSuccessfulHeartbeatTimestampMs() { return lastSuccessfulHeartbeatTimestamp; } /** * Used to determine if the Discovery client's first attempt to fetch from the service registry succeeded with * non-empty results. * * @return true if succeeded, failed otherwise */ public boolean initSucceeded() { return initLocalRegistrySize() > 0 && initTimestampMs() > 0; } } }