Auto-Scaling Microservices with Eureka and Spring Boot

Auto-scaling Microservices with Eureka and Spring Boot involves leveraging Eureka for the service discovery and Spring Boot for building microservices that can dynamically scale based on the demand. This setup can allows for efficient resource utilization and it can ensure that the system can handle varying loads seamlessly.

Eureka is a service registry that allows microservices to self-register and discover new users Spring Boot can simplify the development of microservices by providing features such as embedded servers and dependency injection.

Auto-scaling can configure the number of instances of microservices based on metrics such as CPU usage or incoming requests.

Step-by-step implementation of the Auto-Scaling Microservices with Eureka and Spring Boot

Below are the steps to implement Auto-Scaling Microservices with Eureka and Spring Boot.

Set up the Eureka-server

Step 1: Create a Spring project using Spring Initializr, and include the following dependencies:

Dependencies:

  • Spring Web
  • Eureka Server
  • Spring Dev Tools
  • Lombok

After creating the Spring project, the file structure will resemble the image below.


Step 2: Open the application.properties file and add the following code to configure the server port and Eureka server settings for the project.

spring.application.name=eureka-server-config

server.port=9099
eureka.instance.prefer-ip-address=true
eureka.client.fetch-registry=true
eureka.client.register-with-eureka=true
eureka.client.service-url.defaultZone= http://localhost:9099/eureka


Step 3: In the main class, include the annotation @EnableEurekaServer to activate the Eureka server functionality.

Java
package org.example.eurekaserverconfig;

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

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerConfigApplication {

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

}


pom.xml:

XML
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>org.example</groupId>
    <artifactId>eureka-server-config</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eureka-server-config</name>
    <description>eureka-server-config</description>
    <properties>
        <java.version>17</java.version>
        <spring-cloud.version>2023.0.1</spring-cloud.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


Step 4: Once the Spring project is completed and run as a Spring application successfully, it will start at port 9099.


Create the User-service microservice

Step 1: Create the Spring project using Spring Initializr. When creating the project, add the following dependencies:

Dependencies:

  • Spring Web
  • Eureka Server Client
  • Spring Dev Tools
  • Lombok

After creating the Spring project, the file structure should resemble the image below.


Step 2: Open the application.properties file and insert the following code to configure the server port and Eureka client settings for the project.

spring.application.name=user-service
server.port=8086


management.endpoints.web.exposure.include=*
management.endpoint.metrics.enabled=true
management.prometheus.metrics.export.enabled=true


autoscaler.cpu.threshold=0.75

eureka.instance.prefer-ip-address=true
eureka.client.fetch-registry=true
eureka.client.register-with-eureka=true
eureka.client.service-url.defaultZone= http://localhost:9099/eureka

spring.data.mongodb.uri=mongodb://localhost:27017/demo


Step 3: Create a new package named “model”. Within this package, create a new Java class named “User”.

Go to src > org.example.userservice > model > User and put the below code.

Java
package org.example.userservice.model;

import lombok.*;


import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    @Id
    private String id;
    private String username;
    private String email;
}


Step 4: Create the new package and it named as the repository in that package create the new Java class and it named as UserRepository .

Go to src > org.example.userservice > repository > UserRepository and put the below code.

Java
package org.example.userservice.repository;

import org.example.userservice.model.User;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends MongoRepository<User, String> {
    // You can add custom query methods here if needed
}


Step 5: Create the new package and it named as the service in that package create the new Java class and it named as UserService.

Go to src > org.example.userservice > service > UserService and put the below code.

Java
package org.example.userservice.service;

import org.example.userservice.model.User;

import java.util.List;

public interface UserService {
    List<User> getAllUsers();

    User createUser(User user);

    void deleteUser(String userId);
}


Step 6: Create the new package and it named as the service in that package create the new Java class and it named as UserServiceImpl.

Go to src > org.example.userservice > service > UserServiceImpl and put the below code.

Java
package org.example.userservice.service;

import org.example.userservice.repository.UserRepository;
import org.example.userservice.model.User;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;


@Service
public class UserServiceImpl implements UserService {

    private final UserRepository userRepository;

    @Autowired
    public UserServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public List<User> getAllUsers() {
        return userRepository.findAll();
    }



    @Override
    public User createUser(User user) {
        return userRepository.save(user);
    }


    @Override
    public void deleteUser(String userId) {
        userRepository.deleteById(userId);
    }
}


Step 7: Create the new Java class and it named as AutoScaler.

Go to src > org.example.userservice > AutoScaler and put the below code.

Java
package org.example.userservice;



import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

@Component
public class AutoScaler {
    @Value("${autoscaler.cpu.threshold}")
    private double cpuThreshold;

    @Value("${spring.application.name}")
    private String serviceName;

    @Scheduled(fixedRate = 60000) // Check every minute
    public void autoScale() {
        // Retrieve CPU usage for the service
        double cpuUsage = getCpuUsage(serviceName);

        if (cpuUsage > cpuThreshold) {
            // Scale up
            // Logic to increase the number of instances
            System.out.println("Scaling up " + serviceName + " due to high CPU usage.");
        } else {
            // Scale down
            // Logic to decrease the number of instances
            System.out.println("Scaling down " + serviceName + " due to low CPU usage.");
        }
    }

    private double getCpuUsage(String serviceName) {
        // Logic to retrieve CPU usage for the given service
        // Example: Use Micrometer to get CPU metrics
        return 0.5; // Placeholder value for demonstration
    }
}

This AutoScaler class is designed to the automatically scale the number of the instances of the microservice based on the CPU usage.

How Auto Scaling works:

  • Monitoring: The autoscale() method can periodically the checks the CPU usage of the microservices using getCpuUsage() method.
  • Decision Making: If the CPU usage exceeds the predefined the threshold and it can indicating the high demand then the system initiates the scale-up action. If the CPU usage is the below threshold, indicating the low demand then the system initiates the scale down action.
  • Scaling Actions: It can involves the dynamically the adjusting the number of the instances of the microservices. For the scale-up, additional instances are deployed to the handle the increased demand. For the scale-down, excess the instances are terminated to the conserve resources.

After triggering the auto-scaling logic, We can verify whether the application scales up or down by the checking the number of instances or any other relevant metrics in the environment (e.g., monitoring dashboard).


Step 8: Create a new package named “controller”. Within this package, create a new Java class named “UserController”.

Go to src > org.example.userservice > Controller > UserController and put the below code.

Java
package org.example.userservice.controller;

import org.example.userservice.model.User;
import org.example.userservice.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/users")
public class UserController {

    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping
    public ResponseEntity<List<User>> getAllUsers() {
        List<User> users = userService.getAllUsers();
        return ResponseEntity.ok(users);
    }



    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(createdUser);
    }


    @DeleteMapping("/{userId}")
    public ResponseEntity<Void> deleteUser(@PathVariable String userId) {
        userService.deleteUser(userId);
        return ResponseEntity.noContent().build();
    }
}


Step 9: Run the application

Once completed the project after that run the application once runs the application successfully looks like the below image.


Eureka Dashboard:


APIS Outputs:

Create the user:

POST http://localhost://8086/api/users

Output:


Get the users:

GET http://localhost://8086/api/users

Output:

In the above project, this is the basic of the setting up the project for the auto scaling microservices with the Eureka and Spring Boot. Further the configuration and development are needed for the complete solution. It can including the implementing the auto-scaling logic.



Contact Us