July 16, 2021

Spring Boot Microservices Eureka + Ribbon

In the article Spring Boot Microservices example we saw an example of Spring Boot Microservice where we used Eureka for service registration and discovery. In this post we’ll extend that example further to see how to use both Eureka and Ribbon load balancer with Spring Boot to load the balance among micro services instances.

In the example there were two separate services User and Account which were registered with Eureka. From User service there was a call to Account service to fetch some data. In this post we’ll see how to configure more than one instances of Account service and use Ribbon load balancer to route the calls among those instances of Account service.

Load Balancing

In simple terms load balancing means distributing the load across several resources rather than putting all the load on a single resource. That helps in increasing throughput as there are more resources to share the load, increases reliability as there are more redundant resource to process the request even if any one resource goes down so there is no single point of failure.

Using Ribbon load balancing with Eureka

As we know Eureka is used for service registration and discovery where as Ribbon is a client side load balancer. Here I’ll try to explain how these two tools work together.

Eureka server maintains a service registry by registering each microservice with the Eureka server. When the inter-service communication happens, calling service interrogates the service registry using DiscoveryClient and gets in return all the instances of the called microservice. Now the question is, out of all the returned instances which one to call?

That’s where client load balancer like Ribbon comes in the picture. Client side load balancer uses an algorithm, like round robin (calling each instance in sequence) or using zone information (to locate a server in the same zone as the client), to get the instance of service that has to be called.

Spring Boot Microservice with loadbalancing

Spring Boot Micro Service with Ribbon example

In the Spring Boot Microservices example we have already seen how to configure Eureka server and how to register micro services User and Account with it by enabling discovery client (Using @EnableDiscoveryClient (or @EnableEurekaClient) annotation).

One problem was the URL used while calling Account service from User which hardcodes the host and port. That means every time same service will be called even if you create more instances of Account.

List<Account> accounts = new RestTemplate().exchange(
       "http://localhost:9000/accounts/{empId}", HttpMethod.GET, null, new
       ParameterizedTypeReference<List<Account>>(){}, id).getBody();

What we want is an abstraction so that the correct host and port is resolved at run time, that’s what we’ll try to configure using Netflix’s Ribbon load balancing service. To enable Ribbon you need to add the following dependency in pom.xml

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

Though this dependency is added automatically when you add dependency for eureka client. So, this dependency will suffice.

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

Creating another instance of Account Microservice

Now we want to scale up our service and need multiple copies of Account. We already created a Spring Boot project for the same with the following in application.properties.

eureka.client.service-url.default-zone=http://localhost:8761/eureka
server.port=9000
spring.application.name=account

After starting this instance you can change the port to 9001 and start the Account application again. That way you will have two instances one listening at port 9000 and another at port 9001.

You can also create a Separate Spring Boot project copy the files from Account and paste the following in application.properties

eureka.client.service-url.default-zone=http://localhost:8761/eureka
server.port=9001
spring.application.name=account

Either way you will have two instances running of the same service. You can verify it in Eureka Server by accessing URL - http://localhost:8761/

Eureka with ribbon

Creating LoadBalanced aware RestTemplate

Since we are using an instance of RestTemplate to make a call to another Service so we can make the RestTemplate bean load balanced aware. That can be done using @LoadBalanced annotation which instructs Netflix Ribbon to wrap this RestTemplate bean with load balancing advice.

Create a Config.java class in SpringBootUser project as given below.

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;
 
@Configuration 
public class Config { 
  @Bean 
  @LoadBalanced
  RestTemplate restTemplate() { 
    return new RestTemplate(); 
  } 
}

Injecting the load balanced RestTemplate

Now you can inject this load balanced RestTemplate into the UserService. Then, in the URL, you can use the logical name of the service that was used to register it with Eureka. That’s how we are using this URL http://ACCOUNT/accounts/{empId} to access Account MicroService instance.

import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.client.RestTemplate;

@Service
public class UserService {
  @Autowired
  private RestTemplate restTemplate;
    public List<Account> showEmployees(@PathVariable("id") String id) {
    System.out.println(id);
    
    List<Account> accounts = restTemplate.exchange(
    "http://ACCOUNT/accounts/{empId}", HttpMethod.GET, null, new
    ParameterizedTypeReference<List<Account>>(){}, id).getBody();
    // Another call (to demo load balancing)
    accounts = restTemplate.exchange(
            "http://ACCOUNT/accounts/{empId}", HttpMethod.GET, null, new
            ParameterizedTypeReference<List<Account>>(){}, id).getBody();
    return accounts;          
  }
}

Testing the application

Once the changes are done start all the applications i.e. Eureka Server, User MicroService and two instances of Account MicroService.

In the console of SpringBootUser application you can see that the Load balancer has recognized the list of servers and one of them will be called to service the request.

2020-04-25 17:02:06.405  INFO 9908 --- [nio-8080-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2020-04-25 17:02:06.573  INFO 9908 --- [nio-8080-exec-1] c.n.u.concurrent.ShutdownEnabledTimer    : Shutdown hook installed for: NFLoadBalancer-PingTimer-ACCOUNT
2020-04-25 17:02:06.575  INFO 9908 --- [nio-8080-exec-1] c.netflix.loadbalancer.BaseLoadBalancer  : Client: ACCOUNT instantiated a LoadBalancer: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ACCOUNT,current list of Servers=[],Load balancer stats=Zone stats: {},Server stats: []}ServerList:null
2020-04-25 17:02:06.637  INFO 9908 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer      : Using serverListUpdater PollingServerListUpdater
2020-04-25 17:02:06.905  INFO 9908 --- [nio-8080-exec-1] c.netflix.config.ChainedDynamicProperty  : Flipping property: ACCOUNT.ribbon.ActiveConnectionsLimit to use NEXT property: niws.loadbalancer.availabilityFilteringRule.activeConnectionsLimit = 2147483647
2020-04-25 17:02:06.923  INFO 9908 --- [nio-8080-exec-1] c.n.l.DynamicServerListLoadBalancer      : DynamicServerListLoadBalancer for client ACCOUNT initialized: DynamicServerListLoadBalancer:{NFLoadBalancer:name=ACCOUNT,current list of Servers=[user:9000, user:9001],Load balancer stats=Zone stats: {defaultzone=[Zone:defaultzone;	Instance count:2;	Active connections count: 0;	Circuit breaker tripped count: 0;	Active connections per server: 0.0;]
},Server stats: [[Server:user:9000;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 05:30:00 IST 1970;	First connection made: Thu Jan 01 05:30:00 IST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
, [Server:user:9001;	Zone:defaultZone;	Total Requests:0;	Successive connection failure:0;	Total blackout seconds:0;	Last connection made:Thu Jan 01 05:30:00 IST 1970;	First connection made: Thu Jan 01 05:30:00 IST 1970;	Active Connections:0;	total failure count in last (1000) msecs:0;	average resp time:0.0;	90 percentile resp time:0.0;	95 percentile resp time:0.0;	min resp time:0.0;	max resp time:0.0;	stddev resp time:0.0]
]}ServerList:org.springframework.cloud.netflix.ribbon.eureka.DomainExtractingServerList@7ef76128

Accessing services

You can start the service by accessing URL- http://localhost:8080/user/1

Try to access the same URL in different browser tabs and you can see that requests are getting divided between two Account service instances.

Download source code- https://github.com/knpcode/SpringBoot-MicroService-Ribbon

That's all for the topic Spring Boot Microservices Eureka + Ribbon. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment