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

com.netflix.eureka.registry.RemoteRegionRegistry Maven / Gradle / Ivy

There is a newer version: 2.0.4
Show newest version
/*
 * Copyright 2012 Netflix, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.netflix.eureka.registry;

import com.netflix.discovery.util.SpectatorUtil;
import com.netflix.eureka.transport.EurekaServerHttpClientFactory;
import jakarta.inject.Inject;

import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
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 com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.ActionType;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.TimedSupervisorTask;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.shared.LookupService;
import com.netflix.discovery.shared.resolver.ClusterResolver;
import com.netflix.discovery.shared.resolver.StaticClusterResolver;
import com.netflix.discovery.shared.transport.EurekaHttpClient;
import com.netflix.discovery.shared.transport.EurekaHttpResponse;
import com.netflix.eureka.EurekaServerConfig;
import com.netflix.eureka.resources.ServerCodecs;
//import com.sun.jersey.api.client.ClientResponse;
//import com.sun.jersey.api.client.filter.GZIPContentEncodingFilter;
//import com.sun.jersey.client.apache4.ApacheHttpClient4;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static com.netflix.eureka.Names.METRIC_REGISTRY_PREFIX;

/**
 * Handles all registry operations that needs to be done on a eureka service running in an other region.
 *
 * The primary operations include fetching registry information from remote region and fetching delta information
 * on a periodic basis.
 *
 * TODO: a lot of the networking code in this class can be replaced by newer code in
 * {@link com.netflix.discovery.DiscoveryClient}
 *
 * @author Karthik Ranganathan
 *
 */
public class RemoteRegionRegistry implements LookupService {
    private static final Logger logger = LoggerFactory.getLogger(RemoteRegionRegistry.class);

//    private final ApacheHttpClient4 discoveryApacheClient;
//    private final EurekaJerseyClient discoveryJerseyClient;
    private final com.netflix.spectator.api.Timer fetchRegistryTimer;
    private final URL remoteRegionURL;

    private final ScheduledExecutorService scheduler;
    // monotonically increasing generation counter to ensure stale threads do not reset registry to an older version
    private final AtomicLong fetchRegistryGeneration = new AtomicLong(0);
    private final Lock fetchRegistryUpdateLock = new ReentrantLock();

    private final AtomicReference applications = new AtomicReference<>(new Applications());
    private final AtomicReference applicationsDelta = new AtomicReference<>(new Applications());
    private final EurekaServerConfig serverConfig;
    private volatile boolean readyForServingData;
    private final EurekaHttpClient eurekaHttpClient;
    private long timeOfLastSuccessfulRemoteFetch = System.currentTimeMillis();
    private final AtomicLong deltaSuccesses = SpectatorUtil.monitoredLong(METRIC_REGISTRY_PREFIX + "remoteDeltaSuccesses",
        RemoteRegionRegistry.class);
    private final AtomicLong deltaMismatches = SpectatorUtil.monitoredLong(METRIC_REGISTRY_PREFIX + "remoteDeltaMismatches",
        RemoteRegionRegistry.class);

