Client-Side Load Balancing in Spring Boot Microservices

In this tutorial, we will create two Spring Boot microservices and use client-side load balancing to distribute requests among multiple microservice instances. We will also use Spring Cloud LoadBalancer and Eureka for service discovery.

Introduction to Client-Side Load Balancing

Client-side load Balancing is a technique where the client, instead of a central server, decides which instance of a service to send the request to. This approach can help distribute the load better among service instances and increase fault tolerance. Spring Cloud provides support for client-side load balancing through Spring Cloud LoadBalancer.

Prerequisites

  • JDK 17 or later
  • Maven or Gradle
  • Docker
  • IDE (IntelliJ IDEA, Eclipse, etc.)

Step 1: Set Up Eureka Server

1.1 Create the Project

Use Spring Initializr to create a new project with the following dependencies:

  • Eureka Server

1.2 Configure application.properties

Set up the application properties for the Eureka Server.

server.port=8761
spring.application.name=eureka-server

eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

Explanation:

  • server.port=8761: Sets the port for the Eureka Server.
  • spring.application.name=eureka-server: Names the application.
  • eureka.client.register-with-eureka=false: Indicates that the Eureka Server itself should not try to register with another Eureka Server.
  • eureka.client.fetch-registry=false: Indicates that the Eureka Server should not attempt to fetch registry information from another Eureka Server.

1.3 Enable Eureka Server

Add the @EnableEurekaServer annotation to the main application class.

package com.example.eurekaserver;

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);
    }
}

Step 2: Set Up service-a

2.1 Create the Project

Use Spring Initializr to create a new project with the following dependencies:

  • Spring Web
  • Eureka Discovery Client

2.2 Configure application.properties

Set up the application properties for service-a.

server.port=0
spring.application.name=service-a

eureka.client.service-url.default-zone=http://localhost:8761/eureka/

Explanation:

  • server.port=0: Lets the application choose a random available port.
  • spring.application.name=service-a: Names the application.
  • eureka.client.service-url.default-zone=http://localhost:8761/eureka/: Specifies the Eureka Server URL for service registration.

2.3 Enable Eureka Client

Add the @EnableDiscoveryClient annotation to the main application class.

package com.example.servicea;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceAApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceAApplication.class, args);
    }
}

Explanation:

  • @EnableDiscoveryClient: Indicates that this application should register with a Eureka Server for service discovery.

2.4 Create a Controller

Create a controller to handle requests.

package com.example.servicea;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ServiceAController {

    @GetMapping("/service-a")
    public String getServiceA() {
        return "Response from Service A";
    }
}

Explanation:

  • @RestController: Marks this class as a REST controller.
  • @GetMapping("/service-a"): Maps GET requests to /service-a to this method.

Step 3: Set Up service-b (Client)

3.1 Create the Project

Use Spring Initializr to create a new project with the following dependencies:

  • Spring Web
  • Eureka Discovery Client
  • Spring Cloud LoadBalancer

3.2 Configure application.properties

Set up the application properties for service-b.

server.port=8081
spring.application.name=service-b

eureka.client.service-url.default-zone=http://localhost:8761/eureka/

Explanation:

  • server.port=8081: Sets the port for the client service.
  • spring.application.name=service-b: Names the application.
  • eureka.client.service-url.default-zone=http://localhost:8761/eureka/: Specifies the Eureka Server URL for service registration.

3.3 Enable Eureka Client

Add the @EnableDiscoveryClient annotation to the main application class.

package com.example.serviceb;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
public class ServiceBApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceBApplication.class, args);
    }
}

Explanation:

  • @EnableDiscoveryClient: Indicates that this application should register with a Eureka Server for service discovery.

3.4 Create a RestTemplate Bean

Create a configuration class to define a RestTemplate bean with load balancing.

package com.example.serviceb;

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 AppConfig {

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

Explanation:

  • @Bean: Marks this method as a bean producer.
  • @LoadBalanced: Adds client-side load balancing to the RestTemplate.

3.5 Create a Controller

Create a controller to handle requests and use the RestTemplate to call service-a.

package com.example.serviceb;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class ServiceBController {

    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/service-b")
    public String callServiceA() {
        return restTemplate.getForObject("http://service-a/service-a", String.class);
    }
}

Explanation:

  • @RestController: Marks this class as a REST controller.
  • @GetMapping("/service-b"): Maps GET requests to /service-b to this method.
  • RestTemplate restTemplate: Injects the RestTemplate with load balancing.
  • restTemplate.getForObject("http://service-a/service-a", String.class): Uses the service name service-a to call the endpoint, and the load balancer will distribute the request among available instances.

Step 4: Run the Services

  1. Start the Eureka Server: Run the EurekaServerApplication class.
  2. Start multiple instances of service-a:
    • Use the following command to start the first instance:
      mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dserver.port=8082"
      
    • Use the following command to start the second instance:
      mvn spring-boot:run -Dspring-boot.run.jvmArguments="-Dserver.port=8083"
      
  3. Start service-b: Run the ServiceBApplication class.

Step 5: Test the Load Balancing

Open your browser or use a tool like Postman to test the endpoint:

  • service-b: http://localhost:8081/service-b

Make multiple requests to this endpoint. You should see responses from different instances of service-a, indicating that the load balancer is distributing the requests.

Conclusion

You have successfully set up client-side load balancing in a Spring Boot microservices architecture using Eureka for service discovery and Spring Cloud LoadBalancer. This setup allows you to distribute requests among multiple instances of a microservice, increasing fault tolerance and scalability. This example can be expanded to include more microservices and more complex load balancing and routing logic.


Comments