Skip to content

HarshP34/go-load-balancer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HTTP Load Balancer in Go

A simple Layer 7 (HTTP) load balancer built in Go that forwards requests to backend servers transparently — the request reaches the backend exactly as it came from the client, and the response goes back to the client exactly as it came from the backend.

How It Works

%%{init: {'theme': 'base', 'themeVariables': {'actorBkg': '#2563eb', 'actorBorder': '#1d4ed8', 'actorTextColor': '#ffffff', 'actorLineColor': '#93c5fd', 'signalColor': '#ffffff', 'signalTextColor': '#ffffff', 'noteBkgColor': '#fef08a', 'noteTextColor': '#713f12', 'activationBkgColor': '#bfdbfe', 'activationBorderColor': '#2563eb'}}}%%
sequenceDiagram
    participant C as Client
    participant LB as Load Balancer (:8080)
    participant B1 as Backend :8081
    participant B2 as Backend :8082
    participant B3 as Backend :8083

    C->>LB: Request 1
    Note over LB: Round Robin selects :8081
    LB->>B1: Forward cloned request
    B1-->>LB: HTTP Response
    LB-->>C: Forward response

    C->>LB: Request 2
    Note over LB: Round Robin selects :8082
    LB->>B2: Forward cloned request
    B2-->>LB: HTTP Response
    LB-->>C: Forward response

    C->>LB: Request 3
    Note over LB: Round Robin selects :8083
    LB->>B3: Forward cloned request
    B3-->>LB: HTTP Response
    LB-->>C: Forward response
Loading

Each request is handled by two goroutines:

  • forward goroutine — clones the client request (method, headers, body, path) and sends it to the selected backend
  • reverse goroutine — copies the backend response (status code, headers, body) back to the client

They communicate over a channel. The main handler blocks until both goroutines finish.

Project Structure

load-balancer/
├── main.go                  # entry point — configure backends and algorithm here
├── backend/
│   └── main.go              # simple test backend server
└── balancer/
    ├── backend.go           # Backend struct with active connection counter
    ├── balancer.go          # core: ServeHTTP, forward/reverse goroutines
    ├── algorithm.go         # Algorithm interface
    ├── roundrobin.go        # Round Robin
    ├── leastconn.go         # Least Connections
    ├── iphash.go            # IP Hash
    └── weighted.go          # Weighted Round Robin

Algorithms

Round Robin

Rotates through all backends in order, one request at a time.

Request 1 → backend :8081
Request 2 → backend :8082
Request 3 → backend :8083
Request 4 → backend :8081  (wraps around)

Best for: backends with equal capacity and similar request costs.

algo := &balancer.RoundRobin{}

Weighted Round Robin

Same as Round Robin but each backend gets requests proportional to its weight.

backends: :8081 (weight=1), :8082 (weight=3), :8083 (weight=2)
expanded: [:8081, :8082, :8082, :8082, :8083, :8083]

Request 1 → :8081
Request 2 → :8082
Request 3 → :8082
Request 4 → :8082
Request 5 → :8083
Request 6 → :8083
Request 7 → :8081  (wraps around)

Best for: backends with different hardware capacity — give more traffic to stronger servers.

algo := balancer.NewWeightedRoundRobin(backends)

Note: must use NewWeightedRoundRobin(backends) — not &WeightedRoundRobin{} — so the expanded list is built from the weights before the first request.


Least Connections

Always picks the backend with the fewest active connections at that moment.

:8081 → 5 active connections
:8082 → 2 active connections  ← picked
:8083 → 8 active connections

Best for: mixed workloads where some requests take much longer than others (e.g. file uploads alongside simple API calls).

algo := &balancer.LeastConn{}

IP Hash

Hashes the client IP to always route the same client to the same backend.

client 192.168.1.10  →  hash → :8081  (always)
client 192.168.1.20  →  hash → :8083  (always)

Best for: stateful backends where a client must always reach the same server (e.g. session stored in memory).

algo := &balancer.IPHash{}

Running

Step 1 — Start the test backends

Open three terminals and run one backend per terminal:

go run ./backend/main.go 8081
go run ./backend/main.go 8082
go run ./backend/main.go 8083

Step 2 — Configure and start the load balancer

Edit main.go to set backends and choose an algorithm:

backends := []*balancer.Backend{
    balancer.NewBackend("http://localhost:8081", 1),
    balancer.NewBackend("http://localhost:8082", 3),
    balancer.NewBackend("http://localhost:8083", 2),
}

algo := balancer.NewWeightedRoundRobin(backends)

Then start it:

go run ./main.go

Step 3 — Send requests

curl http://localhost:8080/ping
curl http://localhost:8080/ping
curl http://localhost:8080/ping

Expected output (with Weighted Round Robin, weights 1:3:2):

pong from backend :8081
pong from backend :8082
pong from backend :8082
pong from backend :8082
pong from backend :8083
pong from backend :8083

Load balancer logs:

2026/05/26 14:00:01 client=127.0.0.1 method=GET path=/ping → backend=localhost:8081
2026/05/26 14:00:02 client=127.0.0.1 method=GET path=/ping → backend=localhost:8082
2026/05/26 14:00:03 client=127.0.0.1 method=GET path=/ping → backend=localhost:8082

Switching Algorithms

Change the algo line in main.go:

algo := &balancer.RoundRobin{}                    // Round Robin
algo := balancer.NewWeightedRoundRobin(backends)  // Weighted Round Robin
algo := &balancer.LeastConn{}                     // Least Connections
algo := &balancer.IPHash{}                        // IP Hash

About

A Layer 7 HTTP load balancer in Go supporting Round Robin, Weighted Round Robin, Least Connections, and IP Hash algorithms. Requests are transparently forwarded to backends using concurrent goroutines with a channel-based forward/reverse pipeline.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages