A demonstration of a simple but functional load balancer implementation using Spring Boot, showcasing service discovery, health checking, and round-robin load distribution.
The system consists of three main components:
graph TB
%% Styling
classDef clientStyle fill:#f9f9f9,stroke:#666,stroke-width:2px
classDef loadBalancerStyle fill:#e6f3ff,stroke:#3385ff,stroke-width:2px
classDef serviceStyle fill:#f0fff0,stroke:#33cc33,stroke-width:2px
classDef componentStyle fill:#fff,stroke:#999,stroke-width:1px
%% Client Layer
subgraph Clients["Client Layer"]
direction LR
C1[Web Client]
C2[Mobile Client]
C3[API Client]
end
%% Load Balancer Layer
subgraph LoadBalancer["Load Balancer Layer"]
direction TB
PC[Proxy Controller]
LBS[Load Balancer<br>Service]
HCS[Health Check<br>Service]
PC --> LBS
LBS --> HCS
end
%% API Services Layer
subgraph Services["API Service Cluster"]
direction LR
API1[API Service<br>:8081]
API2[API Service<br>:8082]
API3[API Service<br>:8083]
API4[API Service<br>:8084]
API5[API Service<br>:8085]
end
%% Connections
C1 --> |HTTP Requests| PC
C2 --> |HTTP Requests| PC
C3 --> |HTTP Requests| PC
PC --> |Round Robin| API1
PC --> |Round Robin| API2
PC --> |Round Robin| API3
PC --> |Round Robin| API4
PC --> |Round Robin| API5
API1 --> |Heartbeat 5s| HCS
API2 --> |Heartbeat 5s| HCS
API3 --> |Heartbeat 5s| HCS
API4 --> |Heartbeat 5s| HCS
API5 --> |Heartbeat 5s| HCS
- Round-robin load balancing
- Dynamic service registration
- Automatic health checking
- Request forwarding for all HTTP methods
- Docker support
- Spring Boot Actuator integration
- Modular architecture
- Java 21
- Spring Boot 3.2.0
- Docker & Docker Compose
- Maven
- Project Lombok
- Spring Boot Actuator
.
├── api-service/ # API service module
├── common/ # Shared DTOs and utilities
├── load-balancer/ # Load balancer core module
├── docker-compose.yml # Docker composition
└── pom.xml # Parent POM file
-
load-balancer: Core load balancing functionality
- Load balancing service
- Health checking
- Request proxying
-
api-service: Demo service for testing
- Simple REST endpoints
- Health reporting
- Multiple instance support
-
common: Shared components
- DTOs
- Exceptions
- Common utilities
- Java 21 JDK
- Docker and Docker Compose
- Maven 3.9+
# Clone the repository
git clone [repository-url]
# Build with Maven
./mvnw clean package
# Build with Docker Compose
docker-compose build
# Start all services using Docker Compose
docker-compose up
# Or start individual components with Maven
./mvnw spring-boot:run -pl load-balancer
./mvnw spring-boot:run -pl api-service
server:
port: 8080
spring:
application:
name: load-balancer
management:
endpoints:
web:
exposure:
include: health,info,metrics
server:
port: ${SERVER_PORT:8081}
loadbalancer:
url: ${LOADBALANCER_URL:http://localhost:8080}
- Start the services:
docker-compose up
- Send requests to the load balancer:
curl http://localhost:8080/api/demo
- Monitor distribution of requests across services in the logs.
- Monitor service health:
curl http://localhost:8080/actuator/health
- View registered services:
curl http://localhost:8080/actuator/metrics
The project includes Dockerfile for each service and a docker-compose.yml for orchestration. To run with Docker:
# Build and start all services
docker-compose up --build
# Scale API services
docker-compose up --scale api-service=5
# Stop all services
docker-compose down
The application exposes several endpoints through Spring Boot Actuator:
/actuator/health
: Health information/actuator/info
: Application information/actuator/metrics
: Metrics data
The load balancer uses a round-robin algorithm implemented with an AtomicInteger:
public ServiceNode getNextAvailableNode() {
List<ServiceNode> healthyNodes = serviceNodes.values().stream()
.filter(ServiceNode::healthy)
.toList();
if (healthyNodes.isEmpty()) {
throw new IllegalStateException("No healthy nodes available");
}
int index = currentNodeIndex.getAndIncrement() % healthyNodes.size();
return healthyNodes.get(index);
}
Services send heartbeats every 5 seconds, and nodes are considered unhealthy after 30 seconds of no heartbeat:
@Scheduled(fixedRate = 10000) // Check every 10 seconds
public void checkNodeHealth() {
Instant threshold = Instant.now().minus(HEALTH_CHECK_TIMEOUT_SECONDS,
ChronoUnit.SECONDS);
loadBalancerService.getAllNodes().stream()
.filter(node -> node.lastHeartbeat().isBefore(threshold))
.forEach(node -> loadBalancerService.removeNode(node.serviceId()));
}