The search for physical addresses around services within our microservices is a well-known concept since the beginning of distributed computing. It’s a key piece of our architecture since it directly affects our distributed architecture, starting with the following three key reasons:
- Horizontal Scalability: It offers the application the ability to quickly scale services down and up the without disrupting the service consumers;
- Abstraction: As physical locations are not known by the service consumers, new instances can be removed and added at any time to the available services pool;
- Resiliency: If for some reason a microservice becames unavailable, it will automatically be removed from the available services list, routing other service consumers around them.
The big difference between service discovery (SD) and DNS is, while SD may have an N application server running, DNS will always have a static number that will not go up or down. Another difference is the persistence state, meaning that, while on SD we can restart any service at any time and get their original state, with DNS we will always get the same state as it was at the time of the crash. With these main differences, we can already see some of our horizontal scalability getting limited.
In this article, I will explain you how this cloud-based solution will work together with client-side caching and load-balancing (for when the SD is unavailable).
A stub project will be created with the goal of showing you how easy it is to empower your microservice architecture with few steps just by using Spring Cloud and Netflix’s Eureka service discovery agent.
Service Discovery Architecture
Across all service discovery implementations available out there, four main concepts are used among them, health monitoring, shared information, service address lookups, and services registration.
- Health Monitoring: How will the service share his internal health state?
- Shared Information: How will the service share information with other services?
- Service Address Lookups: How will the service get other service’s information?
- Services Registration: How does a service register himself at the service discovery agent?
All these questions will be answered across the article.
- For the first step, the service will register himself at the SD. To do so, an invocation is done with the correspondent service name, previously defined at his configuration file, and physical location;
2. The other service location can be looked up by a logical name from the SD;
After the service comes up online, a heartbeat check is done to a specific endpoint. If any service fails to return a good health check, it will be removed from the pool of available services.
3. The SD returns the requested service location;
4. All ready to proceed with the request.
Client-side Load Balancing
Load balancing was introduced with the main goal of looking up and cache the registry in each service instance to improve performance.
How does it work:
- After the services register themselves at Eureka, the SD will know each service’s physical location and port number of each service instance along with a service ID (service name) that are being started.
- When service A calls service B, it will use Netflix Ribbon (client-side load balance) library. Ribbon will then contact Netflix Eureka (SD) to retrieve the correspondent service information and then cache it locally.
- Ribbon will regularly contact SD and refresh its local cache.
And that’s it, this simple process can be the difference between the success and failure of your application.
Building your Spring Eureka Service
Being a spring cloud side project, setting it up is pretty much straightforward. We first start by creating a new different spring project with the following pom.xml dependencies:
Then we need to define which port our SD should run in, and define some of the SD behaviour. For that, open the application.yml and set the following properties:
To finish, we just need to edit our application bootstrap class and add the @EnableEurekaServer annotation like:
At this point you can start up the Eureka service by running the following commands at the project directory:
- mvn clean install
- java -jar target/eureka-0.0.1-SNAPSHOT.jar
After this you should be able to access http://localhost:8761/ and see something like this:
And that’s it, with just three steps, we have our service discovery agent up and running! Let’s create a simple example to check if it’s working properly.
Building a client service module
In the previous post, we created a simple client fetching his configuration from a shared configuration server. Here, we follow the same approach, the big difference is that we now have two clients services, where the client A, in the midst of its processing, will invoke client B service for some data fetching.
- Create another spring boot project with the following pom.xml dependencies:
Note: There are a few more dependencies that were added, like h2 in memory DB, to see the full pom.xml file, you can access the link above. For the seek of the context, the important part is spring-cloud-starter-netflix-eureka-client.
The prefer-ip-address should be used mostly in a container-based environment, using docker as an example, by default, services are started with randomly generated hostnames and no DNS entries for them.
Note: Make sure the SD URL is well specified as the service name, this is mandatory for the success of the service.
To test how well this configurations work, run the following commands at the project directory:
- mvn clean install
- java -jar target/client1-0.0.1-SNAPSHOT.jar
You should see something like this on the SD interface:
And again, with only 3 steps, we have our client service up and running, registering himself at the SD, as shown at the beginning.
The only thing remaining is having a client to interact with through our SD. The process is basically the same as the one previously shown, configurations included. Create a new spring boot project and make it run in some other port. Whenever the second service is up and running, a new entry should appear at Eureka like:
Within the context, we have two services up and running, the client service and the address service. We’ll do a simple request to get the address of the client with the given ID, with the following steps:
- The client service will fetch the client with the given ID;
- The client service will ask Eureka for the address service location;
- Creates a simple HTTP request, to the endpoint that will retrieve the address by the fetched client address id;
- Client’s service maps the return json object with a address DTO;
- Ready to return the address object;
Setting up the JPA entities and defining the REST controllers are out of this article scope, however in the near feature you will be able to see how in another article, so stay tuned.
With all this, the most important section would be how the client service will ask Eureka for the address service location and how the data will be then fetched.
For that, make sure your services bootstrap class contains the @EnableDiscoveryClient annotation and it exposes a RestTemplate bean annotated as @LoadBalaced like:
This Load balance is what makes the service able to use Ribbon library to fetch the cached information once given by the SD;
Then at the third and fourth step, we have something like:
With this, the endpoint should retrieve a json object like:
As mentioned already, you will be able to check all these examples, up and running at this github page and try them all by yourself. With these simple steps, you will, for sure, empower your microservices architecture to step up your project quality. Feel free to comment and leave any article suggestions!