Spring Boot Eureka Server und Client-Site Loadbalancing mit einem Ribbon-RestTemplate

07.06.2016

Spring Boot - Eureka Server

In diesem Artikel wird beschrieben, wie ein Eureka-Server mit Spring Boot aufgesetzt wird und wie sich Spring Boot Client-Services bei dem Server mit @EnableEurekaClient registrieren. Anschliessend wird beschrieben, wie sich die registrierten Services programmatisch finden und aufrufen lassen, dabei wird auch gezeigt, wie die Services über ihren Namen (spring.application.name) mittels eines Ribbon-RestTemplate aufgerufen werden und wie eine clientseitige Lastverteilung (LoadBalancing) zwischen mehreren Instanzen eines Services realisiert wird indem man die Annotation @LoadBalanced verwendet.

Übersicht

In diesem Artikel wird ein Eureka Server erstellt und ein Spring Boot DateService, der über eine REST-Schnittstelle einfach nur das aktuelle Datum zurückliefert.

Der DateService wird in dem Szenario zweimal gestartet, allerdings mit unterschiedlichen Ports.

Außerdem wird eine Spring Boot Applikation (EurekaDiscoveryClient) erstellt, die mittels eines RestTemplate den date-Endpoint vom DateService aufruft. Die beiden Instanzen des DateService haben sich beim Eureka Server mit der Annotation @EnableEurekaClient registriert. Das verwendete RestTesmplate wird durch die Annotation @LoadBalanced dazu gebracht ein clientseitiges Loadbalancing mit Ribbon bei jedem Aufruf zu machen.

Eureka Server erstellen

Los geht es auf der Seite start.spring.io. Wo der Eureka Server ausgewählt wird, um dann das Projekt-Template zu erstellen.

Das Template wird anschliessend in einer IDE geöffnet. In der Klasse EurekaServerApplication muss die Annotation @EnableEurekaServer ergänzt werden, damit die Anwendung als Eureka Server startet.

package org.hameister;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(EurekaServerApplication.class, args);
	}
}

In der Datei application.properties müssen dem Eureka Server mitgeteilt werden auf welchem Port er starten soll. Der Port 8761 ist der Standard-Port unter dem die Clients den Eureka Server suchen, wenn kein anderer Port für den Eureka Server im Client angegeben ist. Da Eureka redundant und ausfallsicher entworfen wurde, wird davon ausgegangen, dass immer zwei Instanzen des Eureka Server laufen. In dem Beispiel benutzen wir nur einen Server und schalten deshalb die gegenseitige Registrierung der Server über die Properties aus, damit der Server ohne Fehlermeldung startet.

spring.application=eureka-service
server.port: 8761
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

Spring Boot DateService erstellen

Im nächsten Stritt erstellen wir den DateService. Gestartet wird wieder bei start.spring.io. Dort wählt man Web, Actuator und Eureka Discovery aus und erstellt das Projekt-Template und öffnet es in einer IDE.

Anschließend wird in der Klasse DateServiceApplication die Annotation EnableEurekaClient ergänzt, die dafür sorgt, dass sich die Spring Boot Application bei dem Eureka Server beim Start registriert.

package org.hameister;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class DateServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(DateServiceApplication.class, args);
	}
}

In der Datei application.properties muss noch der Name des Service angegeben werden. Den Port übergeben wir beim Starten des Service.

  spring.application.name=date-service
  

