Tiny Loadbalancer ⚖️

Krishnan Priyanshu
3 min readMar 8, 2023

--

In this load balancer, we define a slice of backend servers and use a random number generator to select a server for each incoming request. We then use Go’s httputil.ReverseProxy package to proxy the request to the selected backend server.

Note that we use a sync.Mutex to protect access to the shared slice of backend servers. This is necessary to prevent race conditions when multiple requests are being processed concurrently.

Also, note that we use a time.Sleep() call to simulate some work being done by the backend servers

package main

import (
"fmt"
"math/rand"
"net/http"
"time"
)

// Define a slice of backend servers
var servers = []string{
"http://localhost:8001",
"http://localhost:8002",
"http://localhost:8003",
}

func main() {
// Create a new HTTP server
server := http.Server{
Addr: ":8080",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Choose a random backend server
rand.Seed(time.Now().UnixNano())
backend := servers[rand.Intn(len(servers))]

// Proxy the request to the backend server
fmt.Printf("Proxying request to %s\n", backend)
proxy := NewSingleHostReverseProxy(backend)
proxy.ServeHTTP(w, r)
}),
}

// Start the server
fmt.Println("Starting load balancer on port 8080")
server.ListenAndServe()
}

// A simple reverse proxy that only proxies requests to a single host
func NewSingleHostReverseProxy(target string) *httputil.ReverseProxy {
targetURL, _ := url.Parse(target)
return &httputil.ReverseProxy{Director: func(req *http.Request) {
req.URL.Scheme = targetURL.Scheme
req.URL.Host = targetURL.Host
req.URL.Path = singleJoiningSlash(targetURL.Path, req.URL.Path)
req.Host = targetURL.Host
}}
}

func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}

we can use goroutines in the load balancer code to handle incoming requests concurrently and improve the scalability of the load balancer and help improve the performance of the load balancer.

Here’s an updated version of the load balancer code that uses goroutines to handle incoming requests:

package main

import (
"fmt"
"math/rand"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"sync"
"time"
)

// Define a slice of backend servers
var servers = []string{
"http://localhost:8001",
"http://localhost:8002",
"http://localhost:8003",
}

// Define a mutex to protect access to the shared slice of servers
var mutex = &sync.Mutex{}

func main() {
// Create a new HTTP server
server := http.Server{
Addr: ":8080",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Use a goroutine to handle each incoming request concurrently
go func() {
// Choose a random backend server
backend := chooseBackendServer()

// Proxy the request to the backend server
fmt.Printf("Proxying request to %s\n", backend)
proxy := NewSingleHostReverseProxy(backend)
proxy.ServeHTTP(w, r)
}()
}),
}

// Start the server
fmt.Println("Starting load balancer on port 8080")
server.ListenAndServe()
}

// A simple function that chooses a random backend server from the slice
func chooseBackendServer() string {
mutex.Lock()
defer mutex.Unlock()
rand.Seed(time.Now().UnixNano())
return servers[rand.Intn(len(servers))]
}

// A simple reverse proxy that only proxies requests to a single host
func NewSingleHostReverseProxy(target string) *httputil.ReverseProxy {
targetURL, _ := url.Parse(target)
return &httputil.ReverseProxy{Director: func(req *http.Request) {
req.URL.Scheme = targetURL.Scheme
req.URL.Host = targetURL.Host
req.URL.Path = singleJoiningSlash(targetURL.Path, req.URL.Path)
req.Host = targetURL.Host
}}
}

func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
switch {
case aslash && bslash:
return a + b[1:]
case !aslash && !bslash:
return a + "/" + b
}
return a + b
}

In a real load balancer, you would replace this with actual work being done by the servers.

Hope you enjoyed reading this article.

--

--