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

com.netflix.discovery.shared.Applications 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.discovery.shared;

import javax.annotation.Nullable;
import java.util.AbstractQueue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.ActionType;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.InstanceRegionChecker;
import com.netflix.discovery.provider.Serializer;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The class that wraps all the registry information returned by eureka server.
 *
 * 

* Note that the registry information is fetched from eureka server as specified * in {@link EurekaClientConfig#getRegistryFetchIntervalSeconds()}. Once the * information is fetched it is shuffled and also filtered for instances with * {@link InstanceStatus#UP} status as specified by the configuration * {@link EurekaClientConfig#shouldFilterOnlyUpInstances()}. *

* * @author Karthik Ranganathan * */ @Serializer("com.netflix.discovery.converters.EntityBodyConverter") @XStreamAlias("applications") @JsonRootName("applications") public class Applications { private static final String APP_INSTANCEID_DELIMITER = "$$"; private static final Logger logger = LoggerFactory.getLogger(Applications.class); private static final String STATUS_DELIMITER = "_"; private Long versionDelta = Long.valueOf(-1); @XStreamImplicit private AbstractQueue applications; private Map appNameApplicationMap = new ConcurrentHashMap(); private Map> virtualHostNameAppMap = new ConcurrentHashMap>(); private Map> secureVirtualHostNameAppMap = new ConcurrentHashMap>(); private Map virtualHostNameIndexMap = new ConcurrentHashMap(); private Map secureVirtualHostNameIndexMap = new ConcurrentHashMap(); private Map>> shuffleVirtualHostNameMap = new ConcurrentHashMap>>(); private Map>> shuffledSecureVirtualHostNameMap = new ConcurrentHashMap>>(); private String appsHashCode; /** * Create a new, empty Eureka application list. */ public Applications() { this.applications = new ConcurrentLinkedQueue(); } /** * Note that appsHashCode and versionDelta key names are formatted in a custom/configurable way. */ @JsonCreator public Applications( @JsonProperty("appsHashCode") String appsHashCode, @JsonProperty("versionDelta") Long versionDelta, @JsonProperty("application") List registeredApplications) { this.applications = new ConcurrentLinkedQueue(); for (Application app : registeredApplications) { this.addApplication(app); } this.appsHashCode = appsHashCode; this.versionDelta = versionDelta; } /** * Create a new Eureka application list, based on the provided applications. The provided container is * not modified. * * @param apps the initial list of apps to store in this applications list */ public Applications(List apps) { this.applications = new ConcurrentLinkedQueue(); this.applications.addAll(apps); } /** * Add the application to the list. * * @param app * the application to be added. */ public void addApplication(Application app) { appNameApplicationMap.put(app.getName().toUpperCase(Locale.ROOT), app); addInstancesToVIPMaps(app); applications.add(app); } /** * Gets the list of all registered applications from eureka. * * @return list containing all applications registered with eureka. */ @JsonProperty("application") public List getRegisteredApplications() { List list = new ArrayList(); list.addAll(this.applications); return list; } /** * Gets the list of all registered applications for the given * application name. * * @param appName * the application name for which the result need to be fetched. * @return the list of registered applications for the given application * name. */ public Application getRegisteredApplications(String appName) { return appNameApplicationMap.get(appName.toUpperCase(Locale.ROOT)); } /** * Gets the list of instances associated to a virtual host name. * * @param virtualHostName * the virtual hostname for which the instances need to be * returned. * @return list of instances. */ public List getInstancesByVirtualHostName(String virtualHostName) { AtomicReference> ref = this.shuffleVirtualHostNameMap .get(virtualHostName.toUpperCase(Locale.ROOT)); if (ref == null || ref.get() == null) { return new ArrayList(); } else { return ref.get(); } } /** * Gets the list of secure instances associated to a virtual host * name. * * @param secureVirtualHostName * the virtual hostname for which the secure instances need to be * returned. * @return list of instances. */ public List getInstancesBySecureVirtualHostName(String secureVirtualHostName) { AtomicReference> ref = this.shuffledSecureVirtualHostNameMap .get(secureVirtualHostName.toUpperCase(Locale.ROOT)); if (ref == null || ref.get() == null) { return new ArrayList(); } else { return ref.get(); } } /** * @return a weakly consistent size of the number of instances in all the applications */ public int size() { int result = 0; for (Application application : applications) { result += application.size(); } return result; } @Deprecated public void setVersion(Long version) { this.versionDelta = version; } @Deprecated @JsonIgnore // Handled directly due to legacy name formatting public Long getVersion() { return this.versionDelta; } /** * Used by the eureka server. Not for external use. * * @param hashCode the hash code to assign for this app collection */ public void setAppsHashCode(String hashCode) { this.appsHashCode = hashCode; } /** * Used by the eureka server. Not for external use. * @return the string indicating the hashcode based on the applications stored. * */ @JsonIgnore // Handled directly due to legacy name formatting public String getAppsHashCode() { return this.appsHashCode; } /** * Gets the hash code for this applications instance. Used for * comparison of instances between eureka server and eureka client. * * @return the internal hash code representation indicating the information * about the instances. */ @JsonIgnore public String getReconcileHashCode() { TreeMap instanceCountMap = new TreeMap(); populateInstanceCountMap(instanceCountMap); return getReconcileHashCode(instanceCountMap); } /** * Populates the provided instance count map. The instance count map is used as part of the general * app list synchronization mechanism. * @param instanceCountMap the map to populate */ public void populateInstanceCountMap(TreeMap instanceCountMap) { for (Application app : this.getRegisteredApplications()) { for (InstanceInfo info : app.getInstancesAsIsFromEureka()) { AtomicInteger instanceCount = instanceCountMap.get(info.getStatus().name()); if (instanceCount == null) { instanceCount = new AtomicInteger(0); instanceCountMap.put(info.getStatus().name(), instanceCount); } instanceCount.incrementAndGet(); } } } /** * Gets the reconciliation hashcode. The hashcode is used to determine whether the applications list * has changed since the last time it was acquired. * @param instanceCountMap the instance count map to use for generating the hash * @return the hash code for this instance */ public static String getReconcileHashCode(TreeMap instanceCountMap) { String reconcileHashCode = ""; for (Map.Entry mapEntry : instanceCountMap.entrySet()) { reconcileHashCode = reconcileHashCode + mapEntry.getKey() + STATUS_DELIMITER + mapEntry.getValue().get() + STATUS_DELIMITER; } return reconcileHashCode; } /** * Gets the exact difference between this applications instance and another * one. * * @param apps * the applications for which to compare this one. * @return a map containing the differences between the two. */ public Map> getReconcileMapDiff(Applications apps) { Map> diffMap = new TreeMap>(); Set allInstanceAppInstanceIds = new HashSet(); for (Application otherApp : apps.getRegisteredApplications()) { Application thisApp = this.getRegisteredApplications(otherApp.getName()); if (thisApp == null) { logger.warn("Application not found in local cache : {}", otherApp.getName()); continue; } for (InstanceInfo instanceInfo : thisApp.getInstancesAsIsFromEureka()) { allInstanceAppInstanceIds.add(new Pair(thisApp.getName(), instanceInfo.getId())); } for (InstanceInfo otherInstanceInfo : otherApp.getInstancesAsIsFromEureka()) { InstanceInfo thisInstanceInfo = thisApp.getByInstanceId(otherInstanceInfo.getId()); if (thisInstanceInfo == null) { List diffList = diffMap.get(ActionType.DELETED.name()); if (diffList == null) { diffList = new ArrayList(); diffMap.put(ActionType.DELETED.name(), diffList); } diffList.add(otherInstanceInfo.getId()); } else if (!thisInstanceInfo.getStatus().name() .equalsIgnoreCase(otherInstanceInfo.getStatus().name())) { List diffList = diffMap.get(ActionType.MODIFIED.name()); if (diffList == null) { diffList = new ArrayList(); diffMap.put(ActionType.MODIFIED.name(), diffList); } diffList.add(thisInstanceInfo.getId() + APP_INSTANCEID_DELIMITER + thisInstanceInfo.getStatus().name() + APP_INSTANCEID_DELIMITER + otherInstanceInfo.getStatus().name()); } allInstanceAppInstanceIds.remove(new Pair(otherApp.getName(), otherInstanceInfo.getId())); } } for (Pair pair : allInstanceAppInstanceIds) { Application app = new Application(pair.getItem1()); InstanceInfo thisInstanceInfo = app.getByInstanceId(pair.getItem2()); if (thisInstanceInfo != null) { List diffList = diffMap.get(ActionType.ADDED.name()); if (diffList == null) { diffList = new ArrayList(); diffMap.put(ActionType.ADDED.name(), diffList); } diffList.add(thisInstanceInfo.getId()); } } return diffMap; } private static final class Pair { private final String item1; private final String item2; public Pair(String item1, String item2) { super(); this.item1 = item1; this.item2 = item2; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((item1 == null) ? 0 : item1.hashCode()); result = prime * result + ((item2 == null) ? 0 : item2.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Pair other = (Pair) obj; if (item1 == null) { if (other.item1 != null) { return false; } } else if (!item1.equals(other.item1)) { return false; } if (item2 == null) { if (other.item2 != null) { return false; } } else if (!item2.equals(other.item2)) { return false; } return true; } public String getItem1() { return item1; } public String getItem2() { return item2; } } /** * Shuffles the provided instances so that they will not always be returned in the same order. * @param filterUpInstances whether to return only UP instances */ public void shuffleInstances(boolean filterUpInstances) { shuffleInstances(filterUpInstances, false, null, null, null); } /** * Shuffles a whole region so that the instances will not always be returned in the same order. * @param remoteRegionsRegistry the map of remote region names to their registries * @param clientConfig the {@link EurekaClientConfig}, whose settings will be used to determine whether to * filter to only UP instances * @param instanceRegionChecker the instance region checker */ public void shuffleAndIndexInstances(Map remoteRegionsRegistry, EurekaClientConfig clientConfig, InstanceRegionChecker instanceRegionChecker) { shuffleInstances(clientConfig.shouldFilterOnlyUpInstances(), true, remoteRegionsRegistry, clientConfig, instanceRegionChecker); } private void shuffleInstances(boolean filterUpInstances, boolean indexByRemoteRegions, @Nullable Map remoteRegionsRegistry, @Nullable EurekaClientConfig clientConfig, @Nullable InstanceRegionChecker instanceRegionChecker) { this.virtualHostNameAppMap.clear(); this.secureVirtualHostNameAppMap.clear(); for (Application application : appNameApplicationMap.values()) { if (indexByRemoteRegions) { application.shuffleAndStoreInstances(remoteRegionsRegistry, clientConfig, instanceRegionChecker); } else { application.shuffleAndStoreInstances(filterUpInstances); } this.addInstancesToVIPMaps(application); } shuffleAndFilterInstances(this.virtualHostNameAppMap, this.shuffleVirtualHostNameMap, virtualHostNameIndexMap, filterUpInstances); shuffleAndFilterInstances(this.secureVirtualHostNameAppMap, this.shuffledSecureVirtualHostNameMap, secureVirtualHostNameIndexMap, filterUpInstances); } /** * Gets the next round-robin index for the given virtual host name. This * index is reset after every registry fetch cycle. * * @param virtualHostname * the virtual host name. * @param secure * indicates whether it is a secure request or a non-secure * request. * @return AtomicLong value representing the next round-robin index. */ public AtomicLong getNextIndex(String virtualHostname, boolean secure) { if (secure) { return this.secureVirtualHostNameIndexMap.get(virtualHostname); } else { return this.virtualHostNameIndexMap.get(virtualHostname); } } /** * Shuffle the instances and filter for only {@link InstanceStatus#UP} if * required. * */ private void shuffleAndFilterInstances( Map> srcMap, Map>> destMap, Map vipIndexMap, boolean filterUpInstances) { for (Map.Entry> entries : srcMap.entrySet()) { AbstractQueue instanceInfoQueue = entries.getValue(); List l = new ArrayList(instanceInfoQueue); if (filterUpInstances) { Iterator it = l.iterator(); while (it.hasNext()) { InstanceInfo instanceInfo = it.next(); if (!InstanceStatus.UP.equals(instanceInfo.getStatus())) { it.remove(); } } } Collections.shuffle(l); AtomicReference> instanceInfoList = destMap.get(entries.getKey()); if (instanceInfoList == null) { instanceInfoList = new AtomicReference>(l); destMap.put(entries.getKey(), instanceInfoList); } instanceInfoList.set(l); vipIndexMap.put(entries.getKey(), new AtomicLong(0)); } // finally remove all vips that are completed deleted (i.e. missing) from the srcSet Set srcVips = srcMap.keySet(); Set destVips = destMap.keySet(); destVips.retainAll(srcVips); } /** * Add the instance to the given map based if the vip address matches with * that of the instance. Note that an instance can be mapped to multiple vip * addresses. * */ private void addInstanceToMap(InstanceInfo info, String vipAddresses, Map> vipMap) { if (vipAddresses != null) { String[] vipAddressArray = vipAddresses.split(","); for (String vipAddress : vipAddressArray) { String vipName = vipAddress.toUpperCase(Locale.ROOT); AbstractQueue instanceInfoList = vipMap.get(vipName); if (instanceInfoList == null) { instanceInfoList = new ConcurrentLinkedQueue(); vipMap.put(vipName, instanceInfoList); } instanceInfoList.add(info); } } } /** * Adds the instances to the internal vip address map. * @param app - the applications for which the instances need to be added. */ private void addInstancesToVIPMaps(Application app) { // Check and add the instances to the their respective virtual host name // mappings for (InstanceInfo info : app.getInstances()) { String vipAddresses = info.getVIPAddress(); String secureVipAddresses = info.getSecureVipAddress(); if ((vipAddresses == null) && (secureVipAddresses == null)) { continue; } addInstanceToMap(info, vipAddresses, virtualHostNameAppMap); addInstanceToMap(info, secureVipAddresses, secureVirtualHostNameAppMap); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy