Крошечный балансировщик нагрузки ⚖️

В этом балансировщике нагрузки мы определяем часть внутренних серверов и используем генератор случайных чисел для выбора сервера для каждого входящего запроса. Затем мы используем пакет Go httputil.ReverseProxy для проксирования запроса на выбранный внутренний сервер.

Обратите внимание, что мы используем sync.Mutex для защиты доступа к общему фрагменту внутренних серверов. Это необходимо для предотвращения состояния гонки при одновременной обработке нескольких запросов.

Также обратите внимание, что мы используем вызов time.Sleep() для имитации некоторой работы, выполняемой внутренними серверами.

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
}

мы можем использовать горутины в коде балансировщика нагрузки для одновременной обработки входящих запросов и улучшения масштабируемости балансировщика нагрузки и повышения производительности балансировщика нагрузки.

Вот обновленная версия кода балансировщика нагрузки, который использует горутины для обработки входящих запросов:

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
}

В реальном балансировщике нагрузки вы бы заменили это фактической работой, выполняемой серверами.

Надеюсь, вам понравилось читать эту статью.