    @Inject
    public RemoteRegionRegistry(EurekaServerConfig serverConfig,
                                EurekaClientConfig clientConfig,
                                ServerCodecs serverCodecs,
                                EurekaServerHttpClientFactory eurekaServerHttpClientFactory,
                                String regionName,
                                URL remoteRegionURL) {
        this.serverConfig = serverConfig;
        this.remoteRegionURL = remoteRegionURL;
        this.fetchRegistryTimer = SpectatorUtil.timer(this.remoteRegionURL.toString() + "_FetchRegistry", RemoteRegionRegistry.class);

        /* FIXME: 2.0
        EurekaJerseyClientBuilder clientBuilder = new EurekaJerseyClientBuilder()
                .withUserAgent("Java-EurekaClient-RemoteRegion")
                .withEncoderWrapper(serverCodecs.getFullJsonCodec())
                .withDecoderWrapper(serverCodecs.getFullJsonCodec())
                .withConnectionTimeout(serverConfig.getRemoteRegionConnectTimeoutMs())
                .withReadTimeout(serverConfig.getRemoteRegionReadTimeoutMs())
                .withMaxConnectionsPerHost(serverConfig.getRemoteRegionTotalConnectionsPerHost())
                .withMaxTotalConnections(serverConfig.getRemoteRegionTotalConnections())
                .withConnectionIdleTimeout(serverConfig.getRemoteRegionConnectionIdleTimeoutSeconds());

        if (remoteRegionURL.getProtocol().equals("http")) {
            clientBuilder.withClientName("Discovery-RemoteRegionClient-" + regionName);
        } else if ("true".equals(System.getProperty("com.netflix.eureka.shouldSSLConnectionsUseSystemSocketFactory"))) {
            clientBuilder.withClientName("Discovery-RemoteRegionSystemSecureClient-" + regionName)
                    .withSystemSSLConfiguration();
        } else {
            clientBuilder.withClientName("Discovery-RemoteRegionSecureClient-" + regionName)
                    .withTrustStoreFile(
                            serverConfig.getRemoteRegionTrustStore(),
                            serverConfig.getRemoteRegionTrustStorePassword()
                    );
        }
        discoveryJerseyClient = clientBuilder.build();
        discoveryApacheClient = null; // discoveryJerseyClient.getClient();

        // should we enable GZip decoding of responses based on Response Headers?
        if (serverConfig.shouldGZipContentFromRemoteRegion()) {
            // compressed only if there exists a 'Content-Encoding' header whose value is "gzip"
            discoveryApacheClient.addFilter(new GZIPContentEncodingFilter(false));
        }

        /*String ip = null;
        try {
            ip = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            logger.warn("Cannot find localhost ip", e);
        }
        EurekaServerIdentity identity = new EurekaServerIdentity(ip);
        discoveryApacheClient.addFilter(new EurekaIdentityHeaderFilter(identity));
        */

        // Configure new transport layer (candidate for injecting in the future)
        EurekaHttpClient newEurekaHttpClient = null;
        try {
            ClusterResolver clusterResolver = StaticClusterResolver.fromURL(regionName, remoteRegionURL);
            newEurekaHttpClient = eurekaServerHttpClientFactory.createRemoteRegionClient(
                    serverConfig, clientConfig.getTransportConfig(), serverCodecs, clusterResolver);
        } catch (Exception e) {
            // logger.warn("Transport initialization failure", e);
            // FIXME: 2.0 go back to warn above when apache client reinstated
            throw new RuntimeException("Transport initialization failure", e);
        }
        this.eurekaHttpClient = newEurekaHttpClient;

        try {
            if (fetchRegistry()) {
                this.readyForServingData = true;
            } else {
                logger.warn("Failed to fetch remote registry. This means this eureka server is not ready for serving "
                        + "traffic.");
            }
        } catch (Throwable e) {
            logger.error("Problem fetching registry information :", e);
        }

        // remote region fetch
        Runnable remoteRegionFetchTask = new Runnable() {
            @Override
            public void run() {
                try {
                    if (fetchRegistry()) {
                        readyForServingData = true;
                    } else {
                        logger.warn("Failed to fetch remote registry. This means this eureka server is not "
                                + "ready for serving traffic.");
                    }
                } catch (Throwable e) {
                    logger.error(
                            "Error getting from remote registry :", e);
                }
            }
        };

        ThreadPoolExecutor remoteRegionFetchExecutor = new ThreadPoolExecutor(
                1, serverConfig.getRemoteRegionFetchThreadPoolSize(), 0, TimeUnit.SECONDS, new SynchronousQueue());  // use direct handoff

        scheduler = Executors.newScheduledThreadPool(1,
            new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread thread = new Thread(r, "Eureka-RemoteRegionCacheRefresher_" + regionName + "-%d");
                    thread.setDaemon(true);
                    return thread;
                }
        });

        scheduler.schedule(
                new TimedSupervisorTask(
                        "RemoteRegionFetch_" + regionName,
                        scheduler,
                        remoteRegionFetchExecutor,
                        serverConfig.getRemoteRegionRegistryFetchInterval(),
                        TimeUnit.SECONDS,
                        5,  // exponential backoff bound
                        remoteRegionFetchTask
                ),
                serverConfig.getRemoteRegionRegistryFetchInterval(), TimeUnit.SECONDS);

        SpectatorUtil.monitoredValue(METRIC_REGISTRY_PREFIX + "secondsSinceLastSuccessfulRemoteFetch",
            this, RemoteRegionRegistry::getTimeOfLastSuccessfulRemoteFetch);
    }

    /**
     * Check if this registry is ready for serving data.
     * @return true if ready, false otherwise.
     */
    public boolean isReadyForServingData() {
        return readyForServingData;
    }

    /**
     * Fetch the registry information from the remote region.
     * @return true, if the fetch was successful, false otherwise.
     */
    private boolean fetchRegistry() {
        boolean success;
        final long time = SpectatorUtil.time(fetchRegistryTimer);

        try {
            // If the delta is disabled or if it is the first time, get all applications
            if (serverConfig.shouldDisableDeltaForRemoteRegions()
                    || (getApplications() == null)
                    || (getApplications().getRegisteredApplications().size() == 0)) {
                logger.info("Disable delta property : {}", serverConfig.shouldDisableDeltaForRemoteRegions());
                logger.info("Application is null : {}", getApplications() == null);
                logger.info("Registered Applications size is zero : {}", getApplications().getRegisteredApplications().isEmpty());
                success = storeFullRegistry();
            } else {
                success = fetchAndStoreDelta();
            }
            logTotalInstances();
        } catch (Throwable e) {
            logger.error("Unable to fetch registry information from the remote registry {}", this.remoteRegionURL, e);
            return false;
        } finally {
            SpectatorUtil.record(fetchRegistryTimer, time);
        }

        if (success) {
            timeOfLastSuccessfulRemoteFetch = System.currentTimeMillis();
        }

        return success;
    }

    private boolean fetchAndStoreDelta() throws Throwable {
        long currGeneration = fetchRegistryGeneration.get();
        Applications delta = fetchRemoteRegistry(true);

        if (delta == null) {
            logger.error("The delta is null for some reason. Not storing this information");
        } else if (fetchRegistryGeneration.compareAndSet(currGeneration, currGeneration + 1)) {
            this.applicationsDelta.set(delta);
        } else {
            delta = null;  // set the delta to null so we don't use it
            logger.warn("Not updating delta as another thread is updating it already");
        }

        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.");
            return storeFullRegistry();
        } else {
            String reconcileHashCode = "";
            if (fetchRegistryUpdateLock.tryLock()) {
                try {
                    updateDelta(delta);
                    reconcileHashCode = getApplications().getReconcileHashCode();
                } finally {
                    fetchRegistryUpdateLock.unlock();
                }
            } else {
                logger.warn("Cannot acquire update lock, aborting updateDelta operation of fetchAndStoreDelta");
            }

            // There is a diff in number of instances for some reason
            if (!reconcileHashCode.equals(delta.getAppsHashCode())) {
                deltaMismatches.incrementAndGet();
                return reconcileAndLogDifference(delta, reconcileHashCode);
            } else {
                deltaSuccesses.incrementAndGet();
            }
        }

        return delta != null;
    }

    /**
     * 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()) {
                ++deltaCount;
                if (ActionType.ADDED.equals(instance.getActionType())) {
                    Application existingApp = getApplications()
                            .getRegisteredApplications(instance.getAppName());
                    if (existingApp == null) {
                        getApplications().addApplication(app);
                    }
                    logger.debug("Added instance {} to the existing apps ",
                            instance.getId());
                    getApplications().getRegisteredApplications(
                            instance.getAppName()).addInstance(instance);
                } else if (ActionType.MODIFIED.equals(instance.getActionType())) {
                    Application existingApp = getApplications()
                            .getRegisteredApplications(instance.getAppName());
                    if (existingApp == null) {
                        getApplications().addApplication(app);
                    }
                    logger.debug("Modified instance {} to the existing apps ",
                            instance.getId());

                    getApplications().getRegisteredApplications(
                            instance.getAppName()).addInstance(instance);

                } else if (ActionType.DELETED.equals(instance.getActionType())) {
                    Application existingApp = getApplications()
                            .getRegisteredApplications(instance.getAppName());
                    if (existingApp == null) {
                        getApplications().addApplication(app);
                    }
                    logger.debug("Deleted instance {} to the existing apps ",
                            instance.getId());
                    getApplications().getRegisteredApplications(
                            instance.getAppName()).removeInstance(instance);
                }
            }
        }
        logger.debug(
                "The total number of instances fetched by the delta processor : {}",
                deltaCount);

    }

    /**
     * Close HTTP response object and its respective resources.
     *
     * @param response
     *            the HttpResponse object.
     */
    private void closeResponse(/*ClientResponse*/ Object response) {
        if (response != null) {
            try {
                // response.close();
            } catch (Throwable th) {
                logger.error("Cannot release response resource :", th);
            }
        }
    }

    /**
     * Gets the full registry information from the eureka server and stores it
     * locally.
     *
     * @return the full registry information.
     */
    public boolean storeFullRegistry() {
        long currentGeneration = fetchRegistryGeneration.get();
        Applications apps = fetchRemoteRegistry(false);
        if (apps == null) {
            logger.error("The application is null for some reason. Not storing this information");
        } else if (fetchRegistryGeneration.compareAndSet(currentGeneration, currentGeneration + 1)) {
            applications.set(apps);
            applicationsDelta.set(apps);
            logger.info("Successfully updated registry with the latest content");
            return true;
        } else {
            logger.warn("Not updating applications as another thread is updating it already");
        }
        return false;
    }

    /**
     * Fetch registry information from the remote region.
     * @param delta - true, if the fetch needs to get deltas, false otherwise
     * @return - response which has information about the data.
     */
    protected Applications fetchRemoteRegistry(boolean delta) {
        logger.info("Getting instance registry info from the eureka server : {} , delta : {}", this.remoteRegionURL, delta);

//        if (shouldUseExperimentalTransport()) {
            try {
                EurekaHttpResponse httpResponse = delta ? eurekaHttpClient.getDelta() : eurekaHttpClient.getApplications();
                int httpStatus = httpResponse.getStatusCode();
                if (httpStatus >= 200 && httpStatus < 300) {
                    logger.debug("Got the data successfully : {}", httpStatus);
                    return httpResponse.getEntity();
                }
                logger.warn("Cannot get the data from {} : {}", this.remoteRegionURL, httpStatus);
            } catch (Throwable t) {
                logger.error("Can't get a response from {}", this.remoteRegionURL, t);
            }
//        } else {
            /*ClientResponse response = null;
            try {
                String urlPath = delta ? "apps/delta" : "apps/";

                response = discoveryApacheClient.resource(this.remoteRegionURL + urlPath)
                        .accept(MediaType.APPLICATION_JSON_TYPE)
                        .get(ClientResponse.class);
                int httpStatus = response.getStatus();
                if (httpStatus >= 200 && httpStatus < 300) {
                    logger.debug("Got the data successfully : {}", httpStatus);
                    return response.getEntity(Applications.class);
                }
                logger.warn("Cannot get the data from {} : {}", this.remoteRegionURL, httpStatus);
            } catch (Throwable t) {
                logger.error("Can't get a response from {}", this.remoteRegionURL, t);
            } finally {
                closeResponse(response);
            }*/
//        }
        return null;
    }

        /**
         * Reconciles the delta information fetched to see if the hashcodes match.
         *
         * @param delta - the delta information fetched previously for reconciliation.
         * @param reconcileHashCode - the hashcode for comparison.
         * @return - response
         * @throws Throwable
         */
    private boolean reconcileAndLogDifference(Applications delta, String reconcileHashCode) throws Throwable {
        logger.warn("The Reconcile hashcodes do not match, client : {}, server : {}. Getting the full registry",
                reconcileHashCode, delta.getAppsHashCode());

        long currentGeneration = fetchRegistryGeneration.get();

        Applications apps = this.fetchRemoteRegistry(false);
        if (apps == null) {
            logger.error("The application is null for some reason. Not storing this information");
            return false;
        }

        if (fetchRegistryGeneration.compareAndSet(currentGeneration, currentGeneration + 1)) {
            applications.set(apps);
            applicationsDelta.set(apps);
            logger.warn("The Reconcile hashcodes after complete sync up, client : {}, server : {}.",
                    getApplications().getReconcileHashCode(),
                    delta.getAppsHashCode());
            return true;
        }else {
            logger.warn("Not setting the applications map as another thread has advanced the update generation");
            return true;  // still return true
        }
    }

    /**
     * Logs the total number of non-filtered instances stored locally.
     */
    private void logTotalInstances() {
        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);
    }

    @Override
    public Applications getApplications() {
        return applications.get();
    }

    @Override
    public InstanceInfo getNextServerFromEureka(String arg0, boolean arg1) {
        return null;
    }

    @Override
    public Application getApplication(String appName) {
        return this.applications.get().getRegisteredApplications(appName);
    }

    @Override
    public List getInstancesById(String id) {
        List list = new ArrayList<>(1);

        for (Application app : applications.get().getRegisteredApplications()) {
            InstanceInfo info = app.getByInstanceId(id);
            if (info != null) {
                list.add(info);
                return list;
            }
        }
        return Collections.emptyList();
    }

    public Applications getApplicationDeltas() {
        return this.applicationsDelta.get();
    }

    /*private boolean shouldUseExperimentalTransport() {
        if (eurekaHttpClient == null) {
            return false;
        }
        String enabled = serverConfig.getExperimental("transport.enabled");
        return enabled != null && "true".equalsIgnoreCase(enabled);
    }*/

    public long getTimeOfLastSuccessfulRemoteFetch() {
        return (System.currentTimeMillis() - timeOfLastSuccessfulRemoteFetch) / 1000;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy