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

com.netflix.eureka.cluster.PeerEurekaNode 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.cluster;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.config.ConfigurationManager;
import com.netflix.discovery.provider.Serializer;
import com.netflix.discovery.shared.EurekaJerseyClient;
import com.netflix.discovery.shared.EurekaJerseyClient.JerseyClient;
import com.netflix.eureka.CurrentRequestVersion;
import com.netflix.eureka.EurekaServerConfig;
import com.netflix.eureka.EurekaServerConfigurationManager;
import com.netflix.eureka.PeerAwareInstanceRegistry;
import com.netflix.eureka.PeerAwareInstanceRegistry.Action;
import com.netflix.eureka.Version;
import com.netflix.eureka.resources.ASGResource.ASGStatus;
import com.netflix.logging.messaging.BatcherFactory;
import com.netflix.logging.messaging.MessageBatcher;
import com.netflix.logging.messaging.MessageProcessor;
import com.netflix.servo.monitor.DynamicCounter;
import com.netflix.servo.monitor.Monitors;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.client.apache4.ApacheHttpClient4;
import com.thoughtworks.xstream.annotations.XStreamAlias;

/**
 * The PeerEurekaNode represents a peer node to which information
 * should be shared from this node.
 * 
 * 

* This class handles replicating all update operations like * Register,Renew,Cancel,Expiration and Status Changes to the eureka * node it represents. *

* * @author Karthik Ranganathan, Greg Kim * */ public class PeerEurekaNode { private static final Logger logger = LoggerFactory .getLogger(PeerEurekaNode.class); private static final int RETRY_SLEEP_TIME_MS = 100; public static final String HEADER_REPLICATION = "x-netflix-discovery-replication"; private static final EurekaServerConfig config = EurekaServerConfigurationManager .getInstance().getConfiguration(); private final String serviceUrl; private final String name; private volatile JerseyClient jerseyClient; private volatile ApacheHttpClient4 jerseyApacheClient; private MessageBatcher heartBeatBatcher; private MessageBatcher statusBatcher; private MessageBatcher registerBatcher; private MessageBatcher cancelBatcher; private MessageBatcher asgStatusBatcher; public PeerEurekaNode(String serviceUrl) { this.serviceUrl = serviceUrl.intern(); this.name = getClass().getSimpleName() + ": " + serviceUrl + "apps/: "; this.heartBeatBatcher = getBatcher(serviceUrl, Action.Heartbeat.name()); this.statusBatcher = getBatcher(serviceUrl, Action.StatusUpdate.name()); this.asgStatusBatcher = getBatcher(serviceUrl, "ASG_" + Action.StatusUpdate.name()); this.registerBatcher = getBatcher(serviceUrl, Action.Register.name()); this.cancelBatcher = getBatcher(serviceUrl, Action.Cancel.name()); synchronized (this.serviceUrl) { if (jerseyApacheClient == null) { try { String hostname; try { hostname = new URL(serviceUrl).getHost(); } catch (MalformedURLException e) { hostname = serviceUrl; } String jerseyClientName = "Discovery-PeerNodeClient-" + hostname; jerseyClient = EurekaJerseyClient.createJerseyClient(jerseyClientName, config.getPeerNodeConnectTimeoutMs(), config.getPeerNodeReadTimeoutMs(), config.getPeerNodeTotalConnections(), config.getPeerNodeTotalConnectionsPerHost(), config.getPeerNodeConnectionIdleTimeoutSeconds()); jerseyApacheClient = jerseyClient.getClient(); } catch (Throwable e) { throw new RuntimeException( "Cannot Create new Replica Node :" + name); } } } try { String serviceUrlHost = new URL(serviceUrl).getHost(); Monitors.registerObject(serviceUrlHost, this); } catch (Throwable e) { logger.error("Cannot register monitors for Peer eureka node :" + serviceUrl, e); } } /** * Sends the registration information of {@link InstanceInfo} receiving by * this node to the peer node represented by this class. * * @param info * the instance information {@link InstanceInfo} of any instance * that is send to this instance. * @throws Exception */ public void register(final InstanceInfo info) throws Exception { ReplicationTask replicationTask = new ReplicationTask(info .getAppName(), info.getId(), Action.Register) { public int execute() { CurrentRequestVersion.set(Version.V2); String urlPath = "apps/" + info.getAppName(); ClientResponse response = null; try { response = jerseyApacheClient.resource(serviceUrl) .path(urlPath).header(HEADER_REPLICATION, "true") .type(MediaType.APPLICATION_JSON_TYPE) .post(ClientResponse.class, info); return response.getStatus(); } finally { if (response != null) { response.close(); } } } @Override public boolean shouldReplicateInstanceInfo () { return true; } }; // Set the actual object to be registered in the task for use in batch operations replicationTask.setInstanceInfo(info); boolean success = registerBatcher.process(replicationTask); if (!success) { logger.error("Cannot find space in the replication pool for " + this.serviceUrl + ". Check the network connectivity or the traffic"); } } /** * Send the cancellation information of an instance to the node represented * by this class. * * @param appName * the application name of the instance. * @param id * the unique identifier of the instance. * @throws Exception */ public void cancel(final String appName, final String id) throws Exception { boolean success = cancelBatcher.process(new ReplicationTask(appName, id, Action.Cancel) { @Override public int execute() { ClientResponse response = null; try { String urlPath = "apps/" + appName + "/" + id; response = jerseyApacheClient.resource(serviceUrl) .path(urlPath).header(HEADER_REPLICATION, "true") .delete(ClientResponse.class); return response.getStatus(); } finally { if (response != null) { response.close(); } } } @Override public void handleFailure(int statusCode) throws Throwable { super.handleFailure(statusCode); if (statusCode == 404) { logger.warn(name + appName + "/" + id + " : delete: missing entry."); } } }); if (!success) { logger.error("Cannot find space in the replication pool for " + this.serviceUrl + ". Check the network connectivity or the traffic"); } } /** * Send the heartbeat information of an instance to the node represented by * this class. If the instance does not exist the node, the instance * registration information is sent again to the peer node. * * @param appName * the application name of the instance. * @param id * the unique identifier of the instance. * @param info * the instance info {@link InstanceInfo} of the instance. * @param overriddenStatus * the overridden status information if any of the instance. * @return true, if the instance exists in the peer node, false otherwise. * @throws Throwable */ public void heartbeat(final String appName, final String id, final InstanceInfo info, final InstanceStatus overriddenStatus, boolean primeConnection) throws Throwable { if (primeConnection) { sendHeartBeat(appName, id, info, overriddenStatus, null); return; } ReplicationTask replicationTask = new ReplicationTask(appName, id, Action.Heartbeat) { @Override public int execute() throws Throwable { return sendHeartBeat(appName, id, info, overriddenStatus, this); } @Override public void handleFailure(int statusCode) throws Throwable { super.handleFailure(statusCode); if (statusCode == 404) { logger.warn(name + appName + "/" + id + " : heartbeat: missing entry."); if (info != null) { logger.warn( "Cannot find instance id {} and hence replicating the instance with status {}", info.getId(), info.getStatus().toString()); register(info); } } else if (config.shouldSyncWhenTimestampDiffers() && this.getPeerInstanceInfo() != null) { syncInstancesIfTimestampDiffers(id, info, this.getPeerInstanceInfo()); } } }; replicationTask.setInstanceInfo(info); replicationTask.setOverriddenStatus(overriddenStatus); boolean success = heartBeatBatcher.process(replicationTask); if (!success) { logger.error("Cannot find space in the replication pool for " + this.serviceUrl + ". Check the network connectivity or the traffic"); } } /** * Send the status information of of the ASG represented by the instance. * *

* ASG (Autoscaling group) names are available for instances in AWS and the * ASG information is used for determining if the instance should be * registered as {@link InstanceStatus#DOWN} or {@link InstanceStatus#UP}. * * @param asgName * the asg name if any of this instance. * @param newStatus * the new status of the ASG. */ public void statusUpdate(final String asgName, final ASGStatus newStatus) { boolean success = asgStatusBatcher.process(new ReplicationTask(asgName, asgName, Action.StatusUpdate) { public int execute() { ClientResponse response = null; try { String urlPath = "asg/" + asgName + "/status"; response = jerseyApacheClient.resource(serviceUrl) .path(urlPath) .queryParam("value", newStatus.name()) .header(HEADER_REPLICATION, "true") .put(ClientResponse.class); return response.getStatus(); } finally { if (response != null) { response.close(); } } } @Override public boolean isBatchingSupported() { return false; } }); if (!success) { logger.error("Cannot find space in the replication pool for " + this.serviceUrl + ". Check the network connectivity or the traffic"); } } /** * * Send the status update of the instance. * * @param appName * the application name of the instance. * @param id * the unique identifier of the instance. * @param newStatus * the new status of the instance. * @param info * the instance information of the instance. * @return true if the update succeeded, false otherwise. */ public void statusUpdate(final String appName, final String id, final InstanceStatus newStatus, final InstanceInfo info) { boolean success = statusBatcher.process(new ReplicationTask(appName, id, Action.StatusUpdate) { @Override public int execute() { CurrentRequestVersion.set(Version.V2); ClientResponse response = null; try { String urlPath = "apps/" + appName + "/" + id + "/status"; response = jerseyApacheClient .resource(serviceUrl) .path(urlPath) .queryParam("value", newStatus.name()) .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString()) .header(HEADER_REPLICATION, "true") .put(ClientResponse.class); return response.getStatus(); } finally { if (response != null) { response.close(); } } } }); if (!success) { logger.error("Cannot find space in the replication pool for " + this.serviceUrl + ". Check the network connectivity or the traffic"); } } /** * Get the service Url of the peer eureka node. * * @return the service Url of the peer eureka node. */ public String getServiceUrl() { return serviceUrl; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((serviceUrl == null) ? 0 : serviceUrl.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; PeerEurekaNode other = (PeerEurekaNode) obj; if (serviceUrl == null) { if (other.serviceUrl != null) return false; } else if (!serviceUrl.equals(other.serviceUrl)) return false; return true; } /** * Destroy the resources created for communication with the Peer Eureka * Server. */ public void destroyResources() { if (jerseyClient != null) { try { jerseyClient.destroyResources(); } catch (Throwable ignore) { } } } /** * Shuts down all resources used for peer replication. */ public void shutDown() { if (this.heartBeatBatcher != null) { this.heartBeatBatcher.stop(); } if (this.registerBatcher != null) { this.registerBatcher.stop(); } if (this.cancelBatcher != null) { this.cancelBatcher.stop(); } if (this.statusBatcher != null) { this.statusBatcher.stop(); } if (this.asgStatusBatcher != null) { this.asgStatusBatcher.stop(); } } /** * Sends heartbeats to peer eureka nodes. * * @param appName * - the application name for which the hearbeat needs to be * sent. * @param id * - the unique identifier of the heartbeat. * @param info * - the {@link InstanceInfo} of the instance for which the * heartbeat need to be sent. * @param overriddenStatus * the overridden status of the instance. * @throws Throwable */ private int sendHeartBeat(final String appName, final String id, final InstanceInfo info, final InstanceStatus overriddenStatus, ReplicationTask task) throws Throwable { ClientResponse response = null; try { String urlPath = "apps/" + appName + "/" + id; WebResource r = jerseyApacheClient .resource(serviceUrl) .path(urlPath) .queryParam("status", info.getStatus().toString()) .queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString()); if (overriddenStatus != null) { r = r.queryParam("overriddenstatus", overriddenStatus.name()); } response = r.accept(MediaType.APPLICATION_JSON_TYPE).header(HEADER_REPLICATION, "true").put( ClientResponse.class); InstanceInfo infoFromPeer = null; if ((response.getStatus() == Status.OK.getStatusCode()) && response.hasEntity()) { infoFromPeer = response .getEntity(InstanceInfo.class); } if ((task != null) && (infoFromPeer != null)) { task.setPeerInstanceInfo(infoFromPeer); } return response.getStatus(); } finally { if (response != null) { response.close(); } } } /** * Check if the exception is some sort of network timeout exception (ie) * read,connect. * * @param e * The exception for which the information needs to be found. * @return true, if it is a network timeout, false otherwise. */ private boolean isNetworkConnectException(Throwable e) { while (e.getCause() != null) { if (IOException.class.isInstance(e.getCause())) { return true; } e = e.getCause(); } return false; } /** * Class that simply tracks the time the task was created. */ private abstract class ReplicationTask { private long submitTime = System.currentTimeMillis(); private String appName; private String id; private Action action; private InstanceInfo instanceInfo; private InstanceInfo peerInstanceInfo; private InstanceStatus overriddenStatus; public String getAppName() { return appName; } public String getId() { return id; } public Action getAction() { return action; } public long getSubmitTime() { return this.submitTime; } public InstanceInfo getInstanceInfo() { return instanceInfo; } public void setInstanceInfo(InstanceInfo instanceInfo) { this.instanceInfo = instanceInfo; } public InstanceStatus getOverriddenStatus() { return overriddenStatus; } public void setOverriddenStatus(InstanceStatus overriddenStatus) { this.overriddenStatus = overriddenStatus; } public InstanceInfo getPeerInstanceInfo() { return peerInstanceInfo; } public void setPeerInstanceInfo(InstanceInfo peerInstanceInfo) { this.peerInstanceInfo = peerInstanceInfo; } public ReplicationTask(String appName, String id, Action action) { this.appName = appName; this.id = id; this.action = action; } public abstract int execute() throws Throwable; public void handleFailure(int statusCode) throws Throwable { Object[] args = { this.appName, this.id, this.action.name(), statusCode }; logger.warn( "The replication of {}/{}/{} failed with response code {}", args); } public boolean shouldReplicateInstanceInfo() { return false; } public boolean isBatchingSupported() { return config.shouldBatchReplication(); } } @Serializer("com.netflix.discovery.converters.EntityBodyConverter") @XStreamAlias("repllist") public static class ReplicationList { private List replicationList = new ArrayList(); public void addReplicationInstance(PeerEurekaNode.ReplicationInstance instance) { replicationList.add(instance); } public List getList() { return this.replicationList; } } @Serializer("com.netflix.discovery.converters.EntityBodyConverter") @XStreamAlias("batchresponse") /** * The jersey resource class that generates the replication batch response. */ public static class ReplicationListResponse { private List responseList = new ArrayList(); public List getResponseList() { return responseList; } public void addResponse( PeerEurekaNode.ReplicationInstanceResponse singleResponse) { responseList.add(singleResponse); } } @Serializer("com.netflix.discovery.converters.EntityBodyConverter") @XStreamAlias("instanceresponse") /** * The jersey resource class that generates the replication indivdiual response. */ public static class ReplicationInstanceResponse { private int statusCode; private InstanceInfo responseEntity; public static final class Builder { private ReplicationInstanceResponse response = new ReplicationInstanceResponse(); public Builder setStatusCode(int statusCode) { response.statusCode = statusCode; return this; } public Builder setResponseEntity(InstanceInfo entity) { response.responseEntity = entity; return this; } public ReplicationInstanceResponse build() { return response; } } public int getStatusCode() { return statusCode; } public InstanceInfo getResponseEntity() { return responseEntity; } } @Serializer("com.netflix.discovery.converters.EntityBodyConverter") @XStreamAlias("replinstance") /** * The jersey resource class that generates a particular replication event */ public static class ReplicationInstance { private String appName; private String id; private Long lastDirtyTimestamp; private String overriddenStatus; private String status; private InstanceInfo instanceInfo; private Action action; public String getAppName() { return appName; } public void setAppName(String appName) { this.appName = appName; } public String getId() { return id; } public void setId(String id) { this.id = id; } public Long getLastDirtyTimestamp() { return lastDirtyTimestamp; } public void setLastDirtyTimestamp(long lastDirtyTimestamp) { this.lastDirtyTimestamp = lastDirtyTimestamp; } public String getOverriddenStatus() { return overriddenStatus; } public void setOverriddenStatus(String overriddenStatus) { this.overriddenStatus = overriddenStatus; } public String getStatus() { return status; } public void setStatus(String status) { this.status = status; } public InstanceInfo getInstanceInfo() { return instanceInfo; } public void setInstanceInfo(InstanceInfo instanceInfo) { this.instanceInfo = instanceInfo; } public Action getAction() { return action; } public void setAction(Action action) { this.action = action; } } /** * Synchronize {@link InstanceInfo} information if the timestamp between * this node and the peer eureka nodes vary. */ private void syncInstancesIfTimestampDiffers(String id, InstanceInfo info, InstanceInfo infoFromPeer ) { try { if (infoFromPeer != null) { Object[] args = { id, info.getLastDirtyTimestamp(), infoFromPeer.getLastDirtyTimestamp() }; logger.warn( "Peer wants us to take the instance information from it, since the timestamp differs,Id : {} My Timestamp : {}, Peer's timestamp: {}", args); if ((infoFromPeer.getOverriddenStatus() != null) && !(InstanceStatus.UNKNOWN.equals(infoFromPeer .getOverriddenStatus()))) { Object[] args1 = { id, info.getOverriddenStatus(), infoFromPeer.getOverriddenStatus() }; logger.warn( "Overridden Status info -id {}, mine {}, peer's {}", args1); PeerAwareInstanceRegistry.getInstance() .storeOverriddenStatusIfRequired(id, infoFromPeer.getOverriddenStatus()); } PeerAwareInstanceRegistry.getInstance().register( infoFromPeer, true); } } catch (Throwable e) { logger.warn("Exception when trying to get information from peer :", e); } } /** * Get the batcher to process the replication events asynchronously. * * @param serviceUrl * the serviceUrl for which the events needs to be replicated * @param action * the action that indicates the type of replication event - * registrations, heartbeat etc * @return The batcher instance */ private MessageBatcher getBatcher(final String serviceUrl, String pBatcherName) { String batcherName = null; try { batcherName = new URL(serviceUrl).getHost(); } catch (MalformedURLException e1) { batcherName = serviceUrl; } String absoluteBatcherName = batcherName + "-" + pBatcherName; ConfigurationManager.getConfigInstance().setProperty( "batcher." + absoluteBatcherName + ".queue.maxMessages", config.getMaxElementsInPeerReplicationPool()); ConfigurationManager.getConfigInstance().setProperty( "batcher." + absoluteBatcherName + ".batch.maxMessages", 250); ConfigurationManager.getConfigInstance().setProperty( "batcher." + absoluteBatcherName + ".keepAliveTime", config.getMaxIdleThreadAgeInMinutesForPeerReplication() * 60); ConfigurationManager.getConfigInstance().setProperty( "batcher." + absoluteBatcherName + ".maxThreads", config.getMaxThreadsForPeerReplication()); return BatcherFactory.createBatcher(absoluteBatcherName, new MessageProcessor() { private String BATCH_URL_PATH = "peerreplication/batch/";; @Override public void process(List tasks) { if (!tasks.get(0).isBatchingSupported()) { executeSingle(tasks); } else if (!executeBatch(tasks)) { executeSingle(tasks); } } private boolean executeBatch(List tasks) { boolean success = true; boolean done = true; PeerEurekaNode.ReplicationList list = new PeerEurekaNode.ReplicationList(); for (ReplicationTask task : tasks) { if (System.currentTimeMillis() - config.getMaxTimeForReplication() > task .getSubmitTime()) { Object[] args = { task.getAppName(), task.getId(), task.getAction(), new Date(System.currentTimeMillis()), new Date(task.getSubmitTime()) }; logger.warn( "Replication events older than the threshold. AppName : {}, Id: {}, Action : {}, Current Time : {}, Submit Time :{}", args); continue; } PeerEurekaNode.ReplicationInstance instance = new PeerEurekaNode.ReplicationInstance(); instance.setAppName(task.getAppName()); instance.setId(task.getId()); InstanceInfo instanceInfo = task.getInstanceInfo(); if (instanceInfo != null) { String overriddenStatus = (task .getOverriddenStatus() == null ? null : task.getOverriddenStatus().name()); instance.setOverriddenStatus(overriddenStatus); instance.setLastDirtyTimestamp(instanceInfo .getLastDirtyTimestamp()); if (task.shouldReplicateInstanceInfo()) { instance.setInstanceInfo(instanceInfo); } String instanceStatus = instanceInfo .getStatus() == null ? null : instanceInfo.getStatus().name(); instance.setStatus(instanceStatus); } instance.setAction(task.getAction()); list.addReplicationInstance(instance); } if (list.getList().size() == 0) { return true; } Action action = list.getList().get(0).action; DynamicCounter.increment("Batch_" + action + "_tries"); do { done = true; ClientResponse response = null; try { response = jerseyApacheClient .resource(serviceUrl) .path(BATCH_URL_PATH) .accept(MediaType.APPLICATION_JSON_TYPE) .type(MediaType.APPLICATION_JSON_TYPE) .post(ClientResponse.class, list); if (!isSuccess(response.getStatus())) { return false; } DynamicCounter.increment("Batch_" + action + "_success"); PeerEurekaNode.ReplicationListResponse batchResponse = response .getEntity(PeerEurekaNode.ReplicationListResponse.class); int ctr = 0; for (PeerEurekaNode.ReplicationInstanceResponse singleResponse : batchResponse .getResponseList()) { int statusCode = singleResponse .getStatusCode(); if ((!isSuccess(statusCode)) || (singleResponse .getResponseEntity() != null)) { if (singleResponse.getResponseEntity() != null) { tasks.get(ctr) .setPeerInstanceInfo( singleResponse .getResponseEntity()); } tasks.get(ctr) .handleFailure(statusCode); } ++ctr; } done = true; } catch (Throwable e) { if ((isNetworkConnectException(e))) { DynamicCounter.increment("Batch_" + action + "_retries"); done = false; } else { success = false; logger.info( "Not re-trying this exception because it does not seem to be a network exception", e); } } finally { if (response != null) { response.close(); } } } while (!done); return success; } private void executeSingle(List tasks) { for (ReplicationTask task : tasks) { boolean done = true; do { done = true; try { if (System.currentTimeMillis() - config.getMaxTimeForReplication() > task .getSubmitTime()) { Object[] args = { task.getAppName(), task.getId(), task.getAction(), new Date(System.currentTimeMillis()), new Date(task.getSubmitTime()) }; logger.warn( "Replication events older than the threshold. AppName : {}, Id: {}, Action : {}, Current Time : {}, Submit Time :{}", args); continue; } DynamicCounter.increment("Single_" + task.getAction().name() + "_tries"); int statusCode = task.execute(); if (!isSuccess(statusCode)) { task.handleFailure(statusCode); } DynamicCounter.increment("Single_" + task.getAction().name() + "_success"); } catch (Throwable e) { logger.error( name + task.getAppName() + "/" + task.getId() + ":" + task.getAction(), e); try { Thread.sleep(RETRY_SLEEP_TIME_MS); } catch (InterruptedException e1) { } if ((isNetworkConnectException(e))) { DynamicCounter.increment(task .getAction().name() + "_retries"); done = false; } else { logger.info( "Not re-trying this exception because it does not seem to be a network exception", e); } } } while (!done); } } }); } private boolean isSuccess(int statusCode) { return statusCode >= 200 && statusCode < 300; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy