Build a Simple Go Load Balancer with Round‑Robin and Health Checks
This article walks through building a simple Go load balancer using round‑robin distribution, reverse proxy, atomic indexing, health checks, and concurrency‑safe data structures, providing complete code examples and explanations for each component.
Working Principle
Load balancing distributes requests among backend services, increasing scalability and availability. If a backend fails, the balancer should route only to healthy nodes.
Round‑Robin Strategy
We implement the simplest strategy, round‑robin, which cycles through backends equally.
Data Structures
<code>type Backend struct {
URL *url.URL
Alive bool
mux sync.RWMutex
ReverseProxy *httputil.ReverseProxy
}</code> <code>type ServerPool struct {
backends []*Backend
current uint64
}</code>ReverseProxy
Go's httputil.ReverseProxy forwards requests to a backend and returns the response.
<code>u, _ := url.Parse("http://localhost:8080")
rp := httputil.NewSingleHostReverseProxy(u)
http.HandlerFunc(rp.ServeHTTP)</code>Selection Process
NextIndex uses an atomic increment to obtain the next backend index modulo the pool size.
<code>func (s *ServerPool) NextIndex() int {
return int(atomic.AddUint64(&s.current, 1) % uint64(len(s.backends)))
}</code>Get Alive Backend
GetNextPeer loops through the backends starting from the next index to find an alive one.
<code>// GetNextPeer returns next active peer to take a connection
func (s *ServerPool) GetNextPeer() *Backend {
// loop entire backends to find an Alive backend
next := s.NextIndex()
l := len(s.backends) + next // start from next and move a full cycle
for i := next; i < l; i++ {
idx := i % len(s.backends)
if s.backends[idx].IsAlive() {
if i != next {
atomic.StoreUint64(&s.current, uint64(idx)) // mark the current one
}
return s.backends[idx]
}
}
return nil
}</code>Concurrency Handling
The Alive field of Backend is protected by a RWMutex to allow safe concurrent reads and writes.
<code>// SetAlive for this backend
func (b *Backend) SetAlive(alive bool) {
b.mux.Lock()
b.Alive = alive
b.mux.Unlock()
}
// IsAlive returns true when backend is alive
func (b *Backend) IsAlive() (alive bool) {
b.mux.RLock()
alive = b.Alive
b.mux.RUnlock()
return
}</code>Request Handling
The lb function selects a peer and forwards the request via its reverse proxy, returning an error if no healthy backend is available.
<code>func lb(w http.ResponseWriter, r *http.Request) {
peer := serverPool.GetNextPeer()
if peer != nil {
peer.ReverseProxy.ServeHTTP(w, r)
return
}
http.Error(w, "Service not available", http.StatusServiceUnavailable)
}</code>Health Checks
A passive health check periodically pings each backend to determine its status and updates the Alive flag.
<code>// isBackendAlive checks whether a backend is alive by establishing a TCP connection
func isBackendAlive(u *url.URL) bool {
timeout := 2 * time.Second
conn, err := net.DialTimeout("tcp", u.Host, timeout)
if err != nil {
log.Println("Site unreachable, error: ", err)
return false
}
_ = conn.Close()
return true
}</code> <code>// HealthCheck pings the backends and updates the status
func (s *ServerPool) HealthCheck() {
for _, b := range s.backends {
status := "up"
alive := isBackendAlive(b.URL)
b.SetAlive(alive)
if !alive {
status = "down"
}
log.Printf("%s [%s]\n", b.URL, status)
}
}</code> <code>// healthCheck runs a routine for checking status of the backends every 20 secs
func healthCheck() {
t := time.NewTicker(time.Second * 20)
for {
select {
case <-t.C:
log.Println("Starting health check...")
serverPool.HealthCheck()
log.Println("Health check completed")
}
}
}</code>Start the health‑check routine in a separate goroutine: go healthCheck() .
Conclusion
The implementation provides a basic load balancer; further enhancements could include weighted round‑robin or least‑connection algorithms, heap‑based alive node tracking, statistics collection, configuration file support, and more.
360 Zhihui Cloud Developer
360 Zhihui Cloud is an enterprise open service platform that aims to "aggregate data value and empower an intelligent future," leveraging 360's extensive product and technology resources to deliver platform services to customers.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.