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: 0.40.13
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.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
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 java.util.stream.Collectors;

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.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;

/**
 * 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 class VipIndexSupport { final AbstractQueue instances = new ConcurrentLinkedQueue<>(); final AtomicLong roundRobinIndex = new AtomicLong(0); final AtomicReference> vipList = new AtomicReference>(Collections.emptyList()); public AtomicLong getRoundRobinIndex() { return roundRobinIndex; } public AtomicReference> getVipList() { return vipList; } } private static final String STATUS_DELIMITER = "_"; private String appsHashCode; private Long versionDelta; @XStreamImplicit private final AbstractQueue applications; private final Map appNameApplicationMap; private final Map virtualHostNameAppMap; private final Map secureVirtualHostNameAppMap; /** * Create a new, empty Eureka application list. */ public Applications() { this(null, -1L, Collections.emptyList()); } /** * 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(); this.appNameApplicationMap = new ConcurrentHashMap(); this.virtualHostNameAppMap = new ConcurrentHashMap(); this.secureVirtualHostNameAppMap = new ConcurrentHashMap(); this.appsHashCode = appsHashCode; this.versionDelta = versionDelta; for (Application app : registeredApplications) { this.addApplication(app); } } /** * 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, this.virtualHostNameAppMap, this.secureVirtualHostNameAppMap); 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() { return new ArrayList(this.applications); } /** * 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) { return Optional.ofNullable(this.virtualHostNameAppMap.get(virtualHostName.toUpperCase(Locale.ROOT))) .map(VipIndexSupport::getVipList) .map(AtomicReference::get) .orElseGet(Collections::emptyList); } /** * 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) { return Optional.ofNullable(this.secureVirtualHostNameAppMap.get(secureVirtualHostName.toUpperCase(Locale.ROOT))) .map(VipIndexSupport::getVipList) .map(AtomicReference::get) .orElseGet(Collections::emptyList); } /** * @return a weakly consistent size of the number of instances in all the * applications */ public int size() { return applications.stream().mapToInt(Application::size).sum(); } @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(Map instanceCountMap) { for (Application app : this.getRegisteredApplications()) { for (InstanceInfo info : app.getInstancesAsIsFromEureka()) { AtomicInteger instanceCount = instanceCountMap.computeIfAbsent(info.getStatus().name(), k -> new AtomicInteger(0)); 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(Map instanceCountMap) { StringBuilder reconcileHashCode = new StringBuilder(75); for (Map.Entry mapEntry : instanceCountMap.entrySet()) { reconcileHashCode.append(mapEntry.getKey()).append(STATUS_DELIMITER).append(mapEntry.getValue().get()) .append(STATUS_DELIMITER); } return reconcileHashCode.toString(); } /** * 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) { Map secureVirtualHostNameAppMap = new HashMap<>(); Map virtualHostNameAppMap = new HashMap<>(); for (Application application : appNameApplicationMap.values()) { if (indexByRemoteRegions) { application.shuffleAndStoreInstances(remoteRegionsRegistry, clientConfig, instanceRegionChecker); } else { application.shuffleAndStoreInstances(filterUpInstances); } this.addInstancesToVIPMaps(application, virtualHostNameAppMap, secureVirtualHostNameAppMap); } shuffleAndFilterInstances(virtualHostNameAppMap, filterUpInstances); shuffleAndFilterInstances(secureVirtualHostNameAppMap, filterUpInstances); this.virtualHostNameAppMap.putAll(virtualHostNameAppMap); this.virtualHostNameAppMap.keySet().retainAll(virtualHostNameAppMap.keySet()); this.secureVirtualHostNameAppMap.putAll(secureVirtualHostNameAppMap); this.secureVirtualHostNameAppMap.keySet().retainAll(secureVirtualHostNameAppMap.keySet()); } /** * 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) { Map index = (secure) ? secureVirtualHostNameAppMap : virtualHostNameAppMap; return Optional.ofNullable(index.get(virtualHostname.toUpperCase(Locale.ROOT))) .map(VipIndexSupport::getRoundRobinIndex) .orElse(null); } /** * Shuffle the instances and filter for only {@link InstanceStatus#UP} if * required. * */ private void shuffleAndFilterInstances(Map srcMap, boolean filterUpInstances) { Random shuffleRandom = new Random(); for (Map.Entry entries : srcMap.entrySet()) { VipIndexSupport vipIndexSupport = entries.getValue(); AbstractQueue vipInstances = vipIndexSupport.instances; final List filteredInstances; if (filterUpInstances) { filteredInstances = vipInstances.stream().filter(ii -> ii.getStatus() == InstanceStatus.UP) .collect(Collectors.toCollection(() -> new ArrayList<>(vipInstances.size()))); } else { filteredInstances = new ArrayList(vipInstances); } Collections.shuffle(filteredInstances, shuffleRandom); vipIndexSupport.vipList.set(filteredInstances); vipIndexSupport.roundRobinIndex.set(0); } } /** * 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.toUpperCase(Locale.ROOT).split(","); for (String vipAddress : vipAddressArray) { VipIndexSupport vis = vipMap.computeIfAbsent(vipAddress, k -> new VipIndexSupport()); vis.instances.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, Map virtualHostNameAppMap, Map secureVirtualHostNameAppMap) { // Check and add the instances to the their respective virtual host name // mappings for (InstanceInfo info : app.getInstances()) { String vipAddresses = info.getVIPAddress(); if (vipAddresses != null) { addInstanceToMap(info, vipAddresses, virtualHostNameAppMap); } String secureVipAddresses = info.getSecureVipAddress(); if (secureVipAddresses != null) { addInstanceToMap(info, secureVipAddresses, secureVirtualHostNameAppMap); } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy