Search code examples
javaspring-bootspring-cloudnetflix-eurekaspring-cloud-netflix

Spring Boot App doesn't start when the first eureka server is down


i've Eureka cluster with multiple servers:

#ENV 
EUREKA_URL=http://host1:8761/eureka/,http://host2:8761/eureka/,http://host3:8761/eureka/

#boostrap.yml
eureka:
 client:
  registryFetchIntervalSeconds: 5
  serviceUrl:
   defaultZone: ${EUREKA_URL:http://127.0.0.1:8761/eureka/}

And in case, when host1 is down, my app doesn't start with exception:

13410 2021-01-14 11:28:34,994 ERROR [ main ] o.s.b.SpringApplication                                 | Application run failed 
org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://host1:8761/eureka/apps/": Connection refused (Connection refused); nested exception is java.net.ConnectException: Connection refused (Connection refused)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:748)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:674)
    at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:583)
    at org.springframework.cloud.netflix.eureka.http.RestTemplateEurekaHttpClient.getApplicationsInternal(RestTemplateEurekaHttpClient.java:154)
    at org.springframework.cloud.netflix.eureka.http.RestTemplateEurekaHttpClient.getApplications(RestTemplateEurekaHttpClient.java:142)
    at org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration.lambda$eurekaConfigServerInstanceProvider$0(EurekaConfigServerBootstrapConfiguration.java:112)
    at org.springframework.cloud.config.client.ConfigServerInstanceProvider.getConfigServerInstances(ConfigServerInstanceProvider.java:50)
    at org.springframework.cloud.config.client.ConfigServerInstanceProvider$$FastClassBySpringCGLIB$$facbf882.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:771)
    at 

It's strange situation for me, because i expected that eureka will try to connect to other servers, but it's not so. I found it in spring-cloud-netflix-eureka-client-2.2.3.RELEASE.jar:

       // EurekaConfigServerBootstrapConfiguration.java
        private String getEurekaUrl(EurekaClientConfigBean config) {
            List<String> urls = EndpointUtils.getServiceUrlsFromConfig(config,
                    EurekaClientConfigBean.DEFAULT_ZONE, true);
            return urls.get(0);
        }

and

    // DefaultEurekaClientConfig.java
    @Override
    public List<String> getEurekaServerServiceUrls(String myZone) {
        String serviceUrls = configInstance.getStringProperty(
                namespace + CONFIG_EUREKA_SERVER_SERVICE_URL_PREFIX + "." + myZone, null).get();
        if (serviceUrls == null || serviceUrls.isEmpty()) {
            serviceUrls = configInstance.getStringProperty(
                    namespace + CONFIG_EUREKA_SERVER_SERVICE_URL_PREFIX + ".default", null).get();

        }
        if (serviceUrls != null) {
            return Arrays.asList(serviceUrls.split(URL_SEPARATOR));
        }

        return new ArrayList<String>();
    }

and finally:

    // RestTemplateEurekaHttpClient.java
    private EurekaHttpResponse<Applications> getApplicationsInternal(String urlPath,
            String[] regions) {
        String url = serviceUrl + urlPath;

        if (regions != null && regions.length > 0) {
            url = url + (urlPath.contains("?") ? "&" : "?") + "regions="
                    + StringUtil.join(regions);
        }

        // There is no exception handling here and above!
        ResponseEntity<EurekaApplications> response = restTemplate.exchange(url,
                HttpMethod.GET, null, EurekaApplications.class);

        return anEurekaHttpResponse(response.getStatusCodeValue(),
                response.getStatusCode().value() == HttpStatus.OK.value()
                        && response.hasBody() ? (Applications) response.getBody() : null)
                                .headers(headersOf(response)).build();
    }

I've java11, springBootVersion = '2.3.4.RELEASE', spring-cloud-netflix-eureka-client-2.2.3.RELEASE, eureka-client-1.9.21.

How I can make it so that eureka, in case of unsuccessful registration on the first server, continues to try on the following servers? Any idea?


Solution

  • I found workaround. I just override bean:

    @Slf4j
    @Configuration
    public class EurekaMultiClientBoostrapConfig {
        @Bean
        @Primary
        public RestTemplateEurekaHttpClient configDiscoveryRestTemplateEurekaHttpClient(EurekaClientConfigBean config) {
            List<String> urls = EndpointUtils.getServiceUrlsFromConfig(config,
                    EurekaClientConfigBean.DEFAULT_ZONE, true);
            for (String url : urls) {
                try {
                    RestTemplateEurekaHttpClient client = (RestTemplateEurekaHttpClient) new RestTemplateTransportClientFactory()
                            .newClient(new DefaultEndpoint(url));
                    client.getApplications(config.getRegion());
                    log.info("Registered on Eureka host '{}' is successful.", url);
                    return client;
                } catch (Exception e) {
                    log.warn("Eureka host '{}' is unavailable(reason: {})", url, e.getMessage());
                }
            }
            throw new IllegalStateException("Failed to register on any eureka host.");
        }
    

    P.S. Since this configuration belongs to the Bootstrap Configuration, don't forget add following param to resources/META-INF/spring.factories

    org.springframework.cloud.bootstrap.BootstrapConfiguration=your.app.package.EurekaMultiClientBoostrapConfig