Der RestController mit der Rest-Schinttstelle sieht folgendermaßen aus:

  package org.hameister;
  
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.cloud.context.config.annotation.RefreshScope;
  import org.springframework.http.HttpStatus;
  import org.springframework.http.MediaType;
  import org.springframework.http.ResponseEntity;
  import org.springframework.web.bind.annotation.RequestMapping;
  import org.springframework.web.bind.annotation.RequestMethod;
  import org.springframework.web.bind.annotation.RestController;
  
  import java.util.Date;
  
  /**
   * Created by hameister on 07.06.16.
   */
  @RestController
  public class DateController {
  
  
      @Value("${instance.name}")
      private String instance;
  
      @RequestMapping(value = "/date", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
      ResponseEntity<String> getDate() {
          return new ResponseEntity<String>(instance+":"+new Date().toString(), HttpStatus.OK);
      }
  
  }
  

In der Response steht der Name der Instanz (instance.name), der beim Starten übergeben wird. Außerdem wird das aktuelle Datum in die Response geschrieben.

Nachdem das mvn package -DskipTests aufgerufen wurde, kann der Service auf der Kommandozeile gestartet werden.

Beim Start wird der Port und der instance.name übergeben. So ist es möglich den gleichen Service unter verschiedenen Ports laufen zu lassen.

Start von Service 1

   java -jar DateService-0.0.1-SNAPSHOT.jar --server.port=8001 --instance.name=Instanz1
  

Start von Service 2

   java -jar DateService-0.0.1-SNAPSHOT.jar --server.port=8002 --instance.name=Instanz2
  

Wenn man vorher den Eureka Server gestartet hat, dann sollten unter der Url: localhost:8761 zwei Instanzen des DATE-SERVICE zu sehen sein:

DiscoveryClient und Lastverteilung

Im letzen Schritt wird ein Discovery-Client erstellt, der den DateService aufruft. Dazu wird einfach über start.spring.io ein Spring Boot Projekt erstellt. Es muss Eureka Discovery ausgewählt werden und abschließend mit Generate Project ein Maven-Projekt erstellt werden.

Das Projekt muss danach in einer IDE geöffnet und die Annotation @EnableEurekaClient ergänzt werden.

  package org.hameister;
  
  import org.apache.commons.lang.builder.ToStringBuilder;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.boot.CommandLineRunner;
  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.SpringBootApplication;
  import org.springframework.boot.builder.SpringApplicationBuilder;
  import org.springframework.cloud.client.ServiceInstance;
  import org.springframework.cloud.client.discovery.DiscoveryClient;
  import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.http.ResponseEntity;
  import org.springframework.stereotype.Component;
  import org.springframework.web.client.RestTemplate;
  
  
  
  @SpringBootApplication
  @EnableEurekaClient
  public class EurekaDiscoveryApplication {
  	public static void main(String[] args) {
  		SpringApplication.run(EurekaDiscoveryApplication.class, args);
  	}
  }
  
  	@Component
  	class RestTemplateExample implements CommandLineRunner {

  		@Autowired
  		private RestTemplate restTemplate;
  
  		@Override
  		public void run(String... strings) throws Exception {
 
  			for (int i=0; i<20;i++) {
  				ResponseEntity<String> resp = this.restTemplate.getForEntity("http://date-service/date", String.class);
  				System.out.println(resp.getBody());
  			}
  		}  
  	}
  

Außerdem wird über eine @Component und einen CommandLineRunner über die run-Methode eine Möglichkeit geschaffen das LoadBalancing auf der Kommandozeile auszuprobieren. Deshalb wird das restTemplate hier beispielhaft 20 mal aufgerufen. Das besondere dabei ist, dass nur der logische Name date-service und der Endpoint /date verwendet wird und kein Hostname und Port benötigt wird.

Damit das funktioniert, muss noch dafür gesorgt werden, dass das @Autowired mit dem Ribbon Template initialisiert wird. Dazu muss folgende Klasse angelegt werden:

   package org.hameister;
   
   import org.springframework.cloud.client.loadbalancer.LoadBalanced;
   import org.springframework.context.annotation.Bean;
   import org.springframework.context.annotation.Configuration;
   import org.springframework.web.client.RestTemplate;
   
   /**
    * Created by hameister on 07.06.16.
    */
   @Configuration
   public class LoadBalancedRestTemplate {
   
       @LoadBalanced
       @Bean
       RestTemplate restTemplate() {
           return new RestTemplate();
       }
   }
   

Zu beachten ist dabei die Annotation @LoadBalanced. Sie sorgt dafür, dass ein Ribbon-RestTemplate angelegt wird, das ein client-seitiges Loadbalancing ermöglicht.

Bis vor kurzem hat das @Autowired per auto configuration funktioniert. Allerdings wurde das geändert: Spring Doku zu der Änderung. Wenn man nach der Zeichenkette // use the "smart" Eureka-aware RestTemplate sucht, findet man noch eine Reihe von Beispielen, in denen die Annotation @LoadBalanced noch nicht verwendet wurde.

Wenn man die Anwendung nun von der Kommandozeile aus startet sieht man sehr schön, dass beide Services aufgerufen werden.

Start der Applikation:

  java -jar EurekaDiscovery-0.0.1-SNAPSHOT.jar
  

Konsolenausgabe:

   ... 
    
  Instanz1:Tue Jun 07 20:21:44 CEST 2016
  Instanz2:Tue Jun 07 20:21:44 CEST 2016
  Instanz1:Tue Jun 07 20:21:44 CEST 2016
  Instanz2:Tue Jun 07 20:21:44 CEST 2016
  Instanz1:Tue Jun 07 20:21:44 CEST 2016
  Instanz2:Tue Jun 07 20:21:44 CEST 2016
  Instanz1:Tue Jun 07 20:21:44 CEST 2016
  Instanz2:Tue Jun 07 20:21:45 CEST 2016
  Instanz1:Tue Jun 07 20:21:45 CEST 2016
  Instanz2:Tue Jun 07 20:21:45 CEST 2016
  Instanz1:Tue Jun 07 20:21:45 CEST 2016
  Instanz2:Tue Jun 07 20:21:45 CEST 2016
  Instanz1:Tue Jun 07 20:21:45 CEST 2016
  Instanz2:Tue Jun 07 20:21:45 CEST 2016
  Instanz1:Tue Jun 07 20:21:45 CEST 2016
  Instanz2:Tue Jun 07 20:21:45 CEST 2016
  Instanz1:Tue Jun 07 20:21:45 CEST 2016
  Instanz2:Tue Jun 07 20:21:45 CEST 2016
  Instanz1:Tue Jun 07 20:21:45 CEST 2016
  Instanz2:Tue Jun 07 20:21:45 CEST 2016
  ....
  

In den folgenden Artikeln findet man noch eine genauere Beschreibung zum Eureka Server und Clients. Allerdings fehlt teilweise die automatische Konfiguration des RestTemplates.