Overview
In this article, we'll get introduced to client-side service discovery and load balancing via Spring Cloud Netflix Eureka.
In a typical microservice architecture we have many small applications deployed separately and they often need to communicate with each other. Specifically, when we say client service, we mean a service that needs to make REST calls to some other end service.
The problem in this type of architecture is how the client service finds all of its end services. We could hardcode the hostname/port in some property file, but this isn't always practical or feasible in a cloud environment. There could be any number of microservices, and it's time and resource-consuming to hard-code when there's an uncertain amount of them, and when their locations may change.
To further add to the complexity, services could have multiple instances of themselves (based on the load). Which instance will actually serve the response could be challenging as we want to have equal load distribution.
Netflix Eureka
Netflix Eureka is a lookup server (also called a registry). All the microservices in the cluster register themselves to this server.
When making a REST call to another service, instead of providing a hostname and port, they just provide the service name.
The actual routing is done at runtime along with equally distributing the load among the end services. There are other service discovery clients like Consul, Zookeeper etc, but we will be using Eureka in this article.
To understand this concept we will be building three services in our example:
- Eureka Server: acts as a service registry.
- Movie Service: a simple REST service that provides movie information.
- Recommendation Service: a simple REST service but it internally calls the Movie Service to complete its requests.
Eureka Server Setup
The best way to start with a skeleton project is to use Spring Initializr. Select your preferred version of Spring Boot and add the "Eureka Server" dependency and generate as a Maven project:
To make a Eureka server, all we need to do is add the @EnableEurekaServer
annotation to our main class:
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
We will be running the server on port 8761
, which is the recommended port by the Spring team. So in application.properties
we'll add:
server.port = 8761
To test this endpoint, navigate your browser to http://localhost:8761/:
End Service Setup (Movie Service)
Again, we are using Spring Initializr to create our project. Select your preferred version of Spring Boot and add the "Web" and "Eureka Discovery" dependencies and generate as a Maven project:
To make this a client, all we need to do is add the @EnableEurekaClient
annotation on the class level:
@SpringBootApplication
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
As an alternative, we could use the @EnableDiscoveryClient
annotation, which comes from spring-cloud-commons
. It picks the implementation (Consul, Zookeeper, Eureka) according to the classpath. In our case, it would automatically choose Eureka.
With that out of the way, we can define our Movie
model:
public class Movie {
private Integer id;
private String name;
private String synopsis;
// getters and setters
}
And finally, we can define a controller:
@RestController
public class MovieController {
private static List<Movie> movieList = new ArrayList<>();
static {
movieList.add(new Movie(1, "movie-1", "summary-1"));
movieList.add(new Movie(2, "movie-2", "summary-2"));
movieList.add(new Movie(3, "movie-3", "summary-3"));
}
@GetMapping("/movies")
public ResponseEntity<?> getMovies() {
return ResponseEntity.ok(movieList);
}
}
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
Above, we created a simple endpoint that returns a list of Movie
objects, simulating a call to a database.
We have the Eureka Discovery
client jar in this project. Upon seeing this in the classpath, Spring will attempt to find a running Spring Eureka server. We need to specify this in the application.properties
:
server.port = 8060
spring.application.name = movie-service
eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka/
Along with specifying the URL of the server, we also need to specify the application name. It will be this name that other services use for making REST calls.
With that, we've registered our service to the server, and any other registered service can call it using spring.application.name
.
Let's refresh our Eureka server endpoint:
Along with the registration with Eureka server, the Eureka client jar is also sending periodic heartbeats to Eureka server to let it know that it's still available.
Client Service Setup (Recommendation Service)
Now let's build our client service that will be called movie-service
. Once again let's create a new project with Spring Initializr with the same dependencies as earlier:
In its application.properties
we again have to specify application name and Eureka details:
server.port = 8050
spring.application.name = recommendation-service
eureka.client.serviceUrl.defaultZone = http://localhost:8761/eureka/
Then we annotate the main class with @EnableEurekaClient
:
@SpringBootApplication
@EnableEurekaClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@LoadBalanced
@Bean
RestTemplate restTemplate() {
return new RestTemplate();
}
}
We also need to create a RestTemplate
bean and mark it as @LoadBalanced
. This tells Spring that we want to take advantage of client-side load balancing, which is in this case done by Ribbon.
Client-side load balancing decides which instance (in case of multiple end services running in the cluster that client can call) to call.
Ribbon was developed by Netflix and later open sourced. Its dependency automatically comes with the Eureka Discovery dependency. It automatically integrates with Spring and distributes loads based on server health, performance, region, etc.
We won't be required to use Ribbon directly as it automatically integrates RestTemplate
, Zuul, Feign, etc. Using @LoadBalanced
we made the RestTemplate
ribbon aware.
Let's write RecommendationController
class that internally calls our movie service:
@RestController
public class RecommendationController {
@Autowired
RestTemplate restTemplate;
@RequestMapping(value = "/recommendations", method = RequestMethod.GET)
@ResponseBody
public Movie[] recommendations() {
Movie[] result = restTemplate.getForObject("http://movie-service/movies", Movie[].class);
return result;
}
}
Above, we @Autowired
the Ribbon-enabled RestTemplate
and used it to call the movie-service
. Note that we do not have to specify the hostname or the port anywhere.
What Spring internally does here is because it's registered with the Eureka server, it stores the list of all the services and its running instances locally. When we make a REST call to the movie-service
like this (instead of providing a hostname and port), it substitutes the actual endpoint URLs from the previously stored list and then makes the REST call.
Of course, the stored list of services along with its running instances is periodically updated. The best part of all this is that we don't have to take care of this at all and all these things are handled by Spring internally.
Let's test the recommendation endpoint, navigate your browser (or use curl, postman, etc.) to http://localhost:8050/recommendations, you will see a response that looks something like:
Conclusion
In this article, we've covered how to use Spring Cloud Eureka for service discovery in the microservice/cloud environment. We created two simple REST services that communicate with each other without hardcoding any hostname/port while making REST calls.
As always, the code for the examples used in this article can be found on Github.