Help us shape the future of Go! Take the 2026 Go Developer Survey

loadbalancer

module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2026 License: MIT

README

loadbalancer

Go Reference

An HTTP reverse-proxy load balancer in Go, using only the standard library and fsnotify. It has three balancing strategies, active health checks, a passive circuit breaker per backend, retries for idempotent requests, config hot reload, and Prometheus metrics.

What it teaches

  • How httputil.ReverseProxy works, and how to put retry logic around it. The error handler records the failure instead of writing a 502, so the request can still go to another backend.
  • Balancing algorithms: round robin, nginx's smooth weighted round robin (weights 5,1,1 give a a b a c a a, not a a a a a b c), and least connections with rotating tie-breaks.
  • The two ways to detect a dead backend. Active checks probe /healthz on a timer. Passive checks (a circuit breaker) watch real traffic and work even without a health endpoint.
  • Changing configuration without dropping traffic. Unchanged backends keep their state, an invalid config is rejected, and the watcher looks at the directory because editors replace files by renaming.

Install

Install the binary with Go:

go install github.com/AbdeldjalilChougui/loadbalancer/cmd/loadbalancer@latest

Quick start

cp lb.example.json lb.json      # edit the backend URLs
go run ./cmd/loadbalancer -config lb.json
curl localhost:8080/            # proxied
curl localhost:9090/metrics     # Prometheus metrics
curl localhost:9090/status      # JSON view of the pool
kill -HUP <pid>                 # reload (saving the file also reloads)
Config
{
  "listen": ":8080",
  "admin": ":9090",
  "strategy": "weighted",              // round_robin | weighted | least_conn
  "retries": 2,                        // extra attempts for idempotent requests
  "backends": [{"url": "http://127.0.0.1:9001", "weight": 3}],
  "health_check": {"path": "/healthz", "interval": "5s", "timeout": "1s",
                   "healthy_threshold": 2, "unhealthy_threshold": 2},
  "circuit_breaker": {"failure_threshold": 5, "cooldown": "10s"}
}

Leave health_check.path empty to turn off active checks. Set failure_threshold to 0 to turn off the breaker.

How it works

request ─► pick(): candidates = healthy ∧ breaker ready ∧ not tried yet
              strategy.Pick(candidates) → breaker.Acquire()
         ─► forward(): ReverseProxy to the backend
              transport error → breaker.Failure(), retry if idempotent
              5xx             → breaker.Failure(), response is passed through
              otherwise       → breaker.Success()
  • Circuit breaker. After failure_threshold consecutive failures it goes closed → open. Once cooldown has passed, exactly one trial request is let through (half-open). A success closes the breaker and a failure opens it again. Ready() (side-effect free) is used to filter candidates and Acquire() reserves the single half-open slot.
  • Health checks. One goroutine probes every backend concurrently each interval. Thresholds stop one lost probe from making the pool flap. When a backend recovers, its breaker is reset too.
  • Retries. Only for GET/HEAD/OPTIONS/TRACE/PUT/DELETE requests without a body, and only when the backend could not be reached, so nothing has been written to the client yet. A 5xx response is never retried.
  • Reload. Apply validates the new config first. It then reuses *Backend values whose URL is unchanged, keeping their in-flight count, health and breaker, and restarts the health checker. SIGHUP and file changes (debounced) both trigger it.

Tests

go test -race ./...

All tests run against httptest backends: strategy order and distribution, least-conn with a blocked backend, breaker state transitions on a fake clock, retry around a dead backend, health checks taking a backend out and putting it back, reload keeping state, the fsnotify watcher, and the metrics output.

Status and limitations

This is a learning project. Things a production LB has that this one doesn't:

  • Only HTTP/1.1 and HTTP/2 to the client through net/http. There is no TLS termination config (put it behind something, or add ListenAndServeTLS), no WebSocket-specific handling beyond what ReverseProxy already does, and no TCP (L4) mode.
  • No sticky sessions, no consistent hashing, no slow start for recovered backends, and no outlier detection beyond consecutive failures.
  • Changing listen or admin needs a restart. The reload logs a warning.
  • Metrics are written by hand in the Prometheus text format. There are no latency histograms.
  • The breaker counts any 5xx from the backend as a failure. That includes a 500 caused by one bad request.

License

MIT. See LICENSE.

Directories

Path Synopsis
cmd
loadbalancer command
Command loadbalancer runs the HTTP load balancer.
Command loadbalancer runs the HTTP load balancer.
Package config loads the load balancer's JSON configuration.
Package config loads the load balancer's JSON configuration.
Package lb is an HTTP load balancer: a reverse proxy that spreads requests over backends with a pluggable strategy, takes unhealthy backends out of rotation (active health checks plus a passive circuit breaker per backend), retries idempotent requests, reloads its config without dropping traffic, and exposes Prometheus metrics.
Package lb is an HTTP load balancer: a reverse proxy that spreads requests over backends with a pluggable strategy, takes unhealthy backends out of rotation (active health checks plus a passive circuit breaker per backend), retries idempotent requests, reloads its config without dropping traffic, and exposes Prometheus metrics.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL