Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save michael-pratt/30517da70da3e00a902a40116e2f87f8 to your computer and use it in GitHub Desktop.
Save michael-pratt/30517da70da3e00a902a40116e2f87f8 to your computer and use it in GitHub Desktop.
Registering multiple instances in Eureka from one application
package com.myorg;
import com.netflix.appinfo.ApplicationInfoManager;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs;
import com.netflix.discovery.DiscoveryClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.cloud.commons.util.InetUtils;
import org.springframework.cloud.commons.util.InetUtilsProperties;
import org.springframework.cloud.netflix.eureka.EurekaClientConfigBean;
import org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean;
import org.springframework.cloud.netflix.eureka.InstanceInfoFactory;
import org.springframework.cloud.netflix.eureka.serviceregistry.EurekaServiceRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextClosedEvent;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@SpringBootApplication
public class MultipleEurekaInstanceApplication
{
private final static Logger ourLogger =
LoggerFactory.createLogger(MultipleEurekaInstanceApplication.class);
/**
* Eureka application name / instance prefix
*/
private final static String APPLICATION_NAME = "my-app";
@Autowired
private EurekaInstanceConfigBean originalInstanceConfig;
@Autowired
private EurekaClientConfigBean originalClientConfig;
@Autowired
private ApplicationInfoManager applicationInfoManager;
@Autowired
private ApplicationContext context;
@Autowired
private AbstractDiscoveryClientOptionalArgs args;
@Value("${HOSTNAME}")
private String hostName;
@Value("${my.app.numInstances:5}")
private int numInstances;
/**
* These are the DiscoverClient objects we manage ourselves
*/
private Set<DiscoveryClient> discoveryClients = new HashSet<>();
public static void main(String[] args)
{
SpringApplication.run(MultipleEurekaInstanceApplication.class, args);
}
/**
* Responsible for registering <code>my.app.numInstances</code> number
* of instances in Eureka for use by session manager. Waits until the
* application context is ready to create the registrations.
*
* @return
*/
@Bean
public ApplicationListener applicationListener()
{
return applicationEvent -> {
if(applicationEvent instanceof ApplicationReadyEvent)
{
if(numInstances < 1)
{
numInstances = 1;
}
//
// We get 1 session for free with Spring Cloud Eureka, so we only need
// to register (numInstances - 1) new instances.
//
for(int i = 1; i < numInstances; i++)
{
//
// Simply creating the DiscoveryClient is enough to force
// registration. We hold on to the reference for later use
// so we can unregister during shutdown.
//
DiscoveryClient discoveryClient =
new DiscoveryClient(
duplicateAppInfoManager(),
duplicateConfig(),
args);
discoveryClients.add(discoveryClient);
}
}
};
}
/**
* Handles forcing all the extra instances we register to DOWN state when the
* application is terminated.
*/
@Bean
public ApplicationListener discoveryClientShutdownListener()
{
return applicationEvent -> {
if(applicationEvent instanceof ContextClosedEvent)
{
for(DiscoveryClient client: discoveryClients)
{
ourLogger.info("Shutting down discovery client for " + client.getApplicationInfoManager().getEurekaInstanceConfig().getInstanceId());
client.shutdown();
}
}
};
}
//
// These duplicateXXX methods are used to create copies of the
// various Eureka beans that the Spring Cloud auto config
// creates for us.
//
private ApplicationInfoManager duplicateAppInfoManager()
{
EurekaInstanceConfigBean newInstanceConfig =
new EurekaInstanceConfigBean(new InetUtils(new InetUtilsProperties()));
newInstanceConfig.setEnvironment(context.getEnvironment());
newInstanceConfig.setAppname(APPLICATION_NAME);
newInstanceConfig.setInstanceId(APPLICATION_NAME + ":" + hostName + ":" + UUID.randomUUID().toString());
newInstanceConfig.setInitialStatus(InstanceInfo.InstanceStatus.UP);
newInstanceConfig.setNonSecurePortEnabled(originalInstanceConfig.isNonSecurePortEnabled());
newInstanceConfig.setNonSecurePort(originalInstanceConfig.getNonSecurePort());
newInstanceConfig.setSecurePortEnabled(originalInstanceConfig.isSecurePortEnabled());
newInstanceConfig.setSecurePort(originalInstanceConfig.getSecurePort());
newInstanceConfig.setDataCenterInfo(originalInstanceConfig.getDataCenterInfo());
newInstanceConfig.setHealthCheckUrl(originalInstanceConfig.getHealthCheckUrl());
newInstanceConfig.setSecureHealthCheckUrl(originalInstanceConfig.getSecureHealthCheckUrl());
newInstanceConfig.setHomePageUrl(originalInstanceConfig.getHomePageUrl());
newInstanceConfig.setStatusPageUrl(originalInstanceConfig.getStatusPageUrl());
newInstanceConfig.setStatusPageUrlPath(originalInstanceConfig.getStatusPageUrlPath());
newInstanceConfig.setIpAddress(originalInstanceConfig.getIpAddress());
newInstanceConfig.setPreferIpAddress(originalInstanceConfig.isPreferIpAddress());
ApplicationInfoManager manager =
new ApplicationInfoManager(
newInstanceConfig,
duplicateInstanceInfo(applicationInfoManager.getInfo()));
return manager;
}
private InstanceInfo duplicateInstanceInfo(InstanceInfo info)
{
//
// Temporarily swap the instance ID; have to restore so that the bean
// created in the auto configs keeps it original ID.
//
String originalId = originalInstanceConfig.getInstanceId();
originalInstanceConfig.setInstanceId(SESSION_SERVER_NAME + ":" + hostName + ":" + UUID.randomUUID().toString());
InstanceInfo newInfo = new InstanceInfoFactory().create(originalInstanceConfig);
newInfo.setStatus(InstanceInfo.InstanceStatus.UP);
originalInstanceConfig.setInstanceId(originalId);
return newInfo;
}
private EurekaClientConfigBean duplicateConfig()
{
EurekaClientConfigBean newConfig = new EurekaClientConfigBean();
newConfig.setFetchRegistry(false);
newConfig.setEurekaServerPort(originalClientConfig.getEurekaServerPort());
newConfig.setAllowRedirects(originalClientConfig.isAllowRedirects());
newConfig.setAvailabilityZones(originalClientConfig.getAvailabilityZones());
newConfig.setBackupRegistryImpl(originalClientConfig.getBackupRegistryImpl());
newConfig.setServiceUrl(originalClientConfig.getServiceUrl());
return newConfig;
}
}
@michael-pratt
Copy link
Author

This assumes you have Spring Cloud Eureka configured to register an instance automatically:

eureka.client.register-with-eureka=true
eureka.instance.appname=my-app
eureka.instance.instance-id=my-app:${HOSTNAME}:${random.uuid}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment