Hone logo
Hone
Problems

Implement a Simple Round-Robin Load Balancer in Go

This challenge asks you to build a basic HTTP load balancer in Go that distributes incoming requests across a pool of backend servers using a round-robin strategy. Load balancing is crucial for improving application availability, scalability, and performance by distributing traffic across multiple instances of a service.

Problem Description

You need to create an HTTP server in Go that acts as a load balancer. This load balancer will receive incoming HTTP requests and forward them to one of several predefined backend HTTP servers. The selection of which backend server to forward the request to should follow a round-robin algorithm.

Key Requirements:

  1. Backend Server Configuration: The load balancer should be configurable with a list of backend server addresses (e.g., http://localhost:8081, http://localhost:8082).
  2. Round-Robin Distribution: Requests must be distributed among the backend servers in a sequential, cyclical manner. The first request goes to server 1, the second to server 2, and so on. When the end of the list is reached, it wraps around to the first server.
  3. Request Forwarding: The load balancer must forward the incoming HTTP request (including method, headers, and body) to the selected backend server.
  4. Response Handling: The load balancer should receive the response from the backend server and return it to the original client, preserving the status code, headers, and body.
  5. Error Handling: If a backend server is unreachable or returns an error, the load balancer should attempt to forward the request to the next available server in the round-robin sequence. If all backend servers fail, it should return an appropriate error to the client.

Expected Behavior:

When a client sends an HTTP request to the load balancer's address, the load balancer will:

  • Choose a backend server based on the round-robin algorithm.
  • Proxy the request to that backend server.
  • Return the backend server's response to the client.

Edge Cases to Consider:

  • Empty Backend List: What happens if no backend servers are provided?
  • Backend Server Failure: How to handle situations where a backend server is down or returns an error? Should the load balancer retry? (For this challenge, attempt to forward to the next available).
  • Zero-Time Response: If a backend server takes a very long time to respond, consider timeouts. (For this exercise, we'll assume standard Go HTTP client timeouts are sufficient).

Examples

Example 1:

Setup:

  • Load Balancer running at http://localhost:8080
  • Backend Server 1 running at http://localhost:8081
  • Backend Server 2 running at http://localhost:8082
  • Backend Server 3 running at http://localhost:8083

Client Request 1:

GET /users HTTP/1.1
Host: localhost:8080

Load Balancer Action: Selects http://localhost:8081 (first in list). Forwards the request.

Backend Server 1 Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 25

{"users": ["Alice", "Bob"]}

Client Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 25

{"users": ["Alice", "Bob"]}

Client Request 2:

GET /users HTTP/1.1
Host: localhost:8080

Load Balancer Action: Selects http://localhost:8082 (second in list). Forwards the request.

Backend Server 2 Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 27

{"users": ["Charlie", "David"]}

Client Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 27

{"users": ["Charlie", "David"]}

Client Request 3:

GET /users HTTP/1.1
Host: localhost:8080

Load Balancer Action: Selects http://localhost:8083 (third in list). Forwards the request.

Backend Server 3 Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 29

{"users": ["Eve", "Frank"]}

Client Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 29

{"users": ["Eve", "Frank"]}

Client Request 4:

GET /users HTTP/1.1
Host: localhost:8080

Load Balancer Action: Selects http://localhost:8081 (wraps around to the first server). Forwards the request.

Backend Server 1 Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 25

{"users": ["Alice", "Bob"]}

Client Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 25

{"users": ["Alice", "Bob"]}

Example 2: Backend Server Failure

Setup:

  • Load Balancer running at http://localhost:8080
  • Backend Server 1 running at http://localhost:8081 (initially OK)
  • Backend Server 2 running at http://localhost:8082 (DOWN)

Client Request 1:

GET /items HTTP/1.1
Host: localhost:8080

Load Balancer Action: Selects http://localhost:8081. Forwards the request.

Backend Server 1 Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 15

{"items": ["A", "B"]}

Client Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 15

{"items": ["A", "B"]}

Client Request 2:

GET /items HTTP/1.1
Host: localhost:8080

Load Balancer Action: Selects http://localhost:8082. It attempts to connect, but the server is down. The load balancer then tries the next available server. In this case, it would wrap around and try http://localhost:8081 again (or if there were more, it would continue sequentially). For this example, let's assume it tries http://localhost:8081 and it succeeds.

Backend Server 1 Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 17

{"items": ["C", "D"]}

Client Response:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 17

{"items": ["C", "D"]}

(Note: In a real-world scenario, you might want to mark http://localhost:8082 as unhealthy and stop sending requests to it for a period.)

Constraints

  • The load balancer should listen on localhost:8080.
  • The list of backend servers will be provided as a slice of strings in the main function.
  • Assume backend servers are reachable via HTTP.
  • Implement the core load balancing logic within a single Go program.
  • The number of backend servers will be between 1 and 10.
  • The round-robin index should be managed safely for concurrent requests (if using goroutines).

Notes

  • You'll need to create simple HTTP servers to act as your backend services for testing. These backend servers can just echo back a simple response indicating which server handled the request.
  • The net/http package in Go is your primary tool for building both the load balancer and the backend servers.
  • Consider how to manage the index for the round-robin selection safely, especially if your load balancer is expected to handle multiple concurrent requests. A sync.Mutex or atomic operations might be useful.
  • The httputil.ReverseProxy can significantly simplify the task of forwarding requests and handling responses. Look into its Director and ErrorHandler fields.
Loading editor...